From 83cb70db17e10ee9c42ba5c20e5547efff6a9118 Mon Sep 17 00:00:00 2001 From: MaxOhn Date: Mon, 19 Aug 2019 22:54:07 +0200 Subject: [PATCH 001/996] Added initial AimAssist mod --- osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs | 97 +++++++++++++++++++ osu.Game.Rulesets.Osu/OsuRuleset.cs | 1 + 2 files changed, 98 insertions(+) create mode 100644 osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs new file mode 100644 index 0000000000..325c5d0c13 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs @@ -0,0 +1,97 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Osu.Mods +{ + internal class OsuModAimAssist : Mod, IApplicableToDrawableHitObjects, IUpdatableByPlayfield + { + public override string Name => "AimAssist"; + public override string Acronym => "AA"; + public override IconUsage Icon => FontAwesome.Solid.MousePointer; + public override ModType Type => ModType.Fun; + public override string Description => "No need to chase the circle, the circle chases you"; + public override double ScoreMultiplier => 1; + public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModAutoplay), typeof(OsuModWiggle), typeof(OsuModTransform) }; + + private HashSet movingObjects = new HashSet(); + private int updateCounter = 0; + + public void Update(Playfield playfield) + { + // Avoid crowded judgment displays + playfield.DisplayJudgements.Value = false; + + // Object destination updated when cursor updates + playfield.Cursor.ActiveCursor.OnUpdate += drawableCursor => + { + // ... every 500th cursor update iteration + // (lower -> potential lags ; higher -> easier to miss if cursor too fast) + if (updateCounter++ < 500) return; + updateCounter = 0; + + // First move objects to new destination, then remove them from movingObjects set if they're too old + movingObjects.RemoveWhere(d => + { + var currentTime = playfield.Clock.CurrentTime; + var h = d.HitObject; + d.ClearTransforms(); + switch (d) + { + case DrawableHitCircle circle: + d.MoveTo(drawableCursor.DrawPosition, Math.Max(0, h.StartTime - currentTime)); + return currentTime > h.StartTime; + case DrawableSlider slider: + + // Move slider to cursor + if (currentTime < h.StartTime) + d.MoveTo(drawableCursor.DrawPosition, Math.Max(0, h.StartTime - currentTime)); + + // Move slider so that sliderball stays on the cursor + else + d.MoveTo(drawableCursor.DrawPosition - slider.Ball.DrawPosition, Math.Max(0, h.StartTime - currentTime)); + return currentTime > (h as IHasEndTime).EndTime - 50; + case DrawableSpinner spinner: + // TODO + return true; + } + return true; // never happens(?) + }); + }; + } + + public void ApplyToDrawableHitObjects(IEnumerable drawables) + { + foreach (var drawable in drawables) + drawable.ApplyCustomUpdateState += drawableOnApplyCustomUpdateState; + } + + private void drawableOnApplyCustomUpdateState(DrawableHitObject drawable, ArmedState state) + { + if (!(drawable is DrawableOsuHitObject d)) + return; + var h = d.HitObject; + using (d.BeginAbsoluteSequence(h.StartTime - h.TimePreempt)) + movingObjects.Add(d); + } + } + + /* + * TODOs + * - remove object timing glitches / artifacts + * - remove FollowPoints + * - automate sliders + * - combine with OsuModRelax (?) + * - must be some way to make this more effictient + * + */ +} diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index d50d4f401c..9dbc144501 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -136,6 +136,7 @@ namespace osu.Game.Rulesets.Osu new OsuModSpinIn(), new MultiMod(new OsuModGrow(), new OsuModDeflate()), new MultiMod(new ModWindUp(), new ModWindDown()), + new OsuModAimAssist(), }; case ModType.System: From 3434458e0a2119e1ee3171b96186f1b2cc4a0a53 Mon Sep 17 00:00:00 2001 From: MaxOhn Date: Mon, 19 Aug 2019 23:51:47 +0200 Subject: [PATCH 002/996] Minor formatting changes --- osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs index 325c5d0c13..26f0d64a5d 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs @@ -23,8 +23,8 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModAutoplay), typeof(OsuModWiggle), typeof(OsuModTransform) }; - private HashSet movingObjects = new HashSet(); - private int updateCounter = 0; + private readonly HashSet movingObjects = new HashSet(); + private int updateCounter; public void Update(Playfield playfield) { @@ -36,7 +36,9 @@ namespace osu.Game.Rulesets.Osu.Mods { // ... every 500th cursor update iteration // (lower -> potential lags ; higher -> easier to miss if cursor too fast) - if (updateCounter++ < 500) return; + if (updateCounter++ < 500) + return; + updateCounter = 0; // First move objects to new destination, then remove them from movingObjects set if they're too old @@ -45,11 +47,13 @@ namespace osu.Game.Rulesets.Osu.Mods var currentTime = playfield.Clock.CurrentTime; var h = d.HitObject; d.ClearTransforms(); + switch (d) { case DrawableHitCircle circle: - d.MoveTo(drawableCursor.DrawPosition, Math.Max(0, h.StartTime - currentTime)); + circle.MoveTo(drawableCursor.DrawPosition, Math.Max(0, h.StartTime - currentTime)); return currentTime > h.StartTime; + case DrawableSlider slider: // Move slider to cursor @@ -59,11 +63,13 @@ namespace osu.Game.Rulesets.Osu.Mods // Move slider so that sliderball stays on the cursor else d.MoveTo(drawableCursor.DrawPosition - slider.Ball.DrawPosition, Math.Max(0, h.StartTime - currentTime)); - return currentTime > (h as IHasEndTime).EndTime - 50; - case DrawableSpinner spinner: + return currentTime > (h as IHasEndTime)?.EndTime - 50; + + case DrawableSpinner _: // TODO return true; } + return true; // never happens(?) }); }; @@ -79,6 +85,7 @@ namespace osu.Game.Rulesets.Osu.Mods { if (!(drawable is DrawableOsuHitObject d)) return; + var h = d.HitObject; using (d.BeginAbsoluteSequence(h.StartTime - h.TimePreempt)) movingObjects.Add(d); From 28f78f67b237a952bb403fb802640b7c4d8c47b3 Mon Sep 17 00:00:00 2001 From: MaxOhn Date: Tue, 20 Aug 2019 16:33:56 +0200 Subject: [PATCH 003/996] No longer subscribing to OnUpdate --- osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs | 70 ++++++++----------- 1 file changed, 28 insertions(+), 42 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs index 26f0d64a5d..8d0c1fdcfe 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Osu.Mods { internal class OsuModAimAssist : Mod, IApplicableToDrawableHitObjects, IUpdatableByPlayfield { - public override string Name => "AimAssist"; + public override string Name => "Aim Assist"; public override string Acronym => "AA"; public override IconUsage Icon => FontAwesome.Solid.MousePointer; public override ModType Type => ModType.Fun; @@ -24,55 +24,45 @@ namespace osu.Game.Rulesets.Osu.Mods public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModAutoplay), typeof(OsuModWiggle), typeof(OsuModTransform) }; private readonly HashSet movingObjects = new HashSet(); - private int updateCounter; public void Update(Playfield playfield) { + var drawableCursor = playfield.Cursor.ActiveCursor; + // Avoid crowded judgment displays playfield.DisplayJudgements.Value = false; - // Object destination updated when cursor updates - playfield.Cursor.ActiveCursor.OnUpdate += drawableCursor => + // First move objects to new destination, then remove them from movingObjects set if they're too old + movingObjects.RemoveWhere(d => { - // ... every 500th cursor update iteration - // (lower -> potential lags ; higher -> easier to miss if cursor too fast) - if (updateCounter++ < 500) - return; + var currentTime = playfield.Clock.CurrentTime; + var h = d.HitObject; - updateCounter = 0; - - // First move objects to new destination, then remove them from movingObjects set if they're too old - movingObjects.RemoveWhere(d => + switch (d) { - var currentTime = playfield.Clock.CurrentTime; - var h = d.HitObject; - d.ClearTransforms(); + case DrawableHitCircle circle: + circle.MoveTo(drawableCursor.DrawPosition, Math.Max(0, h.StartTime - currentTime - 10)); + return currentTime > h.StartTime; - switch (d) - { - case DrawableHitCircle circle: - circle.MoveTo(drawableCursor.DrawPosition, Math.Max(0, h.StartTime - currentTime)); - return currentTime > h.StartTime; + case DrawableSlider slider: - case DrawableSlider slider: + // Move slider to cursor + if (currentTime < h.StartTime) + d.MoveTo(drawableCursor.DrawPosition, Math.Max(0, h.StartTime - currentTime - 10)); - // Move slider to cursor - if (currentTime < h.StartTime) - d.MoveTo(drawableCursor.DrawPosition, Math.Max(0, h.StartTime - currentTime)); + // Move slider so that sliderball stays on the cursor + else + d.MoveTo(drawableCursor.DrawPosition - slider.Ball.DrawPosition); + return currentTime > (h as IHasEndTime)?.EndTime; - // Move slider so that sliderball stays on the cursor - else - d.MoveTo(drawableCursor.DrawPosition - slider.Ball.DrawPosition, Math.Max(0, h.StartTime - currentTime)); - return currentTime > (h as IHasEndTime)?.EndTime - 50; + case DrawableSpinner _: + // TODO + return true; - case DrawableSpinner _: - // TODO - return true; - } - - return true; // never happens(?) - }); - }; + default: + return true; + } + }); } public void ApplyToDrawableHitObjects(IEnumerable drawables) @@ -83,11 +73,7 @@ namespace osu.Game.Rulesets.Osu.Mods private void drawableOnApplyCustomUpdateState(DrawableHitObject drawable, ArmedState state) { - if (!(drawable is DrawableOsuHitObject d)) - return; - - var h = d.HitObject; - using (d.BeginAbsoluteSequence(h.StartTime - h.TimePreempt)) + if (drawable is DrawableOsuHitObject d) movingObjects.Add(d); } } @@ -96,7 +82,7 @@ namespace osu.Game.Rulesets.Osu.Mods * TODOs * - remove object timing glitches / artifacts * - remove FollowPoints - * - automate sliders + * - automate spinners * - combine with OsuModRelax (?) * - must be some way to make this more effictient * From 30f923edde9987bfcfbe866f9bf5258dfdb5b8bf Mon Sep 17 00:00:00 2001 From: MaxOhn Date: Tue, 20 Aug 2019 17:23:50 +0200 Subject: [PATCH 004/996] Hiding follow points --- osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs | 6 +++--- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs index 8d0c1fdcfe..54c80525bf 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs @@ -10,6 +10,7 @@ using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.UI; +using osu.Game.Rulesets.Osu.UI; namespace osu.Game.Rulesets.Osu.Mods { @@ -29,8 +30,9 @@ namespace osu.Game.Rulesets.Osu.Mods { var drawableCursor = playfield.Cursor.ActiveCursor; - // Avoid crowded judgment displays + // Avoid crowded judgment displays and hide follow points playfield.DisplayJudgements.Value = false; + (playfield as OsuPlayfield)?.ConnectionLayer.Hide(); // First move objects to new destination, then remove them from movingObjects set if they're too old movingObjects.RemoveWhere(d => @@ -81,10 +83,8 @@ namespace osu.Game.Rulesets.Osu.Mods /* * TODOs * - remove object timing glitches / artifacts - * - remove FollowPoints * - automate spinners * - combine with OsuModRelax (?) - * - must be some way to make this more effictient * */ } diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 9037faf606..64620d3629 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.UI { private readonly ApproachCircleProxyContainer approachCircles; private readonly JudgementContainer judgementLayer; - private readonly ConnectionRenderer connectionLayer; + public readonly ConnectionRenderer ConnectionLayer; public static readonly Vector2 BASE_SIZE = new Vector2(512, 384); @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.UI { InternalChildren = new Drawable[] { - connectionLayer = new FollowPointRenderer + ConnectionLayer = new FollowPointRenderer { RelativeSizeAxes = Axes.Both, Depth = 2, @@ -81,7 +81,7 @@ namespace osu.Game.Rulesets.Osu.UI public override void PostProcess() { - connectionLayer.HitObjects = HitObjectContainer.Objects.Select(d => d.HitObject).OfType(); + ConnectionLayer.HitObjects = HitObjectContainer.Objects.Select(d => d.HitObject).OfType(); } private void onNewResult(DrawableHitObject judgedObject, JudgementResult result) From d4d348390a6ce2dfac7638d61b8800e5439dceaf Mon Sep 17 00:00:00 2001 From: MaxOhn Date: Tue, 20 Aug 2019 23:18:57 +0200 Subject: [PATCH 005/996] Change set to list + minor changes --- osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs | 39 ++++++++++++------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs index 54c80525bf..1101b93ed1 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs @@ -24,38 +24,46 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModAutoplay), typeof(OsuModWiggle), typeof(OsuModTransform) }; - private readonly HashSet movingObjects = new HashSet(); + private readonly List movingObjects = new List(); public void Update(Playfield playfield) { - var drawableCursor = playfield.Cursor.ActiveCursor; + var cursorPos = playfield.Cursor.ActiveCursor.DrawPosition; - // Avoid crowded judgment displays and hide follow points + // Avoid relocating judgment displays and hide follow points playfield.DisplayJudgements.Value = false; (playfield as OsuPlayfield)?.ConnectionLayer.Hide(); - // First move objects to new destination, then remove them from movingObjects set if they're too old - movingObjects.RemoveWhere(d => + // First move objects to new destination, then remove them from movingObjects list if they're too old + movingObjects.RemoveAll(d => { - var currentTime = playfield.Clock.CurrentTime; var h = d.HitObject; + var currentTime = playfield.Clock.CurrentTime; + var endTime = (h as IHasEndTime)?.EndTime ?? h.StartTime; + d.ClearTransforms(); switch (d) { case DrawableHitCircle circle: - circle.MoveTo(drawableCursor.DrawPosition, Math.Max(0, h.StartTime - currentTime - 10)); - return currentTime > h.StartTime; + + // 10ms earlier on the note to reduce chance of missing when clicking early / cursor moves fast + circle.MoveTo(cursorPos, Math.Max(0, endTime - currentTime - 10)); + return currentTime > endTime; case DrawableSlider slider: // Move slider to cursor if (currentTime < h.StartTime) - d.MoveTo(drawableCursor.DrawPosition, Math.Max(0, h.StartTime - currentTime - 10)); - + { + slider.MoveTo(cursorPos, Math.Max(0, h.StartTime - currentTime - 10)); + return false; + } // Move slider so that sliderball stays on the cursor else - d.MoveTo(drawableCursor.DrawPosition - slider.Ball.DrawPosition); - return currentTime > (h as IHasEndTime)?.EndTime; + { + slider.MoveTo(cursorPos - slider.Ball.DrawPosition); + return currentTime > endTime; + } case DrawableSpinner _: // TODO @@ -75,14 +83,15 @@ namespace osu.Game.Rulesets.Osu.Mods private void drawableOnApplyCustomUpdateState(DrawableHitObject drawable, ArmedState state) { - if (drawable is DrawableOsuHitObject d) - movingObjects.Add(d); + if (drawable is DrawableOsuHitObject hitobject) + movingObjects.Add(hitobject); } } /* * TODOs - * - remove object timing glitches / artifacts + * - fix sliders reappearing at original position after their EndTime (see https://puu.sh/E7zT4/111cf9cdc8.gif) + * - relocate / hide slider headcircle's explosion, flash, ... * - automate spinners * - combine with OsuModRelax (?) * From 4cd5eb783a017f53682291aaa61eb32e91b72bd9 Mon Sep 17 00:00:00 2001 From: MaxOhn Date: Wed, 21 Aug 2019 21:37:56 +0200 Subject: [PATCH 006/996] Add spinner automation --- osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs | 64 +++++++++++++++---- osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs | 2 +- 4 files changed, 54 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs index 1101b93ed1..f70ad2ac7c 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs @@ -11,6 +11,7 @@ using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.Osu.UI; +using osuTK; namespace osu.Game.Rulesets.Osu.Mods { @@ -22,25 +23,30 @@ namespace osu.Game.Rulesets.Osu.Mods public override ModType Type => ModType.Fun; public override string Description => "No need to chase the circle, the circle chases you"; public override double ScoreMultiplier => 1; - public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModAutoplay), typeof(OsuModWiggle), typeof(OsuModTransform) }; + public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay) }; private readonly List movingObjects = new List(); + private DrawableSpinner activeSpinner; + private double spinnerAngle; // in radians public void Update(Playfield playfield) { var cursorPos = playfield.Cursor.ActiveCursor.DrawPosition; + var currentTime = playfield.Clock.CurrentTime; // Avoid relocating judgment displays and hide follow points playfield.DisplayJudgements.Value = false; (playfield as OsuPlayfield)?.ConnectionLayer.Hide(); - // First move objects to new destination, then remove them from movingObjects list if they're too old + // If object too old, remove from movingObjects list, otherwise move to new destination movingObjects.RemoveAll(d => { var h = d.HitObject; - var currentTime = playfield.Clock.CurrentTime; var endTime = (h as IHasEndTime)?.EndTime ?? h.StartTime; - d.ClearTransforms(); + + // Object no longer required to be moved -> remove from list + if (currentTime > endTime) + return true; switch (d) { @@ -48,7 +54,7 @@ namespace osu.Game.Rulesets.Osu.Mods // 10ms earlier on the note to reduce chance of missing when clicking early / cursor moves fast circle.MoveTo(cursorPos, Math.Max(0, endTime - currentTime - 10)); - return currentTime > endTime; + return false; case DrawableSlider slider: @@ -56,23 +62,56 @@ namespace osu.Game.Rulesets.Osu.Mods if (currentTime < h.StartTime) { slider.MoveTo(cursorPos, Math.Max(0, h.StartTime - currentTime - 10)); - return false; } // Move slider so that sliderball stays on the cursor else { + slider.HeadCircle.Hide(); // temporary solution to supress HeadCircle's explosion, flash, ... at wrong location slider.MoveTo(cursorPos - slider.Ball.DrawPosition); - return currentTime > endTime; } - case DrawableSpinner _: - // TODO - return true; + return false; + + case DrawableSpinner spinner: + + // Move spinner to cursor + if (currentTime < h.StartTime) + { + spinner.MoveTo(cursorPos, Math.Max(0, h.StartTime - currentTime - 10)); + return false; + } + else + { + spinnerAngle = 0; + activeSpinner = spinner; + return true; + } default: return true; } }); + + if (activeSpinner != null) + { + if (currentTime > (activeSpinner.HitObject as IHasEndTime)?.EndTime) + { + activeSpinner = null; + spinnerAngle = 0; + } + else + { + const float additional_degrees = 4; + const int dist_from_cursor = 30; + spinnerAngle += additional_degrees * Math.PI / 180; + + // Visual progress + activeSpinner.MoveTo(new Vector2((float)(dist_from_cursor * Math.Cos(spinnerAngle) + cursorPos.X), (float)(dist_from_cursor * Math.Sin(spinnerAngle) + cursorPos.Y))); + + // Logical progress + activeSpinner.Disc.RotationAbsolute += additional_degrees; + } + } } public void ApplyToDrawableHitObjects(IEnumerable drawables) @@ -91,9 +130,8 @@ namespace osu.Game.Rulesets.Osu.Mods /* * TODOs * - fix sliders reappearing at original position after their EndTime (see https://puu.sh/E7zT4/111cf9cdc8.gif) - * - relocate / hide slider headcircle's explosion, flash, ... - * - automate spinners - * - combine with OsuModRelax (?) + * - find nicer way to handle slider headcircle explosion, flash, ... + * - add Aim Assist as incompatible mod for Autoplay (?) * */ } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs index ca72f18e9c..0757547d1c 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs @@ -22,7 +22,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(ModSuddenDeath), typeof(ModNoFail), typeof(ModAutoplay) }; + public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModNoFail), typeof(ModAutoplay), typeof(OsuModAimAssist) }; public bool AllowFail => false; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs index 9b079895fa..7799cdac32 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs @@ -20,7 +20,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) }; + public override Type[] IncompatibleMods => new[] { typeof(OsuModWiggle), typeof(OsuModAimAssist) }; private float theta; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs index 17fcd03dd5..a14fd88ff5 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs @@ -21,7 +21,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) }; + public override Type[] IncompatibleMods => new[] { typeof(OsuModTransform), typeof(OsuModAimAssist) }; private const int wiggle_duration = 90; // (ms) Higher = fewer wiggles private const int wiggle_strength = 10; // Higher = stronger wiggles From 508b26ac6973a07a7edb6870ec13e065b055a21c Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 7 Sep 2019 07:31:07 +0300 Subject: [PATCH 007/996] Implement basic layout --- .../UserInterface/TestScenePageSelector.cs | 29 +++++ .../Graphics/UserInterface/PageSelector.cs | 100 ++++++++++++++++++ 2 files changed, 129 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs create mode 100644 osu.Game/Graphics/UserInterface/PageSelector.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs new file mode 100644 index 0000000000..9911a23bf1 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs @@ -0,0 +1,29 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using osu.Game.Graphics.UserInterface; +using osu.Framework.Graphics; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestScenePageSelector : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(PageSelector) + }; + + public TestScenePageSelector() + { + PageSelector pageSelector; + + Child = pageSelector = new PageSelector(10) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }; + } + } +} diff --git a/osu.Game/Graphics/UserInterface/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector.cs new file mode 100644 index 0000000000..53f112ed68 --- /dev/null +++ b/osu.Game/Graphics/UserInterface/PageSelector.cs @@ -0,0 +1,100 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics; +using osu.Game.Graphics.Containers; +using osu.Framework.Bindables; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; + +namespace osu.Game.Graphics.UserInterface +{ + public class PageSelector : CompositeDrawable + { + private BindableInt currentPage = new BindableInt(1); + + private readonly int maxPages; + private readonly FillFlowContainer pillsFlow; + + public PageSelector(int maxPages) + { + this.maxPages = maxPages; + + AutoSizeAxes = Axes.Both; + InternalChild = pillsFlow = new FillFlowContainer + { + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + currentPage.BindValueChanged(page => redraw(page.NewValue), true); + } + + private void redraw(int newPage) + { + pillsFlow.Clear(); + + for (int i = 0; i < maxPages; i++) + { + addPagePill(i); + } + } + + private void addPagePill(int page) + { + var pill = new Pill(page); + + if (page != currentPage.Value) + pill.Action = () => currentPage.Value = page; + + pillsFlow.Add(pill); + } + + private class Pill : OsuClickableContainer + { + private const int height = 20; + + private readonly Box background; + + public Pill(int page) + { + AutoSizeAxes = Axes.X; + Height = height; + Child = new CircularContainer + { + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Masking = true, + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + }, + new SpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = page.ToString(), + Margin = new MarginPadding { Horizontal = 8 } + } + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + background.Colour = colours.Seafoam; + } + } + } +} From f77cd6582d94811874d74315265d4e91b5f81321 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 7 Sep 2019 08:20:09 +0300 Subject: [PATCH 008/996] Implement CurrentPage class --- .../Graphics/UserInterface/PageSelector.cs | 150 ++++++++++++++---- 1 file changed, 119 insertions(+), 31 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector.cs index 53f112ed68..fef35ab229 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector.cs @@ -8,6 +8,9 @@ using osu.Framework.Bindables; using osu.Framework.Allocation; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Events; +using osu.Framework.Extensions.Color4Extensions; +using System; namespace osu.Game.Graphics.UserInterface { @@ -42,59 +45,144 @@ namespace osu.Game.Graphics.UserInterface { pillsFlow.Clear(); - for (int i = 0; i < maxPages; i++) + for (int i = 1; i <= maxPages; i++) { - addPagePill(i); + if (i == currentPage.Value) + addCurrentPagePill(); + else + addPagePill(i); } } private void addPagePill(int page) { - var pill = new Pill(page); - - if (page != currentPage.Value) - pill.Action = () => currentPage.Value = page; - - pillsFlow.Add(pill); + pillsFlow.Add(new Page(page.ToString(), () => currentPage.Value = page)); } - private class Pill : OsuClickableContainer + private void addCurrentPagePill() + { + pillsFlow.Add(new CurrentPage(currentPage.Value.ToString())); + } + + private abstract class DrawablePage : CompositeDrawable { private const int height = 20; + private const int margin = 8; - private readonly Box background; + protected readonly string Text; - public Pill(int page) + protected DrawablePage(string text) { + Text = text; + AutoSizeAxes = Axes.X; Height = height; - Child = new CircularContainer - { - AutoSizeAxes = Axes.X, - RelativeSizeAxes = Axes.Y, - Masking = true, - Children = new Drawable[] - { - background = new Box - { - RelativeSizeAxes = Axes.Both, - }, - new SpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = page.ToString(), - Margin = new MarginPadding { Horizontal = 8 } - } - } - }; + + var background = CreateBackground(); + + if (background != null) + AddInternal(background); + + var content = CreateContent(); + content.Margin = new MarginPadding { Horizontal = margin }; + + AddInternal(content); + } + + protected abstract Drawable CreateContent(); + + protected virtual Drawable CreateBackground() => null; + } + + private abstract class ActivatedDrawablePage : DrawablePage + { + private readonly Action action; + + public ActivatedDrawablePage(string text, Action action) + : base(text) + { + this.action = action; + } + + protected override bool OnClick(ClickEvent e) + { + action?.Invoke(); + return base.OnClick(e); + } + } + + private class Page : ActivatedDrawablePage + { + private SpriteText text; + + private OsuColour colours; + + public Page(string text, Action action) + : base(text, action) + { } [BackgroundDependencyLoader] private void load(OsuColour colours) { + this.colours = colours; + text.Colour = colours.Seafoam; + } + + protected override bool OnHover(HoverEvent e) + { + text.Colour = colours.Seafoam.Lighten(30f); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + text.Colour = colours.Seafoam; + base.OnHoverLost(e); + } + + protected override Drawable CreateContent() => text = new SpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = Text + }; + } + + private class CurrentPage : DrawablePage + { + private SpriteText text; + + private Box background; + + public CurrentPage(string text) + : base(text) + { + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + text.Colour = colours.GreySeafoam; background.Colour = colours.Seafoam; } + + protected override Drawable CreateContent() => text = new SpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = Text + }; + + protected override Drawable CreateBackground() => new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Child = background = new Box + { + RelativeSizeAxes = Axes.Both, + } + }; } } } From cea26baaefb58171ca76b1b3b39a70772d8eb7a5 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 7 Sep 2019 08:46:16 +0300 Subject: [PATCH 009/996] Implement placeholder and correct redraw algorithm --- .../UserInterface/TestScenePageSelector.cs | 4 +- .../Graphics/UserInterface/PageSelector.cs | 63 +++++++++++++++---- 2 files changed, 52 insertions(+), 15 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs index 9911a23bf1..e5efa65c91 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs @@ -17,9 +17,7 @@ namespace osu.Game.Tests.Visual.UserInterface public TestScenePageSelector() { - PageSelector pageSelector; - - Child = pageSelector = new PageSelector(10) + Child = new PageSelector(200) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Graphics/UserInterface/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector.cs index fef35ab229..244e87f023 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector.cs @@ -3,7 +3,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics; -using osu.Game.Graphics.Containers; using osu.Framework.Bindables; using osu.Framework.Allocation; using osu.Framework.Graphics.Shapes; @@ -16,7 +15,7 @@ namespace osu.Game.Graphics.UserInterface { public class PageSelector : CompositeDrawable { - private BindableInt currentPage = new BindableInt(1); + public readonly BindableInt CurrentPage = new BindableInt(); private readonly int maxPages; private readonly FillFlowContainer pillsFlow; @@ -38,30 +37,47 @@ namespace osu.Game.Graphics.UserInterface { base.LoadComplete(); - currentPage.BindValueChanged(page => redraw(page.NewValue), true); + CurrentPage.BindValueChanged(_ => redraw(), true); } - private void redraw(int newPage) + private void redraw() { pillsFlow.Clear(); - for (int i = 1; i <= maxPages; i++) + if (CurrentPage.Value > 3) + addDrawablePage(1); + + if (CurrentPage.Value > 4) + addPlaceholder(); + + for (int i = Math.Max(CurrentPage.Value - 2, 1); i <= Math.Min(CurrentPage.Value + 2, maxPages); i++) { - if (i == currentPage.Value) + if (i == CurrentPage.Value) addCurrentPagePill(); else - addPagePill(i); + addDrawablePage(i); } + + if (CurrentPage.Value + 2 < maxPages - 1) + addPlaceholder(); + + if (CurrentPage.Value + 2 < maxPages) + addDrawablePage(maxPages); } - private void addPagePill(int page) + private void addDrawablePage(int page) { - pillsFlow.Add(new Page(page.ToString(), () => currentPage.Value = page)); + pillsFlow.Add(new Page(page.ToString(), () => CurrentPage.Value = page)); + } + + private void addPlaceholder() + { + pillsFlow.Add(new Placeholder()); } private void addCurrentPagePill() { - pillsFlow.Add(new CurrentPage(currentPage.Value.ToString())); + pillsFlow.Add(new SelectedPage(CurrentPage.Value.ToString())); } private abstract class DrawablePage : CompositeDrawable @@ -149,13 +165,13 @@ namespace osu.Game.Graphics.UserInterface }; } - private class CurrentPage : DrawablePage + private class SelectedPage : DrawablePage { private SpriteText text; private Box background; - public CurrentPage(string text) + public SelectedPage(string text) : base(text) { } @@ -184,5 +200,28 @@ namespace osu.Game.Graphics.UserInterface } }; } + + private class Placeholder : DrawablePage + { + private SpriteText text; + + public Placeholder() + : base("...") + { + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + text.Colour = colours.Seafoam; + } + + protected override Drawable CreateContent() => text = new SpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = Text + }; + } } } From 9bd4220e9f6817e3200926c7abce593b6f8e52aa Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 7 Sep 2019 09:20:11 +0300 Subject: [PATCH 010/996] Add prev/next buttons --- .../Graphics/UserInterface/PageSelector.cs | 206 ++++++++++++++++-- 1 file changed, 184 insertions(+), 22 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector.cs index 244e87f023..d93dcdace9 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector.cs @@ -10,12 +10,13 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Framework.Extensions.Color4Extensions; using System; +using osuTK; namespace osu.Game.Graphics.UserInterface { public class PageSelector : CompositeDrawable { - public readonly BindableInt CurrentPage = new BindableInt(); + public readonly BindableInt CurrentPage = new BindableInt(1); private readonly int maxPages; private readonly FillFlowContainer pillsFlow; @@ -44,6 +45,11 @@ namespace osu.Game.Graphics.UserInterface { pillsFlow.Clear(); + if (CurrentPage.Value == 1) + addPreviousPageButton(); + else + addPreviousPageButton(() => CurrentPage.Value -= 1); + if (CurrentPage.Value > 3) addDrawablePage(1); @@ -63,6 +69,11 @@ namespace osu.Game.Graphics.UserInterface if (CurrentPage.Value + 2 < maxPages) addDrawablePage(maxPages); + + if (CurrentPage.Value == maxPages) + addNextPageButton(); + else + addNextPageButton(() => CurrentPage.Value += 1); } private void addDrawablePage(int page) @@ -80,12 +91,23 @@ namespace osu.Game.Graphics.UserInterface pillsFlow.Add(new SelectedPage(CurrentPage.Value.ToString())); } + private void addPreviousPageButton(Action action = null) + { + pillsFlow.Add(new PreviousPageButton(action)); + } + + private void addNextPageButton(Action action = null) + { + pillsFlow.Add(new NextPageButton(action)); + } + private abstract class DrawablePage : CompositeDrawable { private const int height = 20; private const int margin = 8; protected readonly string Text; + protected readonly Drawable Content; protected DrawablePage(string text) { @@ -99,10 +121,10 @@ namespace osu.Game.Graphics.UserInterface if (background != null) AddInternal(background); - var content = CreateContent(); - content.Margin = new MarginPadding { Horizontal = margin }; + Content = CreateContent(); + Content.Margin = new MarginPadding { Horizontal = margin }; - AddInternal(content); + AddInternal(Content); } protected abstract Drawable CreateContent(); @@ -112,25 +134,23 @@ namespace osu.Game.Graphics.UserInterface private abstract class ActivatedDrawablePage : DrawablePage { - private readonly Action action; + protected readonly Action Action; - public ActivatedDrawablePage(string text, Action action) + public ActivatedDrawablePage(string text, Action action = null) : base(text) { - this.action = action; + Action = action; } protected override bool OnClick(ClickEvent e) { - action?.Invoke(); + Action?.Invoke(); return base.OnClick(e); } } private class Page : ActivatedDrawablePage { - private SpriteText text; - private OsuColour colours; public Page(string text, Action action) @@ -142,22 +162,22 @@ namespace osu.Game.Graphics.UserInterface private void load(OsuColour colours) { this.colours = colours; - text.Colour = colours.Seafoam; + Content.Colour = colours.Seafoam; } protected override bool OnHover(HoverEvent e) { - text.Colour = colours.Seafoam.Lighten(30f); + Content.Colour = colours.Seafoam.Lighten(30f); return base.OnHover(e); } protected override void OnHoverLost(HoverLostEvent e) { - text.Colour = colours.Seafoam; + Content.Colour = colours.Seafoam; base.OnHoverLost(e); } - protected override Drawable CreateContent() => text = new SpriteText + protected override Drawable CreateContent() => new SpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -167,8 +187,6 @@ namespace osu.Game.Graphics.UserInterface private class SelectedPage : DrawablePage { - private SpriteText text; - private Box background; public SelectedPage(string text) @@ -179,11 +197,11 @@ namespace osu.Game.Graphics.UserInterface [BackgroundDependencyLoader] private void load(OsuColour colours) { - text.Colour = colours.GreySeafoam; + Content.Colour = colours.GreySeafoam; background.Colour = colours.Seafoam; } - protected override Drawable CreateContent() => text = new SpriteText + protected override Drawable CreateContent() => new SpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -203,8 +221,6 @@ namespace osu.Game.Graphics.UserInterface private class Placeholder : DrawablePage { - private SpriteText text; - public Placeholder() : base("...") { @@ -213,15 +229,161 @@ namespace osu.Game.Graphics.UserInterface [BackgroundDependencyLoader] private void load(OsuColour colours) { - text.Colour = colours.Seafoam; + Content.Colour = colours.Seafoam; } - protected override Drawable CreateContent() => text = new SpriteText + protected override Drawable CreateContent() => new SpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, Text = Text }; } + + private class PreviousPageButton : ActivatedDrawablePage + { + private OsuColour colours; + private Box background; + + public PreviousPageButton(Action action) + : base("prev", action) + { + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + this.colours = colours; + Content.Colour = colours.Seafoam; + background.Colour = colours.GreySeafoam; + + if (Action == null) + { + Content.FadeColour(colours.GrayA); + background.FadeColour(colours.GrayA); + } + } + + protected override bool OnHover(HoverEvent e) + { + Content.Colour = colours.Seafoam.Lighten(30f); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + Content.Colour = colours.Seafoam; + base.OnHoverLost(e); + } + + protected override Drawable CreateContent() => new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(3), + Children = new Drawable[] + { + new SpriteIcon + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Icon = FontAwesome.Solid.CaretLeft, + Size = new Vector2(10), + }, + new SpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Text = Text.ToUpper(), + } + } + }; + + protected override Drawable CreateBackground() => new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Child = background = new Box + { + RelativeSizeAxes = Axes.Both, + } + }; + } + + private class NextPageButton : ActivatedDrawablePage + { + private OsuColour colours; + private Box background; + + public NextPageButton(Action action) + : base("next", action) + { + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + this.colours = colours; + Content.Colour = colours.Seafoam; + background.Colour = colours.GreySeafoam; + + if (Action == null) + { + Content.FadeColour(colours.GrayA); + background.FadeColour(colours.GrayA); + } + } + + protected override bool OnHover(HoverEvent e) + { + Content.Colour = colours.Seafoam.Lighten(30f); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + Content.Colour = colours.Seafoam; + base.OnHoverLost(e); + } + + protected override Drawable CreateContent() => new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(3), + Children = new Drawable[] + { + new SpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Text = Text.ToUpper(), + }, + new SpriteIcon + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Icon = FontAwesome.Solid.CaretRight, + Size = new Vector2(10), + }, + } + }; + + protected override Drawable CreateBackground() => new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Child = background = new Box + { + RelativeSizeAxes = Axes.Both, + } + }; + } } } From ba18f77b628496569cb5d5718081ea271b51e4f3 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 7 Sep 2019 22:31:08 +0300 Subject: [PATCH 011/996] Use OsuHoverContainer for prev/next buttons --- .../Graphics/UserInterface/PageSelector.cs | 231 ++++++------------ 1 file changed, 78 insertions(+), 153 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector.cs index d93dcdace9..3ca133d64b 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector.cs @@ -11,6 +11,8 @@ using osu.Framework.Input.Events; using osu.Framework.Extensions.Color4Extensions; using System; using osuTK; +using osu.Game.Graphics.Containers; +using System.Collections.Generic; namespace osu.Game.Graphics.UserInterface { @@ -21,16 +23,34 @@ namespace osu.Game.Graphics.UserInterface private readonly int maxPages; private readonly FillFlowContainer pillsFlow; + private readonly Button previousPageButton; + private readonly Button nextPageButton; + public PageSelector(int maxPages) { this.maxPages = maxPages; AutoSizeAxes = Axes.Both; - InternalChild = pillsFlow = new FillFlowContainer + InternalChild = new FillFlowContainer { - AutoSizeAxes = Axes.X, - RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + previousPageButton = new Button(false, "prev") + { + Action = () => CurrentPage.Value -= 1, + }, + pillsFlow = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + }, + nextPageButton = new Button(true, "next") + { + Action = () => CurrentPage.Value += 1 + } + } }; } @@ -43,12 +63,10 @@ namespace osu.Game.Graphics.UserInterface private void redraw() { - pillsFlow.Clear(); + previousPageButton.Enabled.Value = CurrentPage.Value != 1; + nextPageButton.Enabled.Value = CurrentPage.Value != maxPages; - if (CurrentPage.Value == 1) - addPreviousPageButton(); - else - addPreviousPageButton(() => CurrentPage.Value -= 1); + pillsFlow.Clear(); if (CurrentPage.Value > 3) addDrawablePage(1); @@ -69,11 +87,6 @@ namespace osu.Game.Graphics.UserInterface if (CurrentPage.Value + 2 < maxPages) addDrawablePage(maxPages); - - if (CurrentPage.Value == maxPages) - addNextPageButton(); - else - addNextPageButton(() => CurrentPage.Value += 1); } private void addDrawablePage(int page) @@ -91,16 +104,6 @@ namespace osu.Game.Graphics.UserInterface pillsFlow.Add(new SelectedPage(CurrentPage.Value.ToString())); } - private void addPreviousPageButton(Action action = null) - { - pillsFlow.Add(new PreviousPageButton(action)); - } - - private void addNextPageButton(Action action = null) - { - pillsFlow.Add(new NextPageButton(action)); - } - private abstract class DrawablePage : CompositeDrawable { private const int height = 20; @@ -240,150 +243,72 @@ namespace osu.Game.Graphics.UserInterface }; } - private class PreviousPageButton : ActivatedDrawablePage + private class Button : OsuHoverContainer { - private OsuColour colours; - private Box background; + private const int height = 20; + private const int margin = 8; - public PreviousPageButton(Action action) - : base("prev", action) + private readonly Anchor alignment; + private readonly Box background; + + protected override IEnumerable EffectTargets => new[] { background }; + + public Button(bool rightAligned, string text) { - } + alignment = rightAligned ? Anchor.x0 : Anchor.x2; - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - this.colours = colours; - Content.Colour = colours.Seafoam; - background.Colour = colours.GreySeafoam; + AutoSizeAxes = Axes.X; + Height = height; - if (Action == null) + Child = new CircularContainer { - Content.FadeColour(colours.GrayA); - background.FadeColour(colours.GrayA); - } - } - - protected override bool OnHover(HoverEvent e) - { - Content.Colour = colours.Seafoam.Lighten(30f); - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - Content.Colour = colours.Seafoam; - base.OnHoverLost(e); - } - - protected override Drawable CreateContent() => new FillFlowContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.X, - RelativeSizeAxes = Axes.Y, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(3), - Children = new Drawable[] - { - new SpriteIcon + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Masking = true, + Children = new Drawable[] { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Icon = FontAwesome.Solid.CaretLeft, - Size = new Vector2(10), - }, - new SpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Text = Text.ToUpper(), + background = new Box + { + RelativeSizeAxes = Axes.Both, + }, + new Container + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Child = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Horizontal = margin }, + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + new SpriteText + { + Anchor = Anchor.y1 | alignment, + Origin = Anchor.y1 | alignment, + Text = text.ToUpper(), + }, + new SpriteIcon + { + Anchor = Anchor.y1 | alignment, + Origin = Anchor.y1 | alignment, + Icon = alignment == Anchor.x2 ? FontAwesome.Solid.ChevronLeft : FontAwesome.Solid.ChevronRight, + Size = new Vector2(10), + }, + } + } + } } - } - }; - - protected override Drawable CreateBackground() => new CircularContainer - { - RelativeSizeAxes = Axes.Both, - Masking = true, - Child = background = new Box - { - RelativeSizeAxes = Axes.Both, - } - }; - } - - private class NextPageButton : ActivatedDrawablePage - { - private OsuColour colours; - private Box background; - - public NextPageButton(Action action) - : base("next", action) - { + }; } [BackgroundDependencyLoader] private void load(OsuColour colours) { - this.colours = colours; - Content.Colour = colours.Seafoam; - background.Colour = colours.GreySeafoam; - - if (Action == null) - { - Content.FadeColour(colours.GrayA); - background.FadeColour(colours.GrayA); - } + IdleColour = colours.GreySeafoamDark; + HoverColour = colours.GrayA; } - - protected override bool OnHover(HoverEvent e) - { - Content.Colour = colours.Seafoam.Lighten(30f); - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - Content.Colour = colours.Seafoam; - base.OnHoverLost(e); - } - - protected override Drawable CreateContent() => new FillFlowContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.X, - RelativeSizeAxes = Axes.Y, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(3), - Children = new Drawable[] - { - new SpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Text = Text.ToUpper(), - }, - new SpriteIcon - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Icon = FontAwesome.Solid.CaretRight, - Size = new Vector2(10), - }, - } - }; - - protected override Drawable CreateBackground() => new CircularContainer - { - RelativeSizeAxes = Axes.Both, - Masking = true, - Child = background = new Box - { - RelativeSizeAxes = Axes.Both, - } - }; } } } From ec8298ac53108d65a7901228bf524ebcae794356 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 7 Sep 2019 22:33:26 +0300 Subject: [PATCH 012/996] Simplify redraw function --- .../Graphics/UserInterface/PageSelector.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector.cs index 3ca133d64b..0b0c2eec83 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector.cs @@ -58,34 +58,34 @@ namespace osu.Game.Graphics.UserInterface { base.LoadComplete(); - CurrentPage.BindValueChanged(_ => redraw(), true); + CurrentPage.BindValueChanged(page => redraw(page.NewValue), true); } - private void redraw() + private void redraw(int newPage) { - previousPageButton.Enabled.Value = CurrentPage.Value != 1; - nextPageButton.Enabled.Value = CurrentPage.Value != maxPages; + previousPageButton.Enabled.Value = newPage != 1; + nextPageButton.Enabled.Value = newPage != maxPages; pillsFlow.Clear(); - if (CurrentPage.Value > 3) + if (newPage > 3) addDrawablePage(1); - if (CurrentPage.Value > 4) + if (newPage > 4) addPlaceholder(); - for (int i = Math.Max(CurrentPage.Value - 2, 1); i <= Math.Min(CurrentPage.Value + 2, maxPages); i++) + for (int i = Math.Max(newPage - 2, 1); i <= Math.Min(newPage + 2, maxPages); i++) { - if (i == CurrentPage.Value) + if (i == newPage) addCurrentPagePill(); else addDrawablePage(i); } - if (CurrentPage.Value + 2 < maxPages - 1) + if (newPage + 2 < maxPages - 1) addPlaceholder(); - if (CurrentPage.Value + 2 < maxPages) + if (newPage + 2 < maxPages) addDrawablePage(maxPages); } From df29465ba44c974290bdaa775d78b736a2340f8d Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 7 Sep 2019 23:42:30 +0300 Subject: [PATCH 013/996] Implement a single PageItem component --- .../Graphics/UserInterface/PageSelector.cs | 237 +++++++----------- 1 file changed, 86 insertions(+), 151 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector.cs index 0b0c2eec83..9dea9232ac 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector.cs @@ -7,12 +7,12 @@ using osu.Framework.Bindables; using osu.Framework.Allocation; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; -using osu.Framework.Input.Events; using osu.Framework.Extensions.Color4Extensions; using System; using osuTK; using osu.Game.Graphics.Containers; using System.Collections.Generic; +using osu.Framework.Extensions.IEnumerableExtensions; namespace osu.Game.Graphics.UserInterface { @@ -21,7 +21,7 @@ namespace osu.Game.Graphics.UserInterface public readonly BindableInt CurrentPage = new BindableInt(1); private readonly int maxPages; - private readonly FillFlowContainer pillsFlow; + private readonly FillFlowContainer itemsFlow; private readonly Button previousPageButton; private readonly Button nextPageButton; @@ -41,7 +41,7 @@ namespace osu.Game.Graphics.UserInterface { Action = () => CurrentPage.Value -= 1, }, - pillsFlow = new FillFlowContainer + itemsFlow = new FillFlowContainer { AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, @@ -66,7 +66,7 @@ namespace osu.Game.Graphics.UserInterface previousPageButton.Enabled.Value = newPage != 1; nextPageButton.Enabled.Value = newPage != maxPages; - pillsFlow.Clear(); + itemsFlow.Clear(); if (newPage > 3) addDrawablePage(1); @@ -77,7 +77,7 @@ namespace osu.Game.Graphics.UserInterface for (int i = Math.Max(newPage - 2, 1); i <= Math.Min(newPage + 2, maxPages); i++) { if (i == newPage) - addCurrentPagePill(); + addDrawableCurrentPage(); else addDrawablePage(i); } @@ -89,137 +89,101 @@ namespace osu.Game.Graphics.UserInterface addDrawablePage(maxPages); } - private void addDrawablePage(int page) + private void addDrawablePage(int page) => itemsFlow.Add(new DrawablePage(page.ToString()) { - pillsFlow.Add(new Page(page.ToString(), () => CurrentPage.Value = page)); - } + Action = () => CurrentPage.Value = page, + }); - private void addPlaceholder() - { - pillsFlow.Add(new Placeholder()); - } + private void addPlaceholder() => itemsFlow.Add(new Placeholder()); - private void addCurrentPagePill() - { - pillsFlow.Add(new SelectedPage(CurrentPage.Value.ToString())); - } + private void addDrawableCurrentPage() => itemsFlow.Add(new SelectedPage(CurrentPage.Value.ToString())); - private abstract class DrawablePage : CompositeDrawable + private abstract class PageItem : OsuHoverContainer { - private const int height = 20; private const int margin = 8; + private const int height = 20; - protected readonly string Text; - protected readonly Drawable Content; - - protected DrawablePage(string text) + protected PageItem(string text) { - Text = text; - AutoSizeAxes = Axes.X; Height = height; + var contentContainer = new CircularContainer + { + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Masking = true, + }; + var background = CreateBackground(); - if (background != null) - AddInternal(background); + contentContainer.Add(background); - Content = CreateContent(); - Content.Margin = new MarginPadding { Horizontal = margin }; + var drawableText = CreateText(text); + if (drawableText != null) + { + drawableText.Margin = new MarginPadding { Horizontal = margin }; + contentContainer.Add(drawableText); + } - AddInternal(Content); + Add(contentContainer); } - protected abstract Drawable CreateContent(); + protected abstract Drawable CreateText(string text); - protected virtual Drawable CreateBackground() => null; + protected abstract Drawable CreateBackground(); } - private abstract class ActivatedDrawablePage : DrawablePage + private class DrawablePage : PageItem { - protected readonly Action Action; + protected SpriteText SpriteText; - public ActivatedDrawablePage(string text, Action action = null) + protected override IEnumerable EffectTargets => new[] { SpriteText }; + + public DrawablePage(string text) : base(text) { - Action = action; } - protected override bool OnClick(ClickEvent e) + protected override Drawable CreateBackground() => null; + + protected override Drawable CreateText(string text) => SpriteText = new SpriteText { - Action?.Invoke(); - return base.OnClick(e); - } - } - - private class Page : ActivatedDrawablePage - { - private OsuColour colours; - - public Page(string text, Action action) - : base(text, action) - { - } + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = text, + }; [BackgroundDependencyLoader] private void load(OsuColour colours) { - this.colours = colours; - Content.Colour = colours.Seafoam; + IdleColour = colours.Seafoam; + HoverColour = colours.Seafoam.Lighten(30f); } - - protected override bool OnHover(HoverEvent e) - { - Content.Colour = colours.Seafoam.Lighten(30f); - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - Content.Colour = colours.Seafoam; - base.OnHoverLost(e); - } - - protected override Drawable CreateContent() => new SpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = Text - }; } private class SelectedPage : DrawablePage { private Box background; + protected override IEnumerable EffectTargets => null; + public SelectedPage(string text) : base(text) { } + protected override Drawable CreateBackground() => background = new Box + { + RelativeSizeAxes = Axes.Both, + }; + [BackgroundDependencyLoader] private void load(OsuColour colours) { - Content.Colour = colours.GreySeafoam; background.Colour = colours.Seafoam; + SpriteText.Colour = colours.GreySeafoamDark; } - - protected override Drawable CreateContent() => new SpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = Text - }; - - protected override Drawable CreateBackground() => new CircularContainer - { - RelativeSizeAxes = Axes.Both, - Masking = true, - Child = background = new Box - { - RelativeSizeAxes = Axes.Both, - } - }; } private class Placeholder : DrawablePage @@ -228,79 +192,28 @@ namespace osu.Game.Graphics.UserInterface : base("...") { } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - Content.Colour = colours.Seafoam; - } - - protected override Drawable CreateContent() => new SpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = Text - }; } - private class Button : OsuHoverContainer + private class Button : PageItem { - private const int height = 20; - private const int margin = 8; - - private readonly Anchor alignment; - private readonly Box background; + private Box background; + private FillFlowContainer textContainer; + private SpriteIcon icon; protected override IEnumerable EffectTargets => new[] { background }; public Button(bool rightAligned, string text) + : base(text) { - alignment = rightAligned ? Anchor.x0 : Anchor.x2; + var alignment = rightAligned ? Anchor.x0 : Anchor.x2; - AutoSizeAxes = Axes.X; - Height = height; - - Child = new CircularContainer + textContainer.ForEach(drawable => { - AutoSizeAxes = Axes.X, - RelativeSizeAxes = Axes.Y, - Masking = true, - Children = new Drawable[] - { - background = new Box - { - RelativeSizeAxes = Axes.Both, - }, - new Container - { - AutoSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Child = new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Margin = new MarginPadding { Horizontal = margin }, - Direction = FillDirection.Horizontal, - Children = new Drawable[] - { - new SpriteText - { - Anchor = Anchor.y1 | alignment, - Origin = Anchor.y1 | alignment, - Text = text.ToUpper(), - }, - new SpriteIcon - { - Anchor = Anchor.y1 | alignment, - Origin = Anchor.y1 | alignment, - Icon = alignment == Anchor.x2 ? FontAwesome.Solid.ChevronLeft : FontAwesome.Solid.ChevronRight, - Size = new Vector2(10), - }, - } - } - } - } - }; + drawable.Anchor = Anchor.y1 | alignment; + drawable.Origin = Anchor.y1 | alignment; + }); + + icon.Icon = alignment == Anchor.x2 ? FontAwesome.Solid.ChevronLeft : FontAwesome.Solid.ChevronRight; } [BackgroundDependencyLoader] @@ -309,6 +222,28 @@ namespace osu.Game.Graphics.UserInterface IdleColour = colours.GreySeafoamDark; HoverColour = colours.GrayA; } + + protected override Drawable CreateBackground() => background = new Box + { + RelativeSizeAxes = Axes.Both, + }; + + protected override Drawable CreateText(string text) => textContainer = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + new SpriteText + { + Text = text.ToUpper(), + }, + icon = new SpriteIcon + { + Size = new Vector2(10), + }, + } + }; } } } From b0884d16fbcc4a540af86f40aa359a577d39d988 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 8 Sep 2019 00:13:23 +0300 Subject: [PATCH 014/996] Visual adjustments --- .../Graphics/UserInterface/PageSelector.cs | 67 +++++++++++++------ 1 file changed, 45 insertions(+), 22 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector.cs index 9dea9232ac..9a5ffad0d4 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector.cs @@ -13,6 +13,7 @@ using osuTK; using osu.Game.Graphics.Containers; using System.Collections.Generic; using osu.Framework.Extensions.IEnumerableExtensions; +using osuTK.Graphics; namespace osu.Game.Graphics.UserInterface { @@ -100,38 +101,47 @@ namespace osu.Game.Graphics.UserInterface private abstract class PageItem : OsuHoverContainer { - private const int margin = 8; + private const int margin = 10; private const int height = 20; + protected override Container Content => contentContainer; + + private readonly CircularContainer contentContainer; + protected PageItem(string text) { AutoSizeAxes = Axes.X; Height = height; - var contentContainer = new CircularContainer + base.Content.Add(contentContainer = new CircularContainer { AutoSizeAxes = Axes.X, RelativeSizeAxes = Axes.Y, Masking = true, - }; + }); var background = CreateBackground(); if (background != null) - contentContainer.Add(background); + Add(background); var drawableText = CreateText(text); if (drawableText != null) { drawableText.Margin = new MarginPadding { Horizontal = margin }; - contentContainer.Add(drawableText); + Add(drawableText); } + } - Add(contentContainer); + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + IdleColour = colours.Seafoam; + HoverColour = colours.Seafoam.Lighten(30f); } protected abstract Drawable CreateText(string text); - protected abstract Drawable CreateBackground(); + protected virtual Drawable CreateBackground() => null; } private class DrawablePage : PageItem @@ -145,28 +155,20 @@ namespace osu.Game.Graphics.UserInterface { } - protected override Drawable CreateBackground() => null; - protected override Drawable CreateText(string text) => SpriteText = new SpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, Text = text, + Font = OsuFont.GetFont(size: 12), }; - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - IdleColour = colours.Seafoam; - HoverColour = colours.Seafoam.Lighten(30f); - } } private class SelectedPage : DrawablePage { private Box background; - protected override IEnumerable EffectTargets => null; + protected override IEnumerable EffectTargets => new[] { background }; public SelectedPage(string text) : base(text) @@ -181,7 +183,6 @@ namespace osu.Game.Graphics.UserInterface [BackgroundDependencyLoader] private void load(OsuColour colours) { - background.Colour = colours.Seafoam; SpriteText.Colour = colours.GreySeafoamDark; } } @@ -196,11 +197,14 @@ namespace osu.Game.Graphics.UserInterface private class Button : PageItem { + private const int duration = 100; + private Box background; private FillFlowContainer textContainer; private SpriteIcon icon; + private readonly Box fadeBox; - protected override IEnumerable EffectTargets => new[] { background }; + protected override IEnumerable EffectTargets => new[] { textContainer }; public Button(bool rightAligned, string text) : base(text) @@ -214,13 +218,29 @@ namespace osu.Game.Graphics.UserInterface }); icon.Icon = alignment == Anchor.x2 ? FontAwesome.Solid.ChevronLeft : FontAwesome.Solid.ChevronRight; + + Add(fadeBox = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black.Opacity(100) + }); } [BackgroundDependencyLoader] private void load(OsuColour colours) { - IdleColour = colours.GreySeafoamDark; - HoverColour = colours.GrayA; + background.Colour = colours.GreySeafoamDark; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + Enabled.BindValueChanged(onEnabledChanged, true); + } + + private void onEnabledChanged(ValueChangedEvent enabled) + { + fadeBox.FadeTo(enabled.NewValue ? 0 : 1, duration); } protected override Drawable CreateBackground() => background = new Box @@ -231,16 +251,19 @@ namespace osu.Game.Graphics.UserInterface protected override Drawable CreateText(string text) => textContainer = new FillFlowContainer { AutoSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, Direction = FillDirection.Horizontal, Children = new Drawable[] { new SpriteText { Text = text.ToUpper(), + Font = OsuFont.GetFont(size: 12), }, icon = new SpriteIcon { - Size = new Vector2(10), + Size = new Vector2(8), }, } }; From b97f4a81db6a35ba5fac734bbdb0f8b42a948419 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 8 Sep 2019 00:27:40 +0300 Subject: [PATCH 015/996] Add more testing --- .../UserInterface/TestScenePageSelector.cs | 22 +++++++++++++++++++ .../Graphics/UserInterface/PageSelector.cs | 18 +++++++++++++-- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs index e5efa65c91..cb83fbd028 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs @@ -22,6 +22,28 @@ namespace osu.Game.Tests.Visual.UserInterface Anchor = Anchor.Centre, Origin = Anchor.Centre, }; + + AddStep("1 max pages", () => redraw(1)); + AddStep("10 max pages", () => redraw(10)); + AddStep("200 max pages, current 199", () => redraw(200, 199)); + AddStep("200 max pages, current 201", () => redraw(200, 201)); + AddStep("200 max pages, current -10", () => redraw(200, -10)); + } + + private void redraw(int maxPages, int currentPage = 0) + { + Clear(); + + var selector = new PageSelector(maxPages) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }; + + if (currentPage != 0) + selector.CurrentPage.Value = currentPage; + + Add(selector); } } } diff --git a/osu.Game/Graphics/UserInterface/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector.cs index 9a5ffad0d4..79a1680e4b 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector.cs @@ -59,11 +59,25 @@ namespace osu.Game.Graphics.UserInterface { base.LoadComplete(); - CurrentPage.BindValueChanged(page => redraw(page.NewValue), true); + CurrentPage.BindValueChanged(_ => redraw(), true); } - private void redraw(int newPage) + private void redraw() { + if (CurrentPage.Value > maxPages) + { + CurrentPage.Value = maxPages; + return; + } + + if (CurrentPage.Value < 1) + { + CurrentPage.Value = 1; + return; + } + + int newPage = CurrentPage.Value; + previousPageButton.Enabled.Value = newPage != 1; nextPageButton.Enabled.Value = newPage != maxPages; From 0451b45fab4ed1a377f99e7f508e7b9c0b451ef6 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 8 Sep 2019 00:37:11 +0300 Subject: [PATCH 016/996] Add missing blank line --- osu.Game/Graphics/UserInterface/PageSelector.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Graphics/UserInterface/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector.cs index 79a1680e4b..9db6366ad6 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector.cs @@ -139,6 +139,7 @@ namespace osu.Game.Graphics.UserInterface Add(background); var drawableText = CreateText(text); + if (drawableText != null) { drawableText.Margin = new MarginPadding { Horizontal = margin }; From bee7760d29e884dff532828e3bbb1f1d71fd3d6d Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 10 Sep 2019 01:36:25 +0300 Subject: [PATCH 017/996] Make MaxPages value a bindable --- .../UserInterface/TestScenePageSelector.cs | 47 ++++++++++--------- .../Graphics/UserInterface/PageSelector.cs | 40 ++++++++++++---- 2 files changed, 57 insertions(+), 30 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs index cb83fbd028..14cb27c97e 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs @@ -15,35 +15,40 @@ namespace osu.Game.Tests.Visual.UserInterface typeof(PageSelector) }; + private readonly PageSelector pageSelector; + public TestScenePageSelector() { - Child = new PageSelector(200) + Child = pageSelector = new PageSelector { Anchor = Anchor.Centre, Origin = Anchor.Centre, }; - AddStep("1 max pages", () => redraw(1)); - AddStep("10 max pages", () => redraw(10)); - AddStep("200 max pages, current 199", () => redraw(200, 199)); - AddStep("200 max pages, current 201", () => redraw(200, 201)); - AddStep("200 max pages, current -10", () => redraw(200, -10)); - } - - private void redraw(int maxPages, int currentPage = 0) - { - Clear(); - - var selector = new PageSelector(maxPages) + AddStep("10 max pages", () => setMaxPages(10)); + AddStep("200 max pages, current 199", () => { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }; - - if (currentPage != 0) - selector.CurrentPage.Value = currentPage; - - Add(selector); + setMaxPages(200); + setCurrentPage(199); + }); + AddStep("200 max pages, current 201", () => + { + setMaxPages(200); + setCurrentPage(201); + }); + AddStep("200 max pages, current -10", () => + { + setMaxPages(200); + setCurrentPage(-10); + }); + AddStep("-10 max pages", () => + { + setMaxPages(-10); + }); } + + private void setMaxPages(int maxPages) => pageSelector.MaxPages.Value = maxPages; + + private void setCurrentPage(int currentPage) => pageSelector.CurrentPage.Value = currentPage; } } diff --git a/osu.Game/Graphics/UserInterface/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector.cs index 9db6366ad6..66f51a6dad 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector.cs @@ -20,17 +20,15 @@ namespace osu.Game.Graphics.UserInterface public class PageSelector : CompositeDrawable { public readonly BindableInt CurrentPage = new BindableInt(1); + public readonly BindableInt MaxPages = new BindableInt(1); - private readonly int maxPages; private readonly FillFlowContainer itemsFlow; private readonly Button previousPageButton; private readonly Button nextPageButton; - public PageSelector(int maxPages) + public PageSelector() { - this.maxPages = maxPages; - AutoSizeAxes = Axes.Both; InternalChild = new FillFlowContainer { @@ -59,24 +57,48 @@ namespace osu.Game.Graphics.UserInterface { base.LoadComplete(); - CurrentPage.BindValueChanged(_ => redraw(), true); + MaxPages.BindValueChanged(pagesAmount => onMaxPagesChanged(pagesAmount.NewValue), true); + CurrentPage.BindValueChanged(page => onCurrentPageChanged(page.NewValue), true); } - private void redraw() + private void onMaxPagesChanged(int pagesAmount) { - if (CurrentPage.Value > maxPages) + if (pagesAmount < 1) { - CurrentPage.Value = maxPages; + MaxPages.Value = 1; return; } - if (CurrentPage.Value < 1) + if (CurrentPage.Value > pagesAmount) + { + CurrentPage.Value = pagesAmount; + return; + } + + redraw(); + } + + private void onCurrentPageChanged(int newPage) + { + if (newPage > MaxPages.Value) + { + CurrentPage.Value = MaxPages.Value; + return; + } + + if (newPage < 1) { CurrentPage.Value = 1; return; } + redraw(); + } + + private void redraw() + { int newPage = CurrentPage.Value; + int maxPages = MaxPages.Value; previousPageButton.Enabled.Value = newPage != 1; nextPageButton.Enabled.Value = newPage != maxPages; From eb683b079b5c8dd20888a8dab9ce5ac8daf6077a Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 10 Sep 2019 05:12:50 +0300 Subject: [PATCH 018/996] Adjust colours --- osu.Game/Graphics/UserInterface/PageSelector.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector.cs index 66f51a6dad..25e6ed654c 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector.cs @@ -172,8 +172,8 @@ namespace osu.Game.Graphics.UserInterface [BackgroundDependencyLoader] private void load(OsuColour colours) { - IdleColour = colours.Seafoam; - HoverColour = colours.Seafoam.Lighten(30f); + IdleColour = colours.Lime; + HoverColour = colours.Lime.Lighten(20f); } protected abstract Drawable CreateText(string text); From 41be9b3f8f08dd691a38ab1ff50872d126fe9f2e Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 19 Sep 2019 16:45:38 +0300 Subject: [PATCH 019/996] Add asserts to the test scene --- osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs index 14cb27c97e..3516e23f98 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs @@ -36,15 +36,18 @@ namespace osu.Game.Tests.Visual.UserInterface setMaxPages(200); setCurrentPage(201); }); + AddAssert("Current equals max", () => pageSelector.CurrentPage.Value == pageSelector.MaxPages.Value); AddStep("200 max pages, current -10", () => { setMaxPages(200); setCurrentPage(-10); }); + AddAssert("Current is 1", () => pageSelector.CurrentPage.Value == 1); AddStep("-10 max pages", () => { setMaxPages(-10); }); + AddAssert("Current is 1, max is 1", () => pageSelector.CurrentPage.Value == 1 && pageSelector.MaxPages.Value == 1); } private void setMaxPages(int maxPages) => pageSelector.MaxPages.Value = maxPages; From 2c0694257cd82e1dd539e0fb88424db7a6e1daba Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Dec 2019 14:44:34 +0900 Subject: [PATCH 020/996] Fix incorrect spritetext usage --- osu.Game/Graphics/UserInterface/PageSelector.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector.cs index 25e6ed654c..c9f0f3c74d 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector.cs @@ -13,6 +13,7 @@ using osuTK; using osu.Game.Graphics.Containers; using System.Collections.Generic; using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Game.Graphics.Sprites; using osuTK.Graphics; namespace osu.Game.Graphics.UserInterface @@ -192,7 +193,7 @@ namespace osu.Game.Graphics.UserInterface { } - protected override Drawable CreateText(string text) => SpriteText = new SpriteText + protected override Drawable CreateText(string text) => SpriteText = new OsuSpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -293,7 +294,7 @@ namespace osu.Game.Graphics.UserInterface Direction = FillDirection.Horizontal, Children = new Drawable[] { - new SpriteText + new OsuSpriteText { Text = text.ToUpper(), Font = OsuFont.GetFont(size: 12), From 6fbbee3093a124021ee80bb053807dba8ec71832 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 4 Jan 2020 19:03:39 +0300 Subject: [PATCH 021/996] Move PageSelector to another namespace and organize TestScene --- .../UserInterface/TestScenePageSelector.cs | 18 +++++++++++++----- .../{ => PageSelector}/PageSelector.cs | 2 +- 2 files changed, 14 insertions(+), 6 deletions(-) rename osu.Game/Graphics/UserInterface/{ => PageSelector}/PageSelector.cs (99%) diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs index 3516e23f98..59491b7f90 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs @@ -3,8 +3,9 @@ using System; using System.Collections.Generic; -using osu.Game.Graphics.UserInterface; +using NUnit.Framework; using osu.Framework.Graphics; +using osu.Game.Graphics.UserInterface.PageSelector; namespace osu.Game.Tests.Visual.UserInterface { @@ -19,12 +20,19 @@ namespace osu.Game.Tests.Visual.UserInterface public TestScenePageSelector() { - Child = pageSelector = new PageSelector + AddRange(new Drawable[] { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }; + pageSelector = new PageSelector + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } + }); + } + [Test] + public void TestPageSelectorValues() + { AddStep("10 max pages", () => setMaxPages(10)); AddStep("200 max pages, current 199", () => { diff --git a/osu.Game/Graphics/UserInterface/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs similarity index 99% rename from osu.Game/Graphics/UserInterface/PageSelector.cs rename to osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs index c9f0f3c74d..6767cb3a75 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs @@ -16,7 +16,7 @@ using osu.Framework.Extensions.IEnumerableExtensions; using osu.Game.Graphics.Sprites; using osuTK.Graphics; -namespace osu.Game.Graphics.UserInterface +namespace osu.Game.Graphics.UserInterface.PageSelector { public class PageSelector : CompositeDrawable { From 70387c19f3f4be9413541b2588d1cf0d9de49d60 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 4 Jan 2020 19:36:05 +0300 Subject: [PATCH 022/996] Implement proper DrawablePage component --- .../UserInterface/TestScenePageSelector.cs | 17 ++- .../PageSelector/DrawablePage.cs | 108 ++++++++++++++++++ .../PageSelector/PageSelector.cs | 2 + 3 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Graphics/UserInterface/PageSelector/DrawablePage.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs index 59491b7f90..aad640ab58 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs @@ -13,10 +13,12 @@ namespace osu.Game.Tests.Visual.UserInterface { public override IReadOnlyList RequiredTypes => new[] { - typeof(PageSelector) + typeof(PageSelector), + typeof(DrawablePage) }; private readonly PageSelector pageSelector; + private readonly DrawablePage drawablePage; public TestScenePageSelector() { @@ -26,6 +28,12 @@ namespace osu.Game.Tests.Visual.UserInterface { Anchor = Anchor.Centre, Origin = Anchor.Centre, + }, + drawablePage = new DrawablePage(1234) + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Margin = new MarginPadding { Top = 50 }, } }); } @@ -58,6 +66,13 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("Current is 1, max is 1", () => pageSelector.CurrentPage.Value == 1 && pageSelector.MaxPages.Value == 1); } + [Test] + public void TestDrawablePage() + { + AddStep("Select", () => drawablePage.Selected = true); + AddStep("Deselect", () => drawablePage.Selected = false); + } + private void setMaxPages(int maxPages) => pageSelector.MaxPages.Value = maxPages; private void setCurrentPage(int currentPage) => pageSelector.CurrentPage.Value = currentPage; diff --git a/osu.Game/Graphics/UserInterface/PageSelector/DrawablePage.cs b/osu.Game/Graphics/UserInterface/PageSelector/DrawablePage.cs new file mode 100644 index 0000000000..fe91874159 --- /dev/null +++ b/osu.Game/Graphics/UserInterface/PageSelector/DrawablePage.cs @@ -0,0 +1,108 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics; +using osu.Framework.Bindables; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Extensions.Color4Extensions; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Framework.Input.Events; + +namespace osu.Game.Graphics.UserInterface.PageSelector +{ + public class DrawablePage : OsuClickableContainer + { + private const int duration = 200; + + private readonly BindableBool selected = new BindableBool(); + + public bool Selected + { + get => selected.Value; + set => selected.Value = value; + } + + [Resolved] + private OsuColour colours { get; set; } + + private readonly Box background; + private readonly OsuSpriteText text; + + public DrawablePage(int page) + { + AutoSizeAxes = Axes.X; + Height = PageSelector.HEIGHT; + Child = new CircularContainer + { + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Masking = true, + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + }, + text = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = page.ToString(), + Font = OsuFont.GetFont(size: 12), + Margin = new MarginPadding { Horizontal = 10 } + } + } + }; + } + + [BackgroundDependencyLoader] + private void load() + { + background.Colour = colours.Lime; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + selected.BindValueChanged(onSelectedChanged, true); + } + + private void onSelectedChanged(ValueChangedEvent selected) + { + background.FadeTo(selected.NewValue ? 1 : 0, duration, Easing.OutQuint); + text.FadeColour(selected.NewValue ? colours.GreySeafoamDarker : colours.Lime, duration, Easing.OutQuint); + } + + protected override bool OnClick(ClickEvent e) + { + if (!selected.Value) + selected.Value = true; + + return base.OnClick(e); + } + + protected override bool OnHover(HoverEvent e) + { + updateHoverState(); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + base.OnHoverLost(e); + updateHoverState(); + } + + private void updateHoverState() + { + if (selected.Value) + return; + + text.FadeColour(IsHovered ? colours.Lime.Lighten(20f) : colours.Lime, duration, Easing.OutQuint); + } + } +} diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs index 6767cb3a75..54e3a035ec 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs @@ -20,6 +20,8 @@ namespace osu.Game.Graphics.UserInterface.PageSelector { public class PageSelector : CompositeDrawable { + public const int HEIGHT = 20; + public readonly BindableInt CurrentPage = new BindableInt(1); public readonly BindableInt MaxPages = new BindableInt(1); From 9af9da039da0c696a3277f4f19d5251a7561883f Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 4 Jan 2020 21:14:56 +0300 Subject: [PATCH 023/996] Implement proper PageSelectorItem --- .../UserInterface/TestScenePageSelector.cs | 5 +- .../PageSelector/DrawablePage.cs | 79 ++--- .../PageSelector/PageSelector.cs | 284 +++--------------- .../PageSelector/PageSelectorButton.cs | 77 +++++ .../PageSelector/PageSelectorItem.cs | 75 +++++ 5 files changed, 222 insertions(+), 298 deletions(-) create mode 100644 osu.Game/Graphics/UserInterface/PageSelector/PageSelectorButton.cs create mode 100644 osu.Game/Graphics/UserInterface/PageSelector/PageSelectorItem.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs index aad640ab58..33deff58dc 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs @@ -14,7 +14,9 @@ namespace osu.Game.Tests.Visual.UserInterface public override IReadOnlyList RequiredTypes => new[] { typeof(PageSelector), - typeof(DrawablePage) + typeof(DrawablePage), + typeof(PageSelectorButton), + typeof(PageSelectorItem) }; private readonly PageSelector pageSelector; @@ -42,6 +44,7 @@ namespace osu.Game.Tests.Visual.UserInterface public void TestPageSelectorValues() { AddStep("10 max pages", () => setMaxPages(10)); + AddStep("11 max pages", () => setMaxPages(11)); AddStep("200 max pages, current 199", () => { setMaxPages(200); diff --git a/osu.Game/Graphics/UserInterface/PageSelector/DrawablePage.cs b/osu.Game/Graphics/UserInterface/PageSelector/DrawablePage.cs index fe91874159..20f418085d 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector/DrawablePage.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector/DrawablePage.cs @@ -1,22 +1,16 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics; using osu.Framework.Bindables; using osu.Framework.Allocation; -using osu.Framework.Graphics.Shapes; using osu.Framework.Extensions.Color4Extensions; -using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; -using osu.Framework.Input.Events; namespace osu.Game.Graphics.UserInterface.PageSelector { - public class DrawablePage : OsuClickableContainer + public class DrawablePage : PageSelectorItem { - private const int duration = 200; - private readonly BindableBool selected = new BindableBool(); public bool Selected @@ -25,44 +19,33 @@ namespace osu.Game.Graphics.UserInterface.PageSelector set => selected.Value = value; } - [Resolved] - private OsuColour colours { get; set; } + public int Page { get; private set; } - private readonly Box background; - private readonly OsuSpriteText text; + private OsuSpriteText text; public DrawablePage(int page) { - AutoSizeAxes = Axes.X; - Height = PageSelector.HEIGHT; - Child = new CircularContainer + Page = page; + text.Text = page.ToString(); + + Background.Alpha = 0; + + Action = () => { - RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X, - Masking = true, - Children = new Drawable[] - { - background = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - }, - text = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = page.ToString(), - Font = OsuFont.GetFont(size: 12), - Margin = new MarginPadding { Horizontal = 10 } - } - } + if (!selected.Value) + selected.Value = true; }; } + protected override Drawable CreateContent() => text = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12), + }; + [BackgroundDependencyLoader] private void load() { - background.Colour = colours.Lime; + Background.Colour = Colours.Lime; } protected override void LoadComplete() @@ -73,36 +56,16 @@ namespace osu.Game.Graphics.UserInterface.PageSelector private void onSelectedChanged(ValueChangedEvent selected) { - background.FadeTo(selected.NewValue ? 1 : 0, duration, Easing.OutQuint); - text.FadeColour(selected.NewValue ? colours.GreySeafoamDarker : colours.Lime, duration, Easing.OutQuint); + Background.FadeTo(selected.NewValue ? 1 : 0, DURATION, Easing.OutQuint); + text.FadeColour(selected.NewValue ? Colours.GreySeafoamDarker : Colours.Lime, DURATION, Easing.OutQuint); } - protected override bool OnClick(ClickEvent e) - { - if (!selected.Value) - selected.Value = true; - - return base.OnClick(e); - } - - protected override bool OnHover(HoverEvent e) - { - updateHoverState(); - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - base.OnHoverLost(e); - updateHoverState(); - } - - private void updateHoverState() + protected override void UpdateHoverState() { if (selected.Value) return; - text.FadeColour(IsHovered ? colours.Lime.Lighten(20f) : colours.Lime, duration, Easing.OutQuint); + text.FadeColour(IsHovered ? Colours.Lime.Lighten(20f) : Colours.Lime, DURATION, Easing.OutQuint); } } } diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs index 54e3a035ec..ae6fc2b500 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs @@ -4,17 +4,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics; using osu.Framework.Bindables; -using osu.Framework.Allocation; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Extensions.Color4Extensions; -using System; -using osuTK; -using osu.Game.Graphics.Containers; -using System.Collections.Generic; using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Game.Graphics.Sprites; -using osuTK.Graphics; namespace osu.Game.Graphics.UserInterface.PageSelector { @@ -25,10 +15,10 @@ namespace osu.Game.Graphics.UserInterface.PageSelector public readonly BindableInt CurrentPage = new BindableInt(1); public readonly BindableInt MaxPages = new BindableInt(1); - private readonly FillFlowContainer itemsFlow; + private readonly FillFlowContainer itemsFlow; - private readonly Button previousPageButton; - private readonly Button nextPageButton; + private readonly PageSelectorButton previousPageButton; + private readonly PageSelectorButton nextPageButton; public PageSelector() { @@ -39,16 +29,16 @@ namespace osu.Game.Graphics.UserInterface.PageSelector Direction = FillDirection.Horizontal, Children = new Drawable[] { - previousPageButton = new Button(false, "prev") + previousPageButton = new PageSelectorButton(false, "prev") { Action = () => CurrentPage.Value -= 1, }, - itemsFlow = new FillFlowContainer + itemsFlow = new FillFlowContainer { AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, }, - nextPageButton = new Button(true, "next") + nextPageButton = new PageSelectorButton(true, "next") { Action = () => CurrentPage.Value += 1 } @@ -60,253 +50,69 @@ namespace osu.Game.Graphics.UserInterface.PageSelector { base.LoadComplete(); - MaxPages.BindValueChanged(pagesAmount => onMaxPagesChanged(pagesAmount.NewValue), true); - CurrentPage.BindValueChanged(page => onCurrentPageChanged(page.NewValue), true); - } - - private void onMaxPagesChanged(int pagesAmount) - { - if (pagesAmount < 1) - { - MaxPages.Value = 1; - return; - } - - if (CurrentPage.Value > pagesAmount) - { - CurrentPage.Value = pagesAmount; - return; - } - + MaxPages.BindValueChanged(_ => redraw()); + CurrentPage.BindValueChanged(page => onCurrentPageChanged(page.NewValue)); redraw(); } private void onCurrentPageChanged(int newPage) { - if (newPage > MaxPages.Value) - { - CurrentPage.Value = MaxPages.Value; - return; - } - if (newPage < 1) { CurrentPage.Value = 1; return; } - redraw(); + if (newPage > MaxPages.Value) + { + CurrentPage.Value = MaxPages.Value; + return; + } + + itemsFlow.ForEach(page => page.Selected = page.Page == newPage ? true : false); + updateButtonsState(); } private void redraw() + { + itemsFlow.Clear(); + + if (MaxPages.Value < 1) + { + MaxPages.Value = 1; + return; + } + + for (int i = 1; i <= MaxPages.Value; i++) + addDrawablePage(i); + + if (CurrentPage.Value > MaxPages.Value) + { + CurrentPage.Value = MaxPages.Value; + return; + } + + if (CurrentPage.Value < 1) + { + CurrentPage.Value = 1; + return; + } + + CurrentPage.TriggerChange(); + } + + private void updateButtonsState() { int newPage = CurrentPage.Value; int maxPages = MaxPages.Value; previousPageButton.Enabled.Value = newPage != 1; nextPageButton.Enabled.Value = newPage != maxPages; - - itemsFlow.Clear(); - - if (newPage > 3) - addDrawablePage(1); - - if (newPage > 4) - addPlaceholder(); - - for (int i = Math.Max(newPage - 2, 1); i <= Math.Min(newPage + 2, maxPages); i++) - { - if (i == newPage) - addDrawableCurrentPage(); - else - addDrawablePage(i); - } - - if (newPage + 2 < maxPages - 1) - addPlaceholder(); - - if (newPage + 2 < maxPages) - addDrawablePage(maxPages); } - private void addDrawablePage(int page) => itemsFlow.Add(new DrawablePage(page.ToString()) + private void addDrawablePage(int page) => itemsFlow.Add(new DrawablePage(page) { Action = () => CurrentPage.Value = page, }); - - private void addPlaceholder() => itemsFlow.Add(new Placeholder()); - - private void addDrawableCurrentPage() => itemsFlow.Add(new SelectedPage(CurrentPage.Value.ToString())); - - private abstract class PageItem : OsuHoverContainer - { - private const int margin = 10; - private const int height = 20; - - protected override Container Content => contentContainer; - - private readonly CircularContainer contentContainer; - - protected PageItem(string text) - { - AutoSizeAxes = Axes.X; - Height = height; - - base.Content.Add(contentContainer = new CircularContainer - { - AutoSizeAxes = Axes.X, - RelativeSizeAxes = Axes.Y, - Masking = true, - }); - - var background = CreateBackground(); - if (background != null) - Add(background); - - var drawableText = CreateText(text); - - if (drawableText != null) - { - drawableText.Margin = new MarginPadding { Horizontal = margin }; - Add(drawableText); - } - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - IdleColour = colours.Lime; - HoverColour = colours.Lime.Lighten(20f); - } - - protected abstract Drawable CreateText(string text); - - protected virtual Drawable CreateBackground() => null; - } - - private class DrawablePage : PageItem - { - protected SpriteText SpriteText; - - protected override IEnumerable EffectTargets => new[] { SpriteText }; - - public DrawablePage(string text) - : base(text) - { - } - - protected override Drawable CreateText(string text) => SpriteText = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = text, - Font = OsuFont.GetFont(size: 12), - }; - } - - private class SelectedPage : DrawablePage - { - private Box background; - - protected override IEnumerable EffectTargets => new[] { background }; - - public SelectedPage(string text) - : base(text) - { - } - - protected override Drawable CreateBackground() => background = new Box - { - RelativeSizeAxes = Axes.Both, - }; - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - SpriteText.Colour = colours.GreySeafoamDark; - } - } - - private class Placeholder : DrawablePage - { - public Placeholder() - : base("...") - { - } - } - - private class Button : PageItem - { - private const int duration = 100; - - private Box background; - private FillFlowContainer textContainer; - private SpriteIcon icon; - private readonly Box fadeBox; - - protected override IEnumerable EffectTargets => new[] { textContainer }; - - public Button(bool rightAligned, string text) - : base(text) - { - var alignment = rightAligned ? Anchor.x0 : Anchor.x2; - - textContainer.ForEach(drawable => - { - drawable.Anchor = Anchor.y1 | alignment; - drawable.Origin = Anchor.y1 | alignment; - }); - - icon.Icon = alignment == Anchor.x2 ? FontAwesome.Solid.ChevronLeft : FontAwesome.Solid.ChevronRight; - - Add(fadeBox = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black.Opacity(100) - }); - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - background.Colour = colours.GreySeafoamDark; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - Enabled.BindValueChanged(onEnabledChanged, true); - } - - private void onEnabledChanged(ValueChangedEvent enabled) - { - fadeBox.FadeTo(enabled.NewValue ? 0 : 1, duration); - } - - protected override Drawable CreateBackground() => background = new Box - { - RelativeSizeAxes = Axes.Both, - }; - - protected override Drawable CreateText(string text) => textContainer = new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Direction = FillDirection.Horizontal, - Children = new Drawable[] - { - new OsuSpriteText - { - Text = text.ToUpper(), - Font = OsuFont.GetFont(size: 12), - }, - icon = new SpriteIcon - { - Size = new Vector2(8), - }, - } - }; - } } } diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorButton.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorButton.cs new file mode 100644 index 0000000000..df007b32e0 --- /dev/null +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorButton.cs @@ -0,0 +1,77 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Extensions.Color4Extensions; +using osu.Game.Graphics.Sprites; +using osu.Framework.Graphics.Sprites; +using osuTK.Graphics; +using osuTK; +using osu.Framework.Extensions.IEnumerableExtensions; + +namespace osu.Game.Graphics.UserInterface.PageSelector +{ + public class PageSelectorButton : PageSelectorItem + { + private readonly Box fadeBox; + private SpriteIcon icon; + private OsuSpriteText name; + private FillFlowContainer buttonContent; + + public PageSelectorButton(bool rightAligned, string text) + { + Add(fadeBox = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black.Opacity(100) + }); + + var alignment = rightAligned ? Anchor.x0 : Anchor.x2; + + buttonContent.ForEach(drawable => + { + drawable.Anchor = Anchor.y1 | alignment; + drawable.Origin = Anchor.y1 | alignment; + }); + + icon.Icon = alignment == Anchor.x2 ? FontAwesome.Solid.ChevronLeft : FontAwesome.Solid.ChevronRight; + + name.Text = text.ToUpper(); + } + + protected override Drawable CreateContent() => buttonContent = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + name = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12), + }, + icon = new SpriteIcon + { + Size = new Vector2(8), + }, + } + }; + + [BackgroundDependencyLoader] + private void load() + { + Background.Colour = Colours.GreySeafoamDark; + name.Colour = icon.Colour = Colours.Lime; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + Enabled.BindValueChanged(enabled => fadeBox.FadeTo(enabled.NewValue ? 0 : 1, DURATION), true); + } + + protected override void UpdateHoverState() => Background.FadeColour(IsHovered ? Colours.GreySeafoam : Colours.GreySeafoamDark, DURATION, Easing.OutQuint); + } +} diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorItem.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorItem.cs new file mode 100644 index 0000000000..d457b7ea0e --- /dev/null +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorItem.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 osu.Framework.Graphics.Containers; +using osu.Framework.Graphics; +using osu.Framework.Bindables; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Extensions.Color4Extensions; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Framework.Input.Events; +using osu.Framework.Graphics.Sprites; +using osuTK.Graphics; +using osuTK; +using osu.Framework.Extensions.IEnumerableExtensions; +using JetBrains.Annotations; + +namespace osu.Game.Graphics.UserInterface.PageSelector +{ + public abstract class PageSelectorItem : OsuClickableContainer + { + protected const int DURATION = 200; + + [Resolved] + protected OsuColour Colours { get; private set; } + + protected override Container Content => content; + + protected readonly Box Background; + private readonly CircularContainer content; + + protected PageSelectorItem() + { + AutoSizeAxes = Axes.X; + Height = PageSelector.HEIGHT; + base.Content.Add(content = new CircularContainer + { + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Masking = true, + Children = new Drawable[] + { + Background = new Box + { + RelativeSizeAxes = Axes.Both + }, + CreateContent().With(content => + { + content.Anchor = Anchor.Centre; + content.Origin = Anchor.Centre; + content.Margin = new MarginPadding { Horizontal = 10 }; + }) + } + }); + } + + [NotNull] + protected abstract Drawable CreateContent(); + + protected override bool OnHover(HoverEvent e) + { + UpdateHoverState(); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + base.OnHoverLost(e); + UpdateHoverState(); + } + + protected abstract void UpdateHoverState(); + } +} From b1c5e437ccf2ca260d5b8f177d78144651b1d24d Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 4 Jan 2020 21:22:45 +0300 Subject: [PATCH 024/996] Remove usings --- .../UserInterface/PageSelector/PageSelectorItem.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorItem.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorItem.cs index d457b7ea0e..63eb87a638 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorItem.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorItem.cs @@ -3,17 +3,10 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics; -using osu.Framework.Bindables; using osu.Framework.Allocation; using osu.Framework.Graphics.Shapes; -using osu.Framework.Extensions.Color4Extensions; using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; using osu.Framework.Input.Events; -using osu.Framework.Graphics.Sprites; -using osuTK.Graphics; -using osuTK; -using osu.Framework.Extensions.IEnumerableExtensions; using JetBrains.Annotations; namespace osu.Game.Graphics.UserInterface.PageSelector From 37482b2ad474379ed941a5ab3a8c40f813427eaf Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 4 Jan 2020 22:05:34 +0300 Subject: [PATCH 025/996] CI fixes --- osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs | 2 +- .../Graphics/UserInterface/PageSelector/PageSelectorItem.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs index ae6fc2b500..eaa102bdd2 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs @@ -69,7 +69,7 @@ namespace osu.Game.Graphics.UserInterface.PageSelector return; } - itemsFlow.ForEach(page => page.Selected = page.Page == newPage ? true : false); + itemsFlow.ForEach(page => page.Selected = page.Page == newPage); updateButtonsState(); } diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorItem.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorItem.cs index 63eb87a638..5f0bfcdfdb 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorItem.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorItem.cs @@ -32,7 +32,7 @@ namespace osu.Game.Graphics.UserInterface.PageSelector RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, Masking = true, - Children = new Drawable[] + Children = new[] { Background = new Box { From d3c2dc43bd28812bafffb6d81a596268ed5ddca1 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 4 Jan 2020 22:25:08 +0300 Subject: [PATCH 026/996] TestScene improvements --- .../UserInterface/TestScenePageSelector.cs | 48 +++++++++---------- .../PageSelector/PageSelector.cs | 15 ++---- .../PageSelector/PageSelectorButton.cs | 1 + 3 files changed, 27 insertions(+), 37 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs index 33deff58dc..5e1105c834 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs @@ -41,32 +41,30 @@ namespace osu.Game.Tests.Visual.UserInterface } [Test] - public void TestPageSelectorValues() + public void TestCurrentPageReset() { - AddStep("10 max pages", () => setMaxPages(10)); - AddStep("11 max pages", () => setMaxPages(11)); - AddStep("200 max pages, current 199", () => - { - setMaxPages(200); - setCurrentPage(199); - }); - AddStep("200 max pages, current 201", () => - { - setMaxPages(200); - setCurrentPage(201); - }); - AddAssert("Current equals max", () => pageSelector.CurrentPage.Value == pageSelector.MaxPages.Value); - AddStep("200 max pages, current -10", () => - { - setMaxPages(200); - setCurrentPage(-10); - }); - AddAssert("Current is 1", () => pageSelector.CurrentPage.Value == 1); - AddStep("-10 max pages", () => - { - setMaxPages(-10); - }); - AddAssert("Current is 1, max is 1", () => pageSelector.CurrentPage.Value == 1 && pageSelector.MaxPages.Value == 1); + AddStep("Set 10 pages", () => setMaxPages(10)); + AddStep("Select 5 page", () => setCurrentPage(5)); + AddStep("Set 11 pages", () => setMaxPages(11)); + AddAssert("Check 1 page is current", () => pageSelector.CurrentPage.Value == 1); + } + + [Test] + public void TestUnexistingPageSelection() + { + AddStep("Set 10 pages", () => setMaxPages(10)); + AddStep("Select 11 page", () => setCurrentPage(11)); + AddAssert("Check current equals max", () => pageSelector.CurrentPage.Value == pageSelector.MaxPages.Value); + + AddStep("Select -1 page", () => setCurrentPage(-1)); + AddAssert("Check current is 1", () => pageSelector.CurrentPage.Value == 1); + } + + [Test] + public void TestNegativeMaxPages() + { + AddStep("Set -10 pages", () => setMaxPages(-10)); + AddAssert("Check current and max is 1", () => pageSelector.CurrentPage.Value == 1 && pageSelector.MaxPages.Value == 1); } [Test] diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs index eaa102bdd2..c2482d6330 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs @@ -86,19 +86,10 @@ namespace osu.Game.Graphics.UserInterface.PageSelector for (int i = 1; i <= MaxPages.Value; i++) addDrawablePage(i); - if (CurrentPage.Value > MaxPages.Value) - { - CurrentPage.Value = MaxPages.Value; - return; - } - - if (CurrentPage.Value < 1) - { + if (CurrentPage.Value == 1) + CurrentPage.TriggerChange(); + else CurrentPage.Value = 1; - return; - } - - CurrentPage.TriggerChange(); } private void updateButtonsState() diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorButton.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorButton.cs index df007b32e0..e81ce20d27 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorButton.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorButton.cs @@ -46,6 +46,7 @@ namespace osu.Game.Graphics.UserInterface.PageSelector { AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, + Spacing = new Vector2(3, 0), Children = new Drawable[] { name = new OsuSpriteText From 753db9599a225784c19e3459b6d830d5f62683c6 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 4 Jan 2020 22:29:11 +0300 Subject: [PATCH 027/996] Move items height out of PageSelector --- osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs | 2 -- .../Graphics/UserInterface/PageSelector/PageSelectorItem.cs | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs index c2482d6330..8e055faea3 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs @@ -10,8 +10,6 @@ namespace osu.Game.Graphics.UserInterface.PageSelector { public class PageSelector : CompositeDrawable { - public const int HEIGHT = 20; - public readonly BindableInt CurrentPage = new BindableInt(1); public readonly BindableInt MaxPages = new BindableInt(1); diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorItem.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorItem.cs index 5f0bfcdfdb..cd61961dbe 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorItem.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorItem.cs @@ -26,7 +26,7 @@ namespace osu.Game.Graphics.UserInterface.PageSelector protected PageSelectorItem() { AutoSizeAxes = Axes.X; - Height = PageSelector.HEIGHT; + Height = 20; base.Content.Add(content = new CircularContainer { RelativeSizeAxes = Axes.Y, From 20ab415838fab4cca9fcf4675d908670032d1b56 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 6 Jan 2020 18:04:27 +0900 Subject: [PATCH 028/996] Reword tests --- .../UserInterface/TestScenePageSelector.cs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs index 5e1105c834..6494486d4e 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs @@ -41,30 +41,31 @@ namespace osu.Game.Tests.Visual.UserInterface } [Test] - public void TestCurrentPageReset() + public void TestResetCurrentPage() { AddStep("Set 10 pages", () => setMaxPages(10)); - AddStep("Select 5 page", () => setCurrentPage(5)); + AddStep("Select page 5", () => setCurrentPage(5)); AddStep("Set 11 pages", () => setMaxPages(11)); - AddAssert("Check 1 page is current", () => pageSelector.CurrentPage.Value == 1); + AddAssert("Page 1 is current", () => pageSelector.CurrentPage.Value == 1); } [Test] - public void TestUnexistingPageSelection() + public void TestOutOfBoundsSelection() { AddStep("Set 10 pages", () => setMaxPages(10)); - AddStep("Select 11 page", () => setCurrentPage(11)); - AddAssert("Check current equals max", () => pageSelector.CurrentPage.Value == pageSelector.MaxPages.Value); + AddStep("Select page 11", () => setCurrentPage(11)); + AddAssert("Page 10 is current", () => pageSelector.CurrentPage.Value == pageSelector.MaxPages.Value); - AddStep("Select -1 page", () => setCurrentPage(-1)); - AddAssert("Check current is 1", () => pageSelector.CurrentPage.Value == 1); + AddStep("Select page -1", () => setCurrentPage(-1)); + AddAssert("Page 1 is current", () => pageSelector.CurrentPage.Value == 1); } [Test] public void TestNegativeMaxPages() { AddStep("Set -10 pages", () => setMaxPages(-10)); - AddAssert("Check current and max is 1", () => pageSelector.CurrentPage.Value == 1 && pageSelector.MaxPages.Value == 1); + AddAssert("Page 1 is current", () => pageSelector.CurrentPage.Value == 1); + AddAssert("Max is 1", () => pageSelector.MaxPages.Value == 1); } [Test] From eaa464e548582da39515ded5d9421146eabdcfc6 Mon Sep 17 00:00:00 2001 From: mk-56 Date: Sun, 28 Nov 2021 02:58:08 +0100 Subject: [PATCH 029/996] Initial implementation of adjustable positional hitobject audio strength --- osu.Game/Configuration/OsuConfigManager.cs | 2 ++ osu.Game/Localisation/AudioSettingsStrings.cs | 5 ++++ .../Sections/Gameplay/AudioSettings.cs | 28 ++++++++++++++++++- .../Objects/Drawables/DrawableHitObject.cs | 4 ++- 4 files changed, 37 insertions(+), 2 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 84da3f666d..a124c1c5ae 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -97,6 +97,7 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.MenuParallax, true); // Gameplay + SetDefault(OsuSetting.PositionalHitsoundsLevel, 0.8f, 0.1f, 1f); SetDefault(OsuSetting.DimLevel, 0.8, 0, 1, 0.01); SetDefault(OsuSetting.BlurLevel, 0, 0, 1, 0.01); SetDefault(OsuSetting.LightenDuringBreaks, true); @@ -251,6 +252,7 @@ namespace osu.Game.Configuration BlurLevel, LightenDuringBreaks, ShowStoryboard, + PositionalHitsoundsLevel, KeyOverlay, PositionalHitSounds, AlwaysPlayFirstComboBreak, diff --git a/osu.Game/Localisation/AudioSettingsStrings.cs b/osu.Game/Localisation/AudioSettingsStrings.cs index 008781c2e5..a55816b401 100644 --- a/osu.Game/Localisation/AudioSettingsStrings.cs +++ b/osu.Game/Localisation/AudioSettingsStrings.cs @@ -32,6 +32,11 @@ namespace osu.Game.Localisation /// /// "Master" /// + public static LocalisableString PositionalLevel => new TranslatableString(getKey(@"positional_hitsound_audio_level"), @"Positional hitsound audio level."); + + /// + /// "Level" + /// public static LocalisableString MasterVolume => new TranslatableString(getKey(@"master_volume"), @"Master"); /// diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/AudioSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/AudioSettings.cs index dba64d695a..c239fd282a 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/AudioSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/AudioSettings.cs @@ -2,10 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Localisation; using osu.Game.Configuration; using osu.Game.Localisation; +using osu.Framework.Graphics.Containers; + namespace osu.Game.Overlays.Settings.Sections.Gameplay { @@ -13,9 +16,15 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay { protected override LocalisableString Header => GameplaySettingsStrings.AudioHeader; + private Bindable positionalHitsoundsLevel; + + private FillFlowContainer> positionalHitsoundsSettings; + + [BackgroundDependencyLoader] - private void load(OsuConfigManager config) + private void load(OsuConfigManager config,OsuConfigManager osuConfig) { + positionalHitsoundsLevel = osuConfig.GetBindable(OsuSetting.PositionalHitsoundsLevel); Children = new Drawable[] { new SettingsCheckbox @@ -23,6 +32,23 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay LabelText = GameplaySettingsStrings.PositionalHitsounds, Current = config.GetBindable(OsuSetting.PositionalHitSounds) }, + positionalHitsoundsSettings = new FillFlowContainer> + { + Direction = FillDirection.Vertical, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Masking = true, + Children = new[] + { + new SettingsSlider + { + LabelText = AudioSettingsStrings.PositionalLevel, + Current = positionalHitsoundsLevel, + KeyboardStep = 0.01f, + DisplayAsPercentage = true, + }, + } + }, new SettingsCheckbox { LabelText = GameplaySettingsStrings.AlwaysPlayFirstComboBreak, diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 01817147ae..1a0fe8d004 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -22,6 +22,7 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Skinning; using osuTK.Graphics; +using osu.Game.Overlays.Settings.Sections.Gameplay; namespace osu.Game.Rulesets.Objects.Drawables { @@ -124,6 +125,7 @@ namespace osu.Game.Rulesets.Objects.Drawables public readonly Bindable StartTimeBindable = new Bindable(); private readonly BindableList samplesBindable = new BindableList(); private readonly Bindable userPositionalHitSounds = new Bindable(); + private readonly Bindable positionalHitsoundsLevel = new Bindable(); private readonly Bindable comboIndexBindable = new Bindable(); private readonly Bindable comboIndexWithOffsetsBindable = new Bindable(); @@ -532,7 +534,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// The lookup X position. Generally should be . protected double CalculateSamplePlaybackBalance(double position) { - const float balance_adjust_amount = 0.4f; + float balance_adjust_amount = positionalHitsoundsLevel.Value; return balance_adjust_amount * (userPositionalHitSounds.Value ? position - 0.5f : 0); } From e83115ad5e617843937eb751a74bc60538fa0796 Mon Sep 17 00:00:00 2001 From: mk-56 Date: Sun, 28 Nov 2021 03:25:11 +0100 Subject: [PATCH 030/996] Binding logic fix, nullification of unnecessary path --- osu.Game/Configuration/OsuConfigManager.cs | 2 +- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index a124c1c5ae..ed0fe17e27 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -97,7 +97,7 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.MenuParallax, true); // Gameplay - SetDefault(OsuSetting.PositionalHitsoundsLevel, 0.8f, 0.1f, 1f); + SetDefault(OsuSetting.PositionalHitsoundsLevel, 0.8f, 0, 1); SetDefault(OsuSetting.DimLevel, 0.8, 0, 1, 0.01); SetDefault(OsuSetting.BlurLevel, 0, 0, 1, 0.01); SetDefault(OsuSetting.LightenDuringBreaks, true); diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 1a0fe8d004..601756485b 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -171,6 +171,7 @@ namespace osu.Game.Rulesets.Objects.Drawables private void load(OsuConfigManager config, ISkinSource skinSource) { config.BindWith(OsuSetting.PositionalHitSounds, userPositionalHitSounds); + config.BindWith(OsuSetting.PositionalHitsoundsLevel, positionalHitsoundsLevel); // Explicit non-virtual function call. base.AddInternal(Samples = new PausableSkinnableSound()); @@ -534,9 +535,9 @@ namespace osu.Game.Rulesets.Objects.Drawables /// The lookup X position. Generally should be . protected double CalculateSamplePlaybackBalance(double position) { - float balance_adjust_amount = positionalHitsoundsLevel.Value; + float balance_adjust_amount = positionalHitsoundsLevel.Value*2; - return balance_adjust_amount * (userPositionalHitSounds.Value ? position - 0.5f : 0); + return balance_adjust_amount * (true ? position - 0.5f : 0); } /// From ff9c68dd6aafcb0103e33f8f735674a6b1cb2b11 Mon Sep 17 00:00:00 2001 From: mk-56 Date: Sun, 28 Nov 2021 03:28:35 +0100 Subject: [PATCH 031/996] cleanup --- osu.Game/Overlays/Settings/Sections/Gameplay/AudioSettings.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/AudioSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/AudioSettings.cs index c239fd282a..9e677943c2 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/AudioSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/AudioSettings.cs @@ -9,7 +9,6 @@ using osu.Game.Configuration; using osu.Game.Localisation; using osu.Framework.Graphics.Containers; - namespace osu.Game.Overlays.Settings.Sections.Gameplay { public class AudioSettings : SettingsSubsection @@ -20,7 +19,6 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay private FillFlowContainer> positionalHitsoundsSettings; - [BackgroundDependencyLoader] private void load(OsuConfigManager config,OsuConfigManager osuConfig) { From c3fb7937627301cb92359ff2939cd9e3197cae31 Mon Sep 17 00:00:00 2001 From: mk-56 Date: Sun, 28 Nov 2021 14:06:53 +0100 Subject: [PATCH 032/996] Fixed the problems that were brought up and deleted the old bind logic --- osu.Game/Configuration/OsuConfigManager.cs | 4 +--- osu.Game/Localisation/AudioSettingsStrings.cs | 2 +- osu.Game/Localisation/GameplaySettingsStrings.cs | 5 ----- .../Settings/Sections/Gameplay/AudioSettings.cs | 7 +------ .../Rulesets/Objects/Drawables/DrawableHitObject.cs | 11 +++++------ 5 files changed, 8 insertions(+), 21 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index ed0fe17e27..90cf09dce1 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -97,7 +97,7 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.MenuParallax, true); // Gameplay - SetDefault(OsuSetting.PositionalHitsoundsLevel, 0.8f, 0, 1); + SetDefault(OsuSetting.PositionalHitsoundsLevel, 0.2f, 0, 1); SetDefault(OsuSetting.DimLevel, 0.8, 0, 1, 0.01); SetDefault(OsuSetting.BlurLevel, 0, 0, 1, 0.01); SetDefault(OsuSetting.LightenDuringBreaks, true); @@ -109,7 +109,6 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.ShowHealthDisplayWhenCantFail, true); SetDefault(OsuSetting.FadePlayfieldWhenHealthLow, true); SetDefault(OsuSetting.KeyOverlay, false); - SetDefault(OsuSetting.PositionalHitSounds, true); SetDefault(OsuSetting.AlwaysPlayFirstComboBreak, true); SetDefault(OsuSetting.FloatingComments, false); @@ -254,7 +253,6 @@ namespace osu.Game.Configuration ShowStoryboard, PositionalHitsoundsLevel, KeyOverlay, - PositionalHitSounds, AlwaysPlayFirstComboBreak, FloatingComments, HUDVisibilityMode, diff --git a/osu.Game/Localisation/AudioSettingsStrings.cs b/osu.Game/Localisation/AudioSettingsStrings.cs index a55816b401..ba48c412c4 100644 --- a/osu.Game/Localisation/AudioSettingsStrings.cs +++ b/osu.Game/Localisation/AudioSettingsStrings.cs @@ -32,7 +32,7 @@ namespace osu.Game.Localisation /// /// "Master" /// - public static LocalisableString PositionalLevel => new TranslatableString(getKey(@"positional_hitsound_audio_level"), @"Positional hitsound audio level."); + public static LocalisableString PositionalLevel => new TranslatableString(getKey(@"positional_hitsound_audio_level"), @"Positional hitsound audio level"); /// /// "Level" diff --git a/osu.Game/Localisation/GameplaySettingsStrings.cs b/osu.Game/Localisation/GameplaySettingsStrings.cs index fa92187650..84c3704e26 100644 --- a/osu.Game/Localisation/GameplaySettingsStrings.cs +++ b/osu.Game/Localisation/GameplaySettingsStrings.cs @@ -84,11 +84,6 @@ namespace osu.Game.Localisation /// public static LocalisableString AlwaysShowKeyOverlay => new TranslatableString(getKey(@"key_overlay"), @"Always show key overlay"); - /// - /// "Positional hitsounds" - /// - public static LocalisableString PositionalHitsounds => new TranslatableString(getKey(@"positional_hitsounds"), @"Positional hitsounds"); - /// /// "Always play first combo break sound" /// diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/AudioSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/AudioSettings.cs index 9e677943c2..a5c5399e98 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/AudioSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/AudioSettings.cs @@ -24,12 +24,7 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay { positionalHitsoundsLevel = osuConfig.GetBindable(OsuSetting.PositionalHitsoundsLevel); Children = new Drawable[] - { - new SettingsCheckbox - { - LabelText = GameplaySettingsStrings.PositionalHitsounds, - Current = config.GetBindable(OsuSetting.PositionalHitSounds) - }, + { positionalHitsoundsSettings = new FillFlowContainer> { Direction = FillDirection.Vertical, diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 601756485b..ea153bd0e6 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -124,10 +124,8 @@ namespace osu.Game.Rulesets.Objects.Drawables public readonly Bindable StartTimeBindable = new Bindable(); private readonly BindableList samplesBindable = new BindableList(); - private readonly Bindable userPositionalHitSounds = new Bindable(); - private readonly Bindable positionalHitsoundsLevel = new Bindable(); - private readonly Bindable comboIndexBindable = new Bindable(); + private readonly Bindable positionalHitsoundsLevel = new Bindable(); private readonly Bindable comboIndexBindable = new Bindable(); private readonly Bindable comboIndexWithOffsetsBindable = new Bindable(); protected override bool RequiresChildrenUpdate => true; @@ -170,7 +168,6 @@ namespace osu.Game.Rulesets.Objects.Drawables [BackgroundDependencyLoader] private void load(OsuConfigManager config, ISkinSource skinSource) { - config.BindWith(OsuSetting.PositionalHitSounds, userPositionalHitSounds); config.BindWith(OsuSetting.PositionalHitsoundsLevel, positionalHitsoundsLevel); // Explicit non-virtual function call. @@ -535,9 +532,11 @@ namespace osu.Game.Rulesets.Objects.Drawables /// The lookup X position. Generally should be . protected double CalculateSamplePlaybackBalance(double position) { + double returnedvalue; float balance_adjust_amount = positionalHitsoundsLevel.Value*2; - - return balance_adjust_amount * (true ? position - 0.5f : 0); + returnedvalue = balance_adjust_amount *(position - 0.5f ); + + return returnedvalue; } /// From 9065179c523eb1f1cfdfa02559da4f9bdfddde60 Mon Sep 17 00:00:00 2001 From: mk-56 Date: Sun, 28 Nov 2021 14:09:30 +0100 Subject: [PATCH 033/996] Fix --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index ea153bd0e6..152f92985e 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -124,6 +124,7 @@ namespace osu.Game.Rulesets.Objects.Drawables public readonly Bindable StartTimeBindable = new Bindable(); private readonly BindableList samplesBindable = new BindableList(); + private readonly Bindable comboIndexBindable = new Bindable(); private readonly Bindable positionalHitsoundsLevel = new Bindable(); private readonly Bindable comboIndexBindable = new Bindable(); private readonly Bindable comboIndexWithOffsetsBindable = new Bindable(); From 9c42cc0c05e643c807fb8fc05afba98a95e0cd3c Mon Sep 17 00:00:00 2001 From: mk-56 Date: Sun, 28 Nov 2021 14:09:30 +0100 Subject: [PATCH 034/996] Fix --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index ea153bd0e6..01c573eb94 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -125,7 +125,8 @@ namespace osu.Game.Rulesets.Objects.Drawables public readonly Bindable StartTimeBindable = new Bindable(); private readonly BindableList samplesBindable = new BindableList(); - private readonly Bindable positionalHitsoundsLevel = new Bindable(); private readonly Bindable comboIndexBindable = new Bindable(); + private readonly Bindable positionalHitsoundsLevel = new Bindable(); + private readonly Bindable comboIndexBindable = new Bindable(); private readonly Bindable comboIndexWithOffsetsBindable = new Bindable(); protected override bool RequiresChildrenUpdate => true; From fd5af1fbe7e0f776d7b5ff5bd5ac7b4e3479fd70 Mon Sep 17 00:00:00 2001 From: mk-56 Date: Fri, 17 Dec 2021 13:16:06 +0100 Subject: [PATCH 035/996] Code refactor and name changes cleaned code up with Jetbrains i hope it suffices --- osu.Game/Localisation/AudioSettingsStrings.cs | 2 +- .../Settings/Sections/Gameplay/AudioSettings.cs | 10 +++++----- .../Rulesets/Objects/Drawables/DrawableHitObject.cs | 11 ++++------- 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/osu.Game/Localisation/AudioSettingsStrings.cs b/osu.Game/Localisation/AudioSettingsStrings.cs index ba48c412c4..f298717c99 100644 --- a/osu.Game/Localisation/AudioSettingsStrings.cs +++ b/osu.Game/Localisation/AudioSettingsStrings.cs @@ -32,7 +32,7 @@ namespace osu.Game.Localisation /// /// "Master" /// - public static LocalisableString PositionalLevel => new TranslatableString(getKey(@"positional_hitsound_audio_level"), @"Positional hitsound audio level"); + public static LocalisableString PositionalLevel => new TranslatableString(getKey(@"positional_hitsound_audio_level"), @"Hitsound stereo separation"); /// /// "Level" diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/AudioSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/AudioSettings.cs index a5c5399e98..4238ad1605 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/AudioSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/AudioSettings.cs @@ -20,11 +20,11 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay private FillFlowContainer> positionalHitsoundsSettings; [BackgroundDependencyLoader] - private void load(OsuConfigManager config,OsuConfigManager osuConfig) + private void load(OsuConfigManager config, OsuConfigManager osuConfig) { positionalHitsoundsLevel = osuConfig.GetBindable(OsuSetting.PositionalHitsoundsLevel); Children = new Drawable[] - { + { positionalHitsoundsSettings = new FillFlowContainer> { Direction = FillDirection.Vertical, @@ -38,15 +38,15 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay LabelText = AudioSettingsStrings.PositionalLevel, Current = positionalHitsoundsLevel, KeyboardStep = 0.01f, - DisplayAsPercentage = true, - }, + DisplayAsPercentage = true + } } }, new SettingsCheckbox { LabelText = GameplaySettingsStrings.AlwaysPlayFirstComboBreak, Current = config.GetBindable(OsuSetting.AlwaysPlayFirstComboBreak) - }, + } }; } } diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 56e02e3ddd..4ab513bf19 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -22,7 +22,6 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Skinning; using osuTK.Graphics; -using osu.Game.Overlays.Settings.Sections.Gameplay; namespace osu.Game.Rulesets.Objects.Drawables { @@ -126,8 +125,7 @@ namespace osu.Game.Rulesets.Objects.Drawables private readonly BindableList samplesBindable = new BindableList(); private readonly Bindable comboIndexBindable = new Bindable(); - private readonly Bindable positionalHitsoundsLevel = new Bindable(); - private readonly Bindable comboIndexBindable = new Bindable(); + private readonly Bindable positionalHitsoundsLevel = new Bindable(); private readonly Bindable comboIndexWithOffsetsBindable = new Bindable(); protected override bool RequiresChildrenUpdate => true; @@ -534,10 +532,9 @@ namespace osu.Game.Rulesets.Objects.Drawables /// The lookup X position. Generally should be . protected double CalculateSamplePlaybackBalance(double position) { - double returnedvalue; - float balance_adjust_amount = positionalHitsoundsLevel.Value*2; - returnedvalue = balance_adjust_amount *(position - 0.5f ); - + float balanceAdjustAmount = positionalHitsoundsLevel.Value * 2; + double returnedvalue = balanceAdjustAmount * (position - 0.5f); + return returnedvalue; } From e448c28cade0b45e9660d5b76969e9951fbacd76 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 26 Dec 2021 16:26:33 +0900 Subject: [PATCH 036/996] Change `IdleHandler` to not consider mouse movement as active unless focused --- osu.Game/Input/IdleTracker.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/Input/IdleTracker.cs b/osu.Game/Input/IdleTracker.cs index bfe21f650a..4ef676b3b6 100644 --- a/osu.Game/Input/IdleTracker.cs +++ b/osu.Game/Input/IdleTracker.cs @@ -1,11 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Framework.Platform; using osu.Game.Input.Bindings; namespace osu.Game.Input @@ -63,8 +65,14 @@ namespace osu.Game.Input public void OnReleased(KeyBindingReleaseEvent e) => updateLastInteractionTime(); + [Resolved] + private GameHost host { get; set; } + protected override bool Handle(UIEvent e) { + if (!host.IsActive.Value) + return base.Handle(e); + switch (e) { case KeyDownEvent _: From 7c25ce81e16d8cd546c2b6eab562e6cf9acf63ce Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 26 Dec 2021 16:26:47 +0900 Subject: [PATCH 037/996] Further reduce chat poll rate when idle or not visible --- osu.Game/Online/Chat/ChannelManager.cs | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index edaf135e93..4889ad0a3b 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -9,6 +9,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Logging; using osu.Game.Database; +using osu.Game.Input; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; @@ -67,11 +68,33 @@ namespace osu.Game.Online.Chat public readonly BindableBool HighPollRate = new BindableBool(); + private IBindable isIdle; + public ChannelManager() { CurrentChannel.ValueChanged += currentChannelChanged; + } - HighPollRate.BindValueChanged(enabled => TimeBetweenPolls.Value = enabled.NewValue ? 1000 : 6000, true); + [BackgroundDependencyLoader] + private void load(IdleTracker idleTracker) + { + HighPollRate.BindValueChanged(updatePollRate); + + isIdle = idleTracker.IsIdle.GetBoundCopy(); + isIdle.BindValueChanged(updatePollRate, true); + } + + private void updatePollRate(ValueChangedEvent valueChangedEvent) + { + // Polling will eventually be replaced with websocket, but let's avoid doing these background operations as much as possible for now. + // The only loss will be delayed PM/message highlight notifications. + + if (HighPollRate.Value) + TimeBetweenPolls.Value = 1000; + else if (!isIdle.Value) + TimeBetweenPolls.Value = 60000; + else + TimeBetweenPolls.Value = 600000; } /// From 8d927920cb89dae62a6dbee7b4ecf8442be610b7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Dec 2021 13:31:07 +0900 Subject: [PATCH 038/996] Add comment explainng why to block idle updates when host is not active --- osu.Game/Input/IdleTracker.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Input/IdleTracker.cs b/osu.Game/Input/IdleTracker.cs index 4ef676b3b6..e2f13309cf 100644 --- a/osu.Game/Input/IdleTracker.cs +++ b/osu.Game/Input/IdleTracker.cs @@ -70,6 +70,9 @@ namespace osu.Game.Input protected override bool Handle(UIEvent e) { + // Even when not active, `MouseMoveEvent`s will arrive. + // We don't want these to trigger a non-idle state as it's quite often the user interacting + // with other windows while osu! is in the background. if (!host.IsActive.Value) return base.Handle(e); From 327822de5bb5e15098c53fce05ae0eddad495e58 Mon Sep 17 00:00:00 2001 From: pikokr Date: Mon, 27 Dec 2021 19:41:36 +0900 Subject: [PATCH 039/996] Add touchscreen support for osu!mania ruleset --- osu.Game.Rulesets.Mania/UI/Column.cs | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 9d060944cd..df39b5397b 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -136,6 +136,33 @@ namespace osu.Game.Rulesets.Mania.UI { } + // https://github.com/ppy/osu-framework/blob/49c954321c3686628b2c223670363438f88a0341/osu.Framework/Graphics/Drawable.cs#L1513-L1524 + private T findClosestParent() where T : class, IDrawable + { + Drawable cursor = this; + + while ((cursor = cursor.Parent) != null) + { + if (cursor is T match) + return match; + } + + return default; + } + + private ManiaInputManager.RulesetKeyBindingContainer keyBindingManager => findClosestParent(); + + protected override bool OnTouchDown(TouchDownEvent e) + { + keyBindingManager.TriggerPressed(Action.Value); + return base.OnTouchDown(e); + } + + protected override void OnTouchUp(TouchUpEvent e) + { + keyBindingManager.TriggerReleased(Action.Value); + } + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) // This probably shouldn't exist as is, but the columns in the stage are separated by a 1px border => DrawRectangle.Inflate(new Vector2(Stage.COLUMN_SPACING / 2, 0)).Contains(ToLocalSpace(screenSpacePos)); From ef763e9bd7f28ab95b3f5b742c08e84cd223eed9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 27 Dec 2021 10:59:05 +0100 Subject: [PATCH 040/996] Add failing test for storyboard not tracking current track --- .../TestSceneBackgroundScreenDefault.cs | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs b/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs index 844fe7705a..884e74346b 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs @@ -6,18 +6,24 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; +using osu.Framework.Audio.Track; +using osu.Framework.Graphics; using osu.Framework.Graphics.Textures; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Database; +using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; +using osu.Game.Graphics.Sprites; using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Screens; using osu.Game.Screens.Backgrounds; using osu.Game.Skinning; +using osu.Game.Storyboards; +using osu.Game.Storyboards.Drawables; using osu.Game.Tests.Beatmaps; namespace osu.Game.Tests.Visual.Background @@ -129,6 +135,46 @@ namespace osu.Game.Tests.Visual.Background AddAssert("top level background reused existing", () => screen.CheckLastLoadChange() == false); } + [Test] + public void TestBeatmapBackgroundWithStoryboardClockAlwaysUsesCurrentTrack() + { + BackgroundScreenBeatmap nestedScreen = null; + WorkingBeatmap originalWorking = null; + + setSupporter(true); + setSourceMode(BackgroundSource.BeatmapWithStoryboard); + + AddStep("change beatmap", () => originalWorking = Beatmap.Value = createTestWorkingBeatmapWithStoryboard()); + AddAssert("background changed", () => screen.CheckLastLoadChange() == true); + AddUntilStep("wait for beatmap background to be loaded", () => getCurrentBackground()?.GetType() == typeof(BeatmapBackgroundWithStoryboard)); + + AddStep("start music", () => MusicController.Play()); + AddUntilStep("storyboard clock running", () => screen.ChildrenOfType().SingleOrDefault()?.Clock.IsRunning == true); + + // of note, this needs to be a type that doesn't match BackgroundScreenDefault else it is silently not pushed by the background stack. + AddStep("push new background to stack", () => stack.Push(nestedScreen = new BackgroundScreenBeatmap(Beatmap.Value))); + AddUntilStep("wait for screen to load", () => nestedScreen.IsLoaded && nestedScreen.IsCurrentScreen()); + + // we're testing a case where scheduling may be used to avoid issues, so ensure the scheduler is no longer running. + AddUntilStep("wait for top level not alive", () => !screen.IsAlive); + + AddStep("stop music", () => MusicController.Stop()); + AddStep("change beatmap", () => Beatmap.Value = createTestWorkingBeatmapWithStoryboard()); + AddStep("change beatmap back", () => Beatmap.Value = originalWorking); + AddStep("restart music", () => MusicController.Play()); + + AddAssert("top level background hasn't changed yet", () => screen.CheckLastLoadChange() == null); + + AddStep("pop screen back to top level", () => screen.MakeCurrent()); + + AddStep("top level screen is current", () => screen.IsCurrentScreen()); + AddAssert("top level background reused existing", () => screen.CheckLastLoadChange() == false); + AddUntilStep("storyboard clock running", () => screen.ChildrenOfType().Single().Clock.IsRunning); + + AddStep("stop music", () => MusicController.Stop()); + AddStep("restore default beatmap", () => Beatmap.SetDefault()); + } + [Test] public void TestBackgroundTypeSwitch() { @@ -198,6 +244,7 @@ namespace osu.Game.Tests.Visual.Background }); private WorkingBeatmap createTestWorkingBeatmapWithUniqueBackground() => new UniqueBackgroundTestWorkingBeatmap(Audio); + private WorkingBeatmap createTestWorkingBeatmapWithStoryboard() => new TestWorkingBeatmapWithStoryboard(Audio); private class TestBackgroundScreenDefault : BackgroundScreenDefault { @@ -233,6 +280,51 @@ namespace osu.Game.Tests.Visual.Background protected override Texture GetBackground() => new Texture(1, 1); } + private class TestWorkingBeatmapWithStoryboard : TestWorkingBeatmap + { + public TestWorkingBeatmapWithStoryboard(AudioManager audioManager) + : base(new Beatmap(), createStoryboard(), audioManager) + { + } + + protected override Track GetBeatmapTrack() => new TrackVirtual(100000); + + private static Storyboard createStoryboard() + { + var storyboard = new Storyboard(); + storyboard.Layers.Last().Add(new TestStoryboardElement()); + return storyboard; + } + + private class TestStoryboardElement : IStoryboardElementWithDuration + { + public string Path => string.Empty; + public bool IsDrawable => true; + public double StartTime => double.MinValue; + public double EndTime => double.MaxValue; + + public Drawable CreateDrawable() => new DrawableTestStoryboardElement(); + } + + private class DrawableTestStoryboardElement : OsuSpriteText + { + public override bool RemoveWhenNotAlive => false; + + public DrawableTestStoryboardElement() + { + Anchor = Origin = Anchor.Centre; + Font = OsuFont.Default.With(size: 32); + Text = "(not started)"; + } + + protected override void Update() + { + base.Update(); + Text = Time.Current.ToString("N2"); + } + } + } + private void setCustomSkin() { // feign a skin switch. this doesn't do anything except force CurrentSkin to become a LegacySkin. From 9f8ad9f833f5057150297c455fee597dc6bc7db1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 27 Dec 2021 11:28:20 +0100 Subject: [PATCH 041/996] Fix menu background storyboard stopping after track reload --- .../BeatmapBackgroundWithStoryboard.cs | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs b/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs index 6a42e83305..71b1ef2c18 100644 --- a/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs +++ b/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs @@ -1,17 +1,25 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +#nullable enable + using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Timing; using osu.Game.Beatmaps; +using osu.Game.Overlays; using osu.Game.Storyboards.Drawables; namespace osu.Game.Graphics.Backgrounds { public class BeatmapBackgroundWithStoryboard : BeatmapBackground { + private InterpolatingFramedClock storyboardClock = null!; + + [Resolved(CanBeNull = true)] + private MusicController? musicController { get; set; } + public BeatmapBackgroundWithStoryboard(WorkingBeatmap beatmap, string fallbackTextureName = "Backgrounds/bg1") : base(beatmap, fallbackTextureName) { @@ -30,8 +38,33 @@ namespace osu.Game.Graphics.Backgrounds { RelativeSizeAxes = Axes.Both, Volume = { Value = 0 }, - Child = new DrawableStoryboard(Beatmap.Storyboard) { Clock = new InterpolatingFramedClock(Beatmap.Track) } + Child = new DrawableStoryboard(Beatmap.Storyboard) { Clock = storyboardClock = new InterpolatingFramedClock(Beatmap.Track) } }, AddInternal); } + + protected override void LoadComplete() + { + base.LoadComplete(); + if (musicController != null) + musicController.TrackChanged += onTrackChanged; + } + + private void onTrackChanged(WorkingBeatmap newBeatmap, TrackChangeDirection _) + { + if (newBeatmap != Beatmap) + return; + + // `MusicController` will sometimes reload the track, even when the working beatmap technically hasn't changed. + // ensure that the storyboard's clock is always using the latest track instance. + storyboardClock.ChangeSource(newBeatmap.Track); + storyboardClock.ProcessFrame(); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + if (musicController != null) + musicController.TrackChanged -= onTrackChanged; + } } } From 58994b790c8e8a3c85fa5d82edc2835b648b8843 Mon Sep 17 00:00:00 2001 From: pikokr Date: Mon, 27 Dec 2021 21:20:52 +0900 Subject: [PATCH 042/996] Get key binding container once instead of getting on every touch --- osu.Game.Rulesets.Mania/ManiaInputManager.cs | 5 +++++ osu.Game.Rulesets.Mania/UI/Column.cs | 21 ++++++-------------- osu.Game/Rulesets/UI/DrawableRuleset.cs | 1 + 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Mania/ManiaInputManager.cs b/osu.Game.Rulesets.Mania/ManiaInputManager.cs index 186fc4b15d..bf94735077 100644 --- a/osu.Game.Rulesets.Mania/ManiaInputManager.cs +++ b/osu.Game.Rulesets.Mania/ManiaInputManager.cs @@ -13,6 +13,11 @@ namespace osu.Game.Rulesets.Mania : base(ruleset, variant, SimultaneousBindingMode.Unique) { } + + public RulesetKeyBindingContainer GetKeyBindingContainer() + { + return (RulesetKeyBindingContainer)KeyBindingContainer; + } } public enum ManiaAction diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index df39b5397b..6be36619c7 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -136,31 +136,22 @@ namespace osu.Game.Rulesets.Mania.UI { } - // https://github.com/ppy/osu-framework/blob/49c954321c3686628b2c223670363438f88a0341/osu.Framework/Graphics/Drawable.cs#L1513-L1524 - private T findClosestParent() where T : class, IDrawable + private ManiaInputManager.RulesetKeyBindingContainer keyBindingContainer { get; set; } + + private ManiaInputManager.RulesetKeyBindingContainer getKeyBindingManager() { - Drawable cursor = this; - - while ((cursor = cursor.Parent) != null) - { - if (cursor is T match) - return match; - } - - return default; + return keyBindingContainer ??= ((ManiaInputManager)GetContainingInputManager()).GetKeyBindingContainer(); } - private ManiaInputManager.RulesetKeyBindingContainer keyBindingManager => findClosestParent(); - protected override bool OnTouchDown(TouchDownEvent e) { - keyBindingManager.TriggerPressed(Action.Value); + getKeyBindingManager().TriggerPressed(Action.Value); return base.OnTouchDown(e); } protected override void OnTouchUp(TouchUpEvent e) { - keyBindingManager.TriggerReleased(Action.Value); + getKeyBindingManager().TriggerReleased(Action.Value); } public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 29559f5036..30012784a3 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -50,6 +50,7 @@ namespace osu.Game.Rulesets.UI /// /// The key conversion input manager for this DrawableRuleset. /// + [Cached] public PassThroughInputManager KeyBindingInputManager; public override double GameplayStartTime => Objects.FirstOrDefault()?.StartTime - 2000 ?? 0; From 568cab68086792a3ed9cf863f63f25331ea13ae4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 27 Dec 2021 21:13:04 +0100 Subject: [PATCH 043/996] Add clarification comment about explicit `ProcessFrame()` call --- .../Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs b/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs index 71b1ef2c18..c5c8a25f4a 100644 --- a/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs +++ b/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs @@ -57,6 +57,9 @@ namespace osu.Game.Graphics.Backgrounds // `MusicController` will sometimes reload the track, even when the working beatmap technically hasn't changed. // ensure that the storyboard's clock is always using the latest track instance. storyboardClock.ChangeSource(newBeatmap.Track); + // more often than not, the previous source track's time will be in the future relative to the new source track. + // explicitly process a single frame so that `InterpolatingFramedClock`'s interpolation logic is bypassed + // and the storyboard clock is correctly rewound to the source track's time exactly. storyboardClock.ProcessFrame(); } From d62930500298d23419b2f37b0376881f0e6bb573 Mon Sep 17 00:00:00 2001 From: pikokr Date: Tue, 28 Dec 2021 21:47:58 +0900 Subject: [PATCH 044/996] Remove `Cached` attribute from `DrawableRuleset.KeyBindingInputManager` --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 30012784a3..29559f5036 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -50,7 +50,6 @@ namespace osu.Game.Rulesets.UI /// /// The key conversion input manager for this DrawableRuleset. /// - [Cached] public PassThroughInputManager KeyBindingInputManager; public override double GameplayStartTime => Objects.FirstOrDefault()?.StartTime - 2000 ?? 0; From 59b4aea5f9dc22a3cf543bddae589e462a8be383 Mon Sep 17 00:00:00 2001 From: pikokr Date: Tue, 28 Dec 2021 21:52:46 +0900 Subject: [PATCH 045/996] Make method and property name to match class name --- osu.Game.Rulesets.Mania/UI/Column.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 6be36619c7..36473aa900 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -136,22 +136,22 @@ namespace osu.Game.Rulesets.Mania.UI { } - private ManiaInputManager.RulesetKeyBindingContainer keyBindingContainer { get; set; } + private ManiaInputManager.RulesetKeyBindingContainer rulesetKeyBindingContainer { get; set; } - private ManiaInputManager.RulesetKeyBindingContainer getKeyBindingManager() + private ManiaInputManager.RulesetKeyBindingContainer getRulesetKeyBindingContainer() { - return keyBindingContainer ??= ((ManiaInputManager)GetContainingInputManager()).GetKeyBindingContainer(); + return rulesetKeyBindingContainer ??= ((ManiaInputManager)GetContainingInputManager()).GetKeyBindingContainer(); } protected override bool OnTouchDown(TouchDownEvent e) { - getKeyBindingManager().TriggerPressed(Action.Value); + getRulesetKeyBindingContainer().TriggerPressed(Action.Value); return base.OnTouchDown(e); } protected override void OnTouchUp(TouchUpEvent e) { - getKeyBindingManager().TriggerReleased(Action.Value); + getRulesetKeyBindingContainer().TriggerReleased(Action.Value); } public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) From 62d6bb8c2e9c8a6e10a318599a7ad04974d55e76 Mon Sep 17 00:00:00 2001 From: pikokr Date: Tue, 28 Dec 2021 22:35:45 +0900 Subject: [PATCH 046/996] Trigger touch on click key area --- osu.Game.Rulesets.Mania/UI/Column.cs | 18 ------------- .../UI/Components/DefaultKeyArea.cs | 25 +++++++++++++++++-- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 36473aa900..9d060944cd 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -136,24 +136,6 @@ namespace osu.Game.Rulesets.Mania.UI { } - private ManiaInputManager.RulesetKeyBindingContainer rulesetKeyBindingContainer { get; set; } - - private ManiaInputManager.RulesetKeyBindingContainer getRulesetKeyBindingContainer() - { - return rulesetKeyBindingContainer ??= ((ManiaInputManager)GetContainingInputManager()).GetKeyBindingContainer(); - } - - protected override bool OnTouchDown(TouchDownEvent e) - { - getRulesetKeyBindingContainer().TriggerPressed(Action.Value); - return base.OnTouchDown(e); - } - - protected override void OnTouchUp(TouchUpEvent e) - { - getRulesetKeyBindingContainer().TriggerReleased(Action.Value); - } - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) // This probably shouldn't exist as is, but the columns in the stage are separated by a 1px border => DrawRectangle.Inflate(new Vector2(Stage.COLUMN_SPACING / 2, 0)).Contains(ToLocalSpace(screenSpacePos)); diff --git a/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs b/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs index 267ed1f5f4..4e0bb71514 100644 --- a/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs +++ b/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs @@ -36,10 +36,31 @@ namespace osu.Game.Rulesets.Mania.UI.Components RelativeSizeAxes = Axes.Both; } + public class ChildContainer : Container + { + private ManiaInputManager.RulesetKeyBindingContainer keyBindingContainer { get; set; } + + private ManiaInputManager.RulesetKeyBindingContainer getKeyBindingContainer() + { + return keyBindingContainer ??= ((ManiaInputManager)GetContainingInputManager()).GetKeyBindingContainer(); + } + + protected override bool OnTouchDown(TouchDownEvent e) + { + getKeyBindingContainer().TriggerPressed(((DefaultKeyArea)Parent).column.Action.Value); + return base.OnTouchDown(e); + } + + protected override void OnTouchUp(TouchUpEvent e) + { + getKeyBindingContainer().TriggerReleased(((DefaultKeyArea)Parent).column.Action.Value); + } + } + [BackgroundDependencyLoader] private void load(IScrollingInfo scrollingInfo) { - InternalChild = directionContainer = new Container + InternalChild = directionContainer = new ChildContainer { RelativeSizeAxes = Axes.X, Height = Stage.HIT_TARGET_POSITION, @@ -69,7 +90,7 @@ namespace osu.Game.Rulesets.Mania.UI.Components AlwaysPresent = true } } - } + }, } }; From dec1f317494c73bbb4313d7be2f1cadb5fde488c Mon Sep 17 00:00:00 2001 From: pikokr Date: Tue, 28 Dec 2021 22:43:07 +0900 Subject: [PATCH 047/996] Make `KeyBindingContainer` public --- osu.Game.Rulesets.Mania/ManiaInputManager.cs | 5 ----- osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs | 2 +- osu.Game/Rulesets/UI/RulesetInputManager.cs | 2 +- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Mania/ManiaInputManager.cs b/osu.Game.Rulesets.Mania/ManiaInputManager.cs index bf94735077..186fc4b15d 100644 --- a/osu.Game.Rulesets.Mania/ManiaInputManager.cs +++ b/osu.Game.Rulesets.Mania/ManiaInputManager.cs @@ -13,11 +13,6 @@ namespace osu.Game.Rulesets.Mania : base(ruleset, variant, SimultaneousBindingMode.Unique) { } - - public RulesetKeyBindingContainer GetKeyBindingContainer() - { - return (RulesetKeyBindingContainer)KeyBindingContainer; - } } public enum ManiaAction diff --git a/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs b/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs index 4e0bb71514..b3a2a97bf7 100644 --- a/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs +++ b/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Mania.UI.Components private ManiaInputManager.RulesetKeyBindingContainer getKeyBindingContainer() { - return keyBindingContainer ??= ((ManiaInputManager)GetContainingInputManager()).GetKeyBindingContainer(); + return keyBindingContainer ??= (ManiaInputManager.RulesetKeyBindingContainer)((ManiaInputManager)GetContainingInputManager()).KeyBindingContainer; } protected override bool OnTouchDown(TouchDownEvent e) diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 6564ff9e23..6ef99f8aae 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.UI protected override InputState CreateInitialState() => new RulesetInputManagerInputState(base.CreateInitialState()); - protected readonly KeyBindingContainer KeyBindingContainer; + public readonly KeyBindingContainer KeyBindingContainer; protected override Container Content => content; From c22a07d9fc19f3598e6d3727c939c2f9443111bc Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 28 Dec 2021 11:21:59 -0800 Subject: [PATCH 048/996] Bump identifier typo inspection and fix remaining identifier names --- osu.Desktop/Windows/WindowsKey.cs | 3 +++ .../Skinning/OsuSkinConfiguration.cs | 2 ++ .../Online/Chat/MessageNotifierTest.cs | 2 +- .../Requests/GetUserRecentActivitiesRequest.cs | 2 ++ osu.sln.DotSettings | 16 +++++++++++++++- 5 files changed, 23 insertions(+), 2 deletions(-) diff --git a/osu.Desktop/Windows/WindowsKey.cs b/osu.Desktop/Windows/WindowsKey.cs index f19d741107..c9d94cd05c 100644 --- a/osu.Desktop/Windows/WindowsKey.cs +++ b/osu.Desktop/Windows/WindowsKey.cs @@ -14,6 +14,8 @@ namespace osu.Desktop.Windows private const int wh_keyboard_ll = 13; private const int wm_keydown = 256; + + // ReSharper disable once IdentifierTypo private const int wm_syskeyup = 261; //Resharper disable once NotAccessedField.Local @@ -69,6 +71,7 @@ namespace osu.Desktop.Windows } [DllImport(@"user32.dll", EntryPoint = @"SetWindowsHookExA")] + // ReSharper disable once IdentifierTypo private static extern IntPtr setWindowsHookEx(int idHook, LowLevelKeyboardProcDelegate lpfn, IntPtr hMod, int dwThreadId); [DllImport(@"user32.dll", EntryPoint = @"UnhookWindowsHookEx")] diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs b/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs index 6953e66b5c..7b9cf8e1d1 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs @@ -12,6 +12,8 @@ namespace osu.Game.Rulesets.Osu.Skinning CursorExpand, CursorRotate, HitCircleOverlayAboveNumber, + + // ReSharper disable once IdentifierTypo HitCircleOverlayAboveNumer, // Some old skins will have this typo SpinnerFrequencyModulate, SpinnerNoBlink diff --git a/osu.Game.Tests/Online/Chat/MessageNotifierTest.cs b/osu.Game.Tests/Online/Chat/MessageNotifierTest.cs index 2ec5b778d1..855de9b656 100644 --- a/osu.Game.Tests/Online/Chat/MessageNotifierTest.cs +++ b/osu.Game.Tests/Online/Chat/MessageNotifierTest.cs @@ -46,7 +46,7 @@ namespace osu.Game.Tests.Online.Chat } [Test] - public void TestContainsUsernameBetweenInterpunction() + public void TestContainsUsernameBetweenPunctuation() { Assert.IsTrue(MessageNotifier.CheckContainsUsername("Hello 'test'-message", "Test")); } diff --git a/osu.Game/Online/API/Requests/GetUserRecentActivitiesRequest.cs b/osu.Game/Online/API/Requests/GetUserRecentActivitiesRequest.cs index 123624d333..f2fa51bde7 100644 --- a/osu.Game/Online/API/Requests/GetUserRecentActivitiesRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserRecentActivitiesRequest.cs @@ -22,6 +22,8 @@ namespace osu.Game.Online.API.Requests public enum RecentActivityType { Achievement, + + // ReSharper disable once IdentifierTypo BeatmapPlaycount, BeatmapsetApprove, BeatmapsetDelete, diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index 90bf4f09eb..5f3a38557d 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -90,7 +90,7 @@ WARNING HINT DO_NOT_SHOW - HINT + WARNING HINT ERROR WARNING @@ -928,11 +928,19 @@ private void load() True True True + True + True + True True + True + True True True True + True True + True + True True True True @@ -949,8 +957,11 @@ private void load() True True True + True + True True True + True True True True @@ -965,6 +976,9 @@ private void load() True True True + True + True + True True True True From 471eea750af41ec00b34fe4a22f928b2524d87e8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 29 Dec 2021 21:17:44 +0900 Subject: [PATCH 049/996] Fix calling `SkinEditorOverlay.Show` before the overlay is loaded causing an exception As seen at https://github.com/ppy/osu/runs/4652969942?check_suite_focus=true. --- osu.Game/Skinning/Editor/SkinEditorOverlay.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs index 340c6ed931..6699e0f658 100644 --- a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs +++ b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs @@ -73,9 +73,13 @@ namespace osu.Game.Skinning.Editor skinEditor = new SkinEditor(target); skinEditor.State.BindValueChanged(editorVisibilityChanged); - Debug.Assert(skinEditor != null); - - LoadComponentAsync(skinEditor, AddInternal); + // Schedule ensures that if `Show` is called before this overlay is loaded, + // it will not throw (LoadComponentAsync requires the load target to be in a loaded state). + Schedule(() => + { + Debug.Assert(skinEditor != null); + LoadComponentAsync(skinEditor, AddInternal); + }); } else skinEditor.Show(); From b1a444180fda72619ae33667411d28989d674cda Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 29 Dec 2021 21:46:34 +0900 Subject: [PATCH 050/996] Fix `Show` then `Reset` potentially resulting in incorrect load target --- osu.Game/Skinning/Editor/SkinEditorOverlay.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs index 6699e0f658..ebf0a1214e 100644 --- a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs +++ b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs @@ -70,16 +70,18 @@ namespace osu.Game.Skinning.Editor // base call intentionally omitted. if (skinEditor == null) { - skinEditor = new SkinEditor(target); - skinEditor.State.BindValueChanged(editorVisibilityChanged); + var editor = new SkinEditor(target); + editor.State.BindValueChanged(editorVisibilityChanged); // Schedule ensures that if `Show` is called before this overlay is loaded, // it will not throw (LoadComponentAsync requires the load target to be in a loaded state). Schedule(() => { - Debug.Assert(skinEditor != null); - LoadComponentAsync(skinEditor, AddInternal); + Debug.Assert(editor != null); + LoadComponentAsync(editor, AddInternal); }); + + skinEditor = editor; } else skinEditor.Show(); From cdc148f78e08fe1929b4e1b8ddb59f8b683b194e Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Wed, 29 Dec 2021 14:07:36 -0800 Subject: [PATCH 051/996] Increase scope of identifier typo disables on special cases --- osu.Desktop/Windows/WindowsKey.cs | 4 ++-- .../Online/API/Requests/GetUserRecentActivitiesRequest.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Desktop/Windows/WindowsKey.cs b/osu.Desktop/Windows/WindowsKey.cs index c9d94cd05c..ec4d3f5d74 100644 --- a/osu.Desktop/Windows/WindowsKey.cs +++ b/osu.Desktop/Windows/WindowsKey.cs @@ -2,10 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; namespace osu.Desktop.Windows { + [SuppressMessage("ReSharper", "IdentifierTypo")] internal class WindowsKey { private delegate int LowLevelKeyboardProcDelegate(int nCode, int wParam, ref KdDllHookStruct lParam); @@ -15,7 +17,6 @@ namespace osu.Desktop.Windows private const int wh_keyboard_ll = 13; private const int wm_keydown = 256; - // ReSharper disable once IdentifierTypo private const int wm_syskeyup = 261; //Resharper disable once NotAccessedField.Local @@ -71,7 +72,6 @@ namespace osu.Desktop.Windows } [DllImport(@"user32.dll", EntryPoint = @"SetWindowsHookExA")] - // ReSharper disable once IdentifierTypo private static extern IntPtr setWindowsHookEx(int idHook, LowLevelKeyboardProcDelegate lpfn, IntPtr hMod, int dwThreadId); [DllImport(@"user32.dll", EntryPoint = @"UnhookWindowsHookEx")] diff --git a/osu.Game/Online/API/Requests/GetUserRecentActivitiesRequest.cs b/osu.Game/Online/API/Requests/GetUserRecentActivitiesRequest.cs index f2fa51bde7..62b27b31b4 100644 --- a/osu.Game/Online/API/Requests/GetUserRecentActivitiesRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserRecentActivitiesRequest.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Online.API.Requests @@ -19,11 +20,10 @@ namespace osu.Game.Online.API.Requests protected override string Target => $"users/{userId}/recent_activity"; } + [SuppressMessage("ReSharper", "IdentifierTypo")] public enum RecentActivityType { Achievement, - - // ReSharper disable once IdentifierTypo BeatmapPlaycount, BeatmapsetApprove, BeatmapsetDelete, From ef49f2ed0e4835498d3d6b9b92431a59977d0700 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Dec 2021 16:02:08 +0900 Subject: [PATCH 052/996] Add extra extra safety against attempting to load a previously expired editor --- osu.Game/Skinning/Editor/SkinEditorOverlay.cs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs index ebf0a1214e..87da67dae0 100644 --- a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs +++ b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs @@ -73,15 +73,23 @@ namespace osu.Game.Skinning.Editor var editor = new SkinEditor(target); editor.State.BindValueChanged(editorVisibilityChanged); + skinEditor = editor; + // Schedule ensures that if `Show` is called before this overlay is loaded, // it will not throw (LoadComponentAsync requires the load target to be in a loaded state). Schedule(() => { - Debug.Assert(editor != null); - LoadComponentAsync(editor, AddInternal); - }); + if (editor != skinEditor) + return; - skinEditor = editor; + LoadComponentAsync(editor, _ => + { + if (editor != skinEditor) + return; + + AddInternal(editor); + }); + }); } else skinEditor.Show(); From 089b756f932b1b59a4b66380553a9d0254d8a121 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Dec 2021 16:03:16 +0900 Subject: [PATCH 053/996] Invert logic to make reading easier --- osu.Game/Skinning/Editor/SkinEditorOverlay.cs | 39 ++++++++++--------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs index 87da67dae0..86854ab6ff 100644 --- a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs +++ b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Diagnostics; using JetBrains.Annotations; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -67,32 +66,34 @@ namespace osu.Game.Skinning.Editor public override void Show() { - // base call intentionally omitted. - if (skinEditor == null) + // base call intentionally omitted as we have custom behaviour. + + if (skinEditor != null) { - var editor = new SkinEditor(target); - editor.State.BindValueChanged(editorVisibilityChanged); + skinEditor.Show(); + return; + } - skinEditor = editor; + var editor = new SkinEditor(target); + editor.State.BindValueChanged(editorVisibilityChanged); - // Schedule ensures that if `Show` is called before this overlay is loaded, - // it will not throw (LoadComponentAsync requires the load target to be in a loaded state). - Schedule(() => + skinEditor = editor; + + // Schedule ensures that if `Show` is called before this overlay is loaded, + // it will not throw (LoadComponentAsync requires the load target to be in a loaded state). + Schedule(() => + { + if (editor != skinEditor) + return; + + LoadComponentAsync(editor, _ => { if (editor != skinEditor) return; - LoadComponentAsync(editor, _ => - { - if (editor != skinEditor) - return; - - AddInternal(editor); - }); + AddInternal(editor); }); - } - else - skinEditor.Show(); + }); } private void editorVisibilityChanged(ValueChangedEvent visibility) From 4cb8272d14369fb7a129d5ec3997b92a14e14867 Mon Sep 17 00:00:00 2001 From: pikokr Date: Thu, 30 Dec 2021 17:37:14 +0900 Subject: [PATCH 054/996] Column Touch area & highlighting on start --- osu.Game.Rulesets.Mania/UI/Column.cs | 60 ++++++++++++++++++- .../UI/Components/DefaultKeyArea.cs | 23 +------ 2 files changed, 60 insertions(+), 23 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 9d060944cd..1e07c84d9f 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -44,6 +44,63 @@ namespace osu.Game.Rulesets.Mania.UI private readonly GameplaySampleTriggerSource sampleTriggerSource; + public class ColumnTouchInputArea : Container + { + private Column column => (Column)Parent; + + private Container hintContainer; + + public ColumnTouchInputArea() + { + RelativeSizeAxes = Axes.X; + Anchor = Anchor.BottomCentre; + Origin = Anchor.BottomCentre; + Height = 100; + InternalChild = hintContainer = new Container + { + RelativeSizeAxes = Axes.Both, + BorderColour = Color4.Red, + BorderThickness = 5, + Masking = true, + }; + } + + protected override void LoadComplete() + { + hintContainer.Delay(1000).FadeOutFromOne(500, Easing.OutSine); + } + + private ManiaInputManager.RulesetKeyBindingContainer keyBindingContainer { get; set; } + + private ManiaInputManager.RulesetKeyBindingContainer getKeyBindingContainer() + { + return keyBindingContainer ??= (ManiaInputManager.RulesetKeyBindingContainer)((ManiaInputManager)GetContainingInputManager()).KeyBindingContainer; + } + + protected override bool OnTouchDown(TouchDownEvent e) + { + getKeyBindingContainer().TriggerPressed(column.Action.Value); + return base.OnTouchDown(e); + } + + protected override bool OnMouseDown(MouseDownEvent e) + { + getKeyBindingContainer().TriggerPressed(column.Action.Value); + + return base.OnMouseDown(e); + } + + protected override void OnMouseUp(MouseUpEvent e) + { + getKeyBindingContainer().TriggerReleased(column.Action.Value); + } + + protected override void OnTouchUp(TouchUpEvent e) + { + getKeyBindingContainer().TriggerReleased(column.Action.Value); + } + } + public Column(int index) { Index = index; @@ -68,7 +125,8 @@ namespace osu.Game.Rulesets.Mania.UI RelativeSizeAxes = Axes.Both }, background, - TopLevelContainer = new Container { RelativeSizeAxes = Axes.Both } + TopLevelContainer = new Container { RelativeSizeAxes = Axes.Both }, + new ColumnTouchInputArea() }; hitPolicy = new OrderedHitPolicy(HitObjectContainer); diff --git a/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs b/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs index b3a2a97bf7..15018b464f 100644 --- a/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs +++ b/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs @@ -36,31 +36,10 @@ namespace osu.Game.Rulesets.Mania.UI.Components RelativeSizeAxes = Axes.Both; } - public class ChildContainer : Container - { - private ManiaInputManager.RulesetKeyBindingContainer keyBindingContainer { get; set; } - - private ManiaInputManager.RulesetKeyBindingContainer getKeyBindingContainer() - { - return keyBindingContainer ??= (ManiaInputManager.RulesetKeyBindingContainer)((ManiaInputManager)GetContainingInputManager()).KeyBindingContainer; - } - - protected override bool OnTouchDown(TouchDownEvent e) - { - getKeyBindingContainer().TriggerPressed(((DefaultKeyArea)Parent).column.Action.Value); - return base.OnTouchDown(e); - } - - protected override void OnTouchUp(TouchUpEvent e) - { - getKeyBindingContainer().TriggerReleased(((DefaultKeyArea)Parent).column.Action.Value); - } - } - [BackgroundDependencyLoader] private void load(IScrollingInfo scrollingInfo) { - InternalChild = directionContainer = new ChildContainer + InternalChild = directionContainer = new Container { RelativeSizeAxes = Axes.X, Height = Stage.HIT_TARGET_POSITION, From 408e8d57109ed44498601f02c829d6b4faa8c05d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Dec 2021 22:21:37 +0900 Subject: [PATCH 055/996] Fix null reference causing crash in `KiaiFlashingDrawable` Can occur if there is no fallback graphics available. Previously would work as it was only setting the `Texture`. As reported in https://github.com/ppy/osu/discussions/16281. --- .../Skinning/Legacy/KiaiFlashingDrawable.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/KiaiFlashingDrawable.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/KiaiFlashingDrawable.cs index cd1d05c985..4ee28d05b5 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/KiaiFlashingDrawable.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/KiaiFlashingDrawable.cs @@ -7,6 +7,8 @@ using osu.Framework.Graphics; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.Containers; +#nullable enable + namespace osu.Game.Rulesets.Osu.Skinning.Legacy { internal class KiaiFlashingDrawable : BeatSyncedContainer @@ -15,18 +17,18 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy private const float flash_opacity = 0.3f; - public KiaiFlashingDrawable(Func creationFunc) + public KiaiFlashingDrawable(Func creationFunc) { AutoSizeAxes = Axes.Both; Children = new[] { - creationFunc.Invoke().With(d => + (creationFunc.Invoke() ?? Empty()).With(d => { d.Anchor = Anchor.Centre; d.Origin = Anchor.Centre; }), - flashingDrawable = creationFunc.Invoke().With(d => + flashingDrawable = (creationFunc.Invoke() ?? Empty()).With(d => { d.Anchor = Anchor.Centre; d.Origin = Anchor.Centre; From f53a675ca3e9c04c1ba7378cbd8e1cab72e0e7a8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Dec 2021 23:35:18 +0900 Subject: [PATCH 056/996] Fix `TestSceneMultiSpectatorLeaderboard` not waiting for user population --- .../Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs index a61e505970..543e6a91d0 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs @@ -50,6 +50,7 @@ namespace osu.Game.Tests.Visual.Multiplayer }); AddUntilStep("wait for load", () => leaderboard.IsLoaded); + AddUntilStep("wait for user population", () => leaderboard.ChildrenOfType().Count() == 2); AddStep("add clock sources", () => { From e38e1bb1d7edd5a20ff401309cc1e38eb2eb199d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Dec 2021 13:22:49 +0900 Subject: [PATCH 057/996] Enable a couple of missing async related inspections --- osu.sln.DotSettings | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index 90bf4f09eb..44d75f265c 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -19,6 +19,7 @@ HINT DO_NOT_SHOW WARNING + WARNING HINT HINT WARNING @@ -231,6 +232,7 @@ HINT DO_NOT_SHOW WARNING + WARNING WARNING WARNING WARNING From 670a30b64bdf1f315898c7a931c0dccc3da7af70 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Dec 2021 23:08:05 +0900 Subject: [PATCH 058/996] Remove usage of `.Result` in `ArchiveReader` --- osu.Game/IO/Archives/ArchiveReader.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/osu.Game/IO/Archives/ArchiveReader.cs b/osu.Game/IO/Archives/ArchiveReader.cs index f787534e2d..1d8da16c72 100644 --- a/osu.Game/IO/Archives/ArchiveReader.cs +++ b/osu.Game/IO/Archives/ArchiveReader.cs @@ -32,7 +32,18 @@ namespace osu.Game.IO.Archives public abstract IEnumerable Filenames { get; } - public virtual byte[] Get(string name) => GetAsync(name).Result; + public virtual byte[] Get(string name) + { + using (Stream input = GetStream(name)) + { + if (input == null) + return null; + + byte[] buffer = new byte[input.Length]; + input.Read(buffer); + return buffer; + } + } public async Task GetAsync(string name, CancellationToken cancellationToken = default) { From 1262e76a584c4b1e748f9309ad1efcfaf6e8ee55 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 31 Dec 2021 23:18:03 +0900 Subject: [PATCH 059/996] Fix test failure due to missing DI cached `IdleTracker` --- osu.Game/Online/Chat/ChannelManager.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 4889ad0a3b..e9e145e2ab 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -68,20 +68,21 @@ namespace osu.Game.Online.Chat public readonly BindableBool HighPollRate = new BindableBool(); - private IBindable isIdle; + private readonly IBindable isIdle = new BindableBool(); public ChannelManager() { CurrentChannel.ValueChanged += currentChannelChanged; } - [BackgroundDependencyLoader] + [BackgroundDependencyLoader(permitNulls: true)] private void load(IdleTracker idleTracker) { HighPollRate.BindValueChanged(updatePollRate); - - isIdle = idleTracker.IsIdle.GetBoundCopy(); isIdle.BindValueChanged(updatePollRate, true); + + if (idleTracker != null) + isIdle.BindTo(idleTracker.IsIdle); } private void updatePollRate(ValueChangedEvent valueChangedEvent) From 675bdd32138fb222889ab8a5d4590283f910c929 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 1 Jan 2022 12:08:49 +0900 Subject: [PATCH 060/996] Fix `MultiplayerMatchSubScreen` mutating mods outside of bindable lease As seen at https://github.com/peppy/osu/runs/4674501626?check_suite_focus=true. --- osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs | 2 +- .../Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index a560d85b7d..a0e7e8de87 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -381,7 +381,7 @@ namespace osu.Game.Screens.OnlinePlay.Match protected virtual void UpdateMods() { - if (SelectedItem.Value == null) + if (SelectedItem.Value == null || !this.IsCurrentScreen()) return; Mods.Value = UserMods.Value.Concat(SelectedItem.Value.RequiredMods).ToList(); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index c4dd200614..4bd68f2034 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -241,7 +241,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer protected override void UpdateMods() { - if (SelectedItem.Value == null || client.LocalUser == null) + if (SelectedItem.Value == null || client.LocalUser == null || !this.IsCurrentScreen()) return; // update local mods based on room's reported status for the local user (omitting the base call implementation). From 04d8fd3a58f59909b5ee1800a61f41182c7d0197 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 1 Jan 2022 15:31:50 +0900 Subject: [PATCH 061/996] Improve reliability of `TestStoryboardSkipOutro` Aims to resolve failures as seen at https://github.com/peppy/osu/runs/4677353822?check_suite_focus=true. Have run quite a lot locally with no failures (while removing the skip step 100% fails). --- osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs index 48a97d54f7..69798dcb82 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs @@ -53,8 +53,8 @@ namespace osu.Game.Tests.Visual.Gameplay CreateTest(null); AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value); AddStep("skip outro", () => InputManager.Key(osuTK.Input.Key.Space)); + AddAssert("player is no longer current screen", () => !Player.IsCurrentScreen()); AddUntilStep("wait for score shown", () => Player.IsScoreShown); - AddUntilStep("time less than storyboard duration", () => Player.GameplayClockContainer.GameplayClock.CurrentTime < currentStoryboardDuration); } [Test] From 5dd024aab7d8add3a143725b1a21beab946da70e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 3 Jan 2022 12:46:02 +0900 Subject: [PATCH 062/996] Remove outdated settings migration --- osu.Game/Configuration/OsuConfigManager.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 616e749c9b..6efbaa4d36 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -174,11 +174,6 @@ namespace osu.Game.Configuration if (!int.TryParse(pieces[1], out int monthDay)) return; int combined = (year * 10000) + monthDay; - - if (combined < 20210413) - { - SetValue(OsuSetting.EditorWaveformOpacity, 0.25f); - } } public override TrackedSettings CreateTrackedSettings() From 623d6d6d2d0635b55da3d99d499b0ef5a085b618 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 3 Jan 2022 12:46:20 +0900 Subject: [PATCH 063/996] Add migration of positional hitsounds setting to new level based setting --- osu.Game/Configuration/OsuConfigManager.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 6efbaa4d36..07d2026c65 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -98,6 +98,7 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.MenuParallax, true); // Gameplay + SetDefault(OsuSetting.PositionalHitsounds, true); // replaced by level setting below, can be removed 20220703. SetDefault(OsuSetting.PositionalHitsoundsLevel, 0.2f, 0, 1); SetDefault(OsuSetting.DimLevel, 0.8, 0, 1, 0.01); SetDefault(OsuSetting.BlurLevel, 0, 0, 1, 0.01); @@ -174,6 +175,13 @@ namespace osu.Game.Configuration if (!int.TryParse(pieces[1], out int monthDay)) return; int combined = (year * 10000) + monthDay; + + if (combined < 20220103) + { + var positionalHitsoundsEnabled = GetBindable(OsuSetting.PositionalHitsounds); + if (!positionalHitsoundsEnabled.Value) + SetValue(OsuSetting.PositionalHitsoundsLevel, 0); + } } public override TrackedSettings CreateTrackedSettings() @@ -250,8 +258,9 @@ namespace osu.Game.Configuration BlurLevel, LightenDuringBreaks, ShowStoryboard, - PositionalHitsoundsLevel, KeyOverlay, + PositionalHitsounds, + PositionalHitsoundsLevel, AlwaysPlayFirstComboBreak, FloatingComments, HUDVisibilityMode, From 6356180b6abd5a11ab32508ad3dcd2984c561aee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 3 Jan 2022 12:53:58 +0900 Subject: [PATCH 064/996] Remove unnecessary code and fix double nesting causing filtering to not work --- .../Sections/Gameplay/AudioSettings.cs | 28 ++++--------------- 1 file changed, 6 insertions(+), 22 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/AudioSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/AudioSettings.cs index 4238ad1605..5029c6a617 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/AudioSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/AudioSettings.cs @@ -2,12 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Localisation; using osu.Game.Configuration; using osu.Game.Localisation; -using osu.Framework.Graphics.Containers; namespace osu.Game.Overlays.Settings.Sections.Gameplay { @@ -15,32 +13,18 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay { protected override LocalisableString Header => GameplaySettingsStrings.AudioHeader; - private Bindable positionalHitsoundsLevel; - - private FillFlowContainer> positionalHitsoundsSettings; - [BackgroundDependencyLoader] private void load(OsuConfigManager config, OsuConfigManager osuConfig) { - positionalHitsoundsLevel = osuConfig.GetBindable(OsuSetting.PositionalHitsoundsLevel); Children = new Drawable[] { - positionalHitsoundsSettings = new FillFlowContainer> + new SettingsSlider { - Direction = FillDirection.Vertical, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Masking = true, - Children = new[] - { - new SettingsSlider - { - LabelText = AudioSettingsStrings.PositionalLevel, - Current = positionalHitsoundsLevel, - KeyboardStep = 0.01f, - DisplayAsPercentage = true - } - } + LabelText = AudioSettingsStrings.PositionalLevel, + Keywords = new[] { @"positional", @"balance" }, + Current = osuConfig.GetBindable(OsuSetting.PositionalHitsoundsLevel), + KeyboardStep = 0.01f, + DisplayAsPercentage = true }, new SettingsCheckbox { From b9851b278d8711532087714bb9612d7e3c8e0b95 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 3 Jan 2022 13:18:27 +0900 Subject: [PATCH 065/996] Add padding to the bottom of the beatmap listing overlay to avoid hovered panels exceeding visible bounds Closes https://github.com/ppy/osu/issues/16120. --- .../Drawables/Cards/BeatmapCardContent.cs | 51 ---------------- .../Cards/ExpandedContentScrollContainer.cs | 61 +++++++++++++++++++ osu.Game/Overlays/BeatmapListingOverlay.cs | 6 +- 3 files changed, 66 insertions(+), 52 deletions(-) create mode 100644 osu.Game/Beatmaps/Drawables/Cards/ExpandedContentScrollContainer.cs diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContent.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContent.cs index 497283bc64..d43b3291be 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContent.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContent.cs @@ -3,16 +3,13 @@ #nullable enable -using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.Events; using osu.Framework.Threading; -using osu.Framework.Utils; using osu.Game.Graphics.Containers; using osu.Game.Overlays; using osuTK; @@ -157,53 +154,5 @@ namespace osu.Game.Beatmaps.Drawables.Cards }, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); } - private class ExpandedContentScrollContainer : OsuScrollContainer - { - public ExpandedContentScrollContainer() - { - ScrollbarVisible = false; - } - - protected override void Update() - { - base.Update(); - - Height = Math.Min(Content.DrawHeight, 400); - } - - private bool allowScroll => !Precision.AlmostEquals(DrawSize, Content.DrawSize); - - protected override bool OnDragStart(DragStartEvent e) - { - if (!allowScroll) - return false; - - return base.OnDragStart(e); - } - - protected override void OnDrag(DragEvent e) - { - if (!allowScroll) - return; - - base.OnDrag(e); - } - - protected override void OnDragEnd(DragEndEvent e) - { - if (!allowScroll) - return; - - base.OnDragEnd(e); - } - - protected override bool OnScroll(ScrollEvent e) - { - if (!allowScroll) - return false; - - return base.OnScroll(e); - } - } } } diff --git a/osu.Game/Beatmaps/Drawables/Cards/ExpandedContentScrollContainer.cs b/osu.Game/Beatmaps/Drawables/Cards/ExpandedContentScrollContainer.cs new file mode 100644 index 0000000000..7c3fbdaf1c --- /dev/null +++ b/osu.Game/Beatmaps/Drawables/Cards/ExpandedContentScrollContainer.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 osu.Framework.Input.Events; +using osu.Framework.Utils; +using osu.Game.Graphics.Containers; + +namespace osu.Game.Beatmaps.Drawables.Cards +{ + public class ExpandedContentScrollContainer : OsuScrollContainer + { + public const float HEIGHT = 400; + + public ExpandedContentScrollContainer() + { + ScrollbarVisible = false; + } + + protected override void Update() + { + base.Update(); + + Height = Math.Min(Content.DrawHeight, HEIGHT); + } + + private bool allowScroll => !Precision.AlmostEquals(DrawSize, Content.DrawSize); + + protected override bool OnDragStart(DragStartEvent e) + { + if (!allowScroll) + return false; + + return base.OnDragStart(e); + } + + protected override void OnDrag(DragEvent e) + { + if (!allowScroll) + return; + + base.OnDrag(e); + } + + protected override void OnDragEnd(DragEndEvent e) + { + if (!allowScroll) + return; + + base.OnDragEnd(e); + } + + protected override bool OnScroll(ScrollEvent e) + { + if (!allowScroll) + return false; + + return base.OnScroll(e); + } + } +} diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 0c2bad95d6..f06945d85b 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -186,7 +186,11 @@ namespace osu.Game.Overlays AutoSizeAxes = Axes.Y, Spacing = new Vector2(10), Alpha = 0, - Margin = new MarginPadding { Vertical = 15 }, + Margin = new MarginPadding + { + Vertical = 15, + Bottom = ExpandedContentScrollContainer.HEIGHT + }, ChildrenEnumerable = newCards }; return content; From 0ad555e9f7821181a00b9aaeba77f0c9fad309c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 3 Jan 2022 18:33:32 +0100 Subject: [PATCH 066/996] Remove surplus blank line --- osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContent.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContent.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContent.cs index d43b3291be..1aaa72f5f0 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContent.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContent.cs @@ -153,6 +153,5 @@ namespace osu.Game.Beatmaps.Drawables.Cards Hollow = true, }, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); } - } } From 7c246670b450cb3daa312d5017e3b1b8c71821c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 3 Jan 2022 18:43:20 +0100 Subject: [PATCH 067/996] Add padding to bottom of spotlights ranking view to avoid hovered panels exceeding visible bounds --- osu.Game/Overlays/Rankings/SpotlightsLayout.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/Rankings/SpotlightsLayout.cs b/osu.Game/Overlays/Rankings/SpotlightsLayout.cs index fb01656c24..7f5d096fe2 100644 --- a/osu.Game/Overlays/Rankings/SpotlightsLayout.cs +++ b/osu.Game/Overlays/Rankings/SpotlightsLayout.cs @@ -140,6 +140,7 @@ namespace osu.Game.Overlays.Rankings { AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, + Margin = new MarginPadding { Bottom = ExpandedContentScrollContainer.HEIGHT }, Spacing = new Vector2(10), Children = response.BeatmapSets.Select(b => new BeatmapCardNormal(b) { From 2660f413391a8b5934f918c332c1c6370bba0c93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 3 Jan 2022 19:27:46 +0100 Subject: [PATCH 068/996] Add failing test case for old cards not expiring correctly --- .../Online/TestSceneBeatmapListingOverlay.cs | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs index ee8794ae87..d0e3340f2a 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs @@ -107,19 +107,31 @@ namespace osu.Game.Tests.Visual.Online AddUntilStep("is hidden", () => overlay.State.Value == Visibility.Hidden); } + [Test] + public void TestCorrectOldContentExpiration() + { + AddAssert("is visible", () => overlay.State.Value == Visibility.Visible); + + AddStep("show many results", () => fetchFor(Enumerable.Repeat(CreateAPIBeatmapSet(Ruleset.Value), 100).ToArray())); + assertAllCardsOfType(100); + + AddStep("show more results", () => fetchFor(Enumerable.Repeat(CreateAPIBeatmapSet(Ruleset.Value), 30).ToArray())); + assertAllCardsOfType(30); + } + [Test] public void TestCardSizeSwitching() { AddAssert("is visible", () => overlay.State.Value == Visibility.Visible); AddStep("show many results", () => fetchFor(Enumerable.Repeat(CreateAPIBeatmapSet(Ruleset.Value), 100).ToArray())); - assertAllCardsOfType(); + assertAllCardsOfType(100); setCardSize(BeatmapCardSize.Extra); - assertAllCardsOfType(); + assertAllCardsOfType(100); setCardSize(BeatmapCardSize.Normal); - assertAllCardsOfType(); + assertAllCardsOfType(100); AddStep("fetch for 0 beatmaps", () => fetchFor()); AddUntilStep("placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); @@ -323,13 +335,12 @@ namespace osu.Game.Tests.Visual.Online private void setCardSize(BeatmapCardSize cardSize) => AddStep($"set card size to {cardSize}", () => overlay.ChildrenOfType().Single().Current.Value = cardSize); - private void assertAllCardsOfType() + private void assertAllCardsOfType(int expectedCount) where T : BeatmapCard => AddUntilStep($"all loaded beatmap cards are {typeof(T)}", () => { int loadedCorrectCount = this.ChildrenOfType().Count(card => card.IsLoaded && card.GetType() == typeof(T)); - int totalCount = this.ChildrenOfType().Count(); - return loadedCorrectCount > 0 && loadedCorrectCount == totalCount; + return loadedCorrectCount > 0 && loadedCorrectCount == expectedCount; }); } } From 97439c3df15879e29163e8b5f3b7861f7a1b6d09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 3 Jan 2022 19:30:17 +0100 Subject: [PATCH 069/996] Rename method to reflect what it actually does --- osu.Game/Overlays/BeatmapListingOverlay.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 0c2bad95d6..d9c2d90e05 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -140,7 +140,7 @@ namespace osu.Game.Overlays if (searchResult.Type == BeatmapListingFilterControl.SearchResultType.SupporterOnlyFilters) { supporterRequiredContent.UpdateText(searchResult.SupporterOnlyFiltersUsed); - addContentToPlaceholder(supporterRequiredContent); + addContentToResultsArea(supporterRequiredContent); return; } @@ -151,13 +151,13 @@ namespace osu.Game.Overlays //No matches case if (!newCards.Any()) { - addContentToPlaceholder(notFoundContent); + addContentToResultsArea(notFoundContent); return; } var content = createCardContainerFor(newCards); - panelLoadTask = LoadComponentAsync(foundContent = content, addContentToPlaceholder, (cancellationToken = new CancellationTokenSource()).Token); + panelLoadTask = LoadComponentAsync(foundContent = content, addContentToResultsArea, (cancellationToken = new CancellationTokenSource()).Token); } else { @@ -192,7 +192,7 @@ namespace osu.Game.Overlays return content; } - private void addContentToPlaceholder(Drawable content) + private void addContentToResultsArea(Drawable content) { Loading.Hide(); lastFetchDisplayedTime = Time.Current; From ef9f56e5850b2d76fc960b7b070a0c9b08601140 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 3 Jan 2022 19:33:01 +0100 Subject: [PATCH 070/996] Fix bad check if content is placeholder The `lastContent == foundContent` check, last touched in a49a4329, is terminally broken, as it would always be false. `foundContent` is mutated when a new card load task is started in `onSearchFinished()`, which is *before* the aforementioned check. The code prior to a49a4329 was checking against the two static reused placeholder drawables which was the correct check to apply, and this commit reverts to using a variant of that check. --- osu.Game/Overlays/BeatmapListingOverlay.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index d9c2d90e05..ea3c08ea61 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -212,7 +212,7 @@ namespace osu.Game.Overlays // To resolve both of these issues, the bypass is delayed until a point when the content transitions (fade-in and fade-out) overlap and it looks good to do so. var sequence = lastContent.Delay(25).Schedule(() => lastContent.BypassAutoSizeAxes = Axes.Y); - if (lastContent == foundContent) + if (!isPlaceholderContent(lastContent)) { sequence.Then().Schedule(() => { @@ -232,6 +232,12 @@ namespace osu.Game.Overlays currentContent.BypassAutoSizeAxes = Axes.None; } + /// + /// Whether is a static placeholder reused multiple times by this overlay. + /// + private bool isPlaceholderContent(Drawable drawable) + => drawable == notFoundContent || drawable == supporterRequiredContent; + private void onCardSizeChanged() { if (foundContent == null || !foundContent.Any()) From 6650a468e0d7d7a010c16093ce5450d5ae08d6a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 3 Jan 2022 19:39:07 +0100 Subject: [PATCH 071/996] Fix and simplify very broken beatmap listing content swap-out logic The beatmap listing content swap-out logic was already a source of several problems, and several attempts of fixing it were made. But as it turns out it was terminally broken in several aspects. * The `BypassAutoSizeAxes` juggling was finicky and ugly, and didn't really look much different than an instant fade. Therefore, all fade durations and manipulations of `BypassAutoSizeAxes` are removed. * The transform sequence juggling the `BypassAutoSizeAxes` manipulations was enqueued on the content which is being in the process of fading out. That was partially fixed in 25e38560, but as it turns out, that only works if `lastContent` is one of the two placeholder drawables (results not found / supporter required to use filter). It would not work if `lastContent` is a `ReverseChildIDFillFlowContainer` with cards from a previous search in it. --- osu.Game/Overlays/BeatmapListingOverlay.cs | 30 +++------------------- 1 file changed, 3 insertions(+), 27 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index ea3c08ea61..55a99d21ef 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -204,32 +204,16 @@ namespace osu.Game.Overlays if (lastContent != null) { - lastContent.FadeOut(100, Easing.OutQuint); - - // Consider the case when the new content is smaller than the last content. - // If the auto-size computation is delayed until fade out completes, the background remain high for too long making the resulting transition to the smaller height look weird. - // At the same time, if the last content's height is bypassed immediately, there is a period where the new content is at Alpha = 0 when the auto-sized height will be 0. - // To resolve both of these issues, the bypass is delayed until a point when the content transitions (fade-in and fade-out) overlap and it looks good to do so. - var sequence = lastContent.Delay(25).Schedule(() => lastContent.BypassAutoSizeAxes = Axes.Y); - + lastContent.FadeOut(); if (!isPlaceholderContent(lastContent)) - { - sequence.Then().Schedule(() => - { - foundContent.Expire(); - foundContent = null; - }); - } + lastContent.Expire(); } if (!content.IsAlive) panelTarget.Add(content); - content.FadeInFromZero(200, Easing.OutQuint); + content.FadeInFromZero(); currentContent = content; - // currentContent may be one of the placeholders, and still have BypassAutoSizeAxes set to Y from the last fade-out. - // restore to the initial state. - currentContent.BypassAutoSizeAxes = Axes.None; } /// @@ -265,10 +249,6 @@ namespace osu.Game.Overlays public class NotFoundDrawable : CompositeDrawable { - // required for scheduled tasks to complete correctly - // (see `addContentToPlaceholder()` and the scheduled `BypassAutoSizeAxes` set during fade-out in outer class above) - public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks; - public NotFoundDrawable() { RelativeSizeAxes = Axes.X; @@ -313,10 +293,6 @@ namespace osu.Game.Overlays // (https://github.com/ppy/osu-framework/issues/4530) public class SupporterRequiredDrawable : CompositeDrawable { - // required for scheduled tasks to complete correctly - // (see `addContentToPlaceholder()` and the scheduled `BypassAutoSizeAxes` set during fade-out in outer class above) - public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks; - private LinkFlowContainer supporterRequiredText; public SupporterRequiredDrawable() From 586f158920b011233fba348d500cfb7c0265da11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 3 Jan 2022 19:52:42 +0100 Subject: [PATCH 072/996] Remove initial `foundContent` value It always is replaced on the first search anyway, and just remains forever in the overlay otherwise. --- osu.Game/Overlays/BeatmapListingOverlay.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 55a99d21ef..c2f2c1dec2 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -79,7 +79,6 @@ namespace osu.Game.Overlays Padding = new MarginPadding { Horizontal = 20 }, Children = new Drawable[] { - foundContent = new FillFlowContainer(), notFoundContent = new NotFoundDrawable(), supporterRequiredContent = new SupporterRequiredDrawable(), } From de33b420abb157bf7c6e62edbc18d1a720bea82f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 3 Jan 2022 20:02:46 +0100 Subject: [PATCH 073/996] Add safety against performing operation on non-alive `foundContent` --- osu.Game/Overlays/BeatmapListingOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index c2f2c1dec2..b5b12391e8 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -223,7 +223,7 @@ namespace osu.Game.Overlays private void onCardSizeChanged() { - if (foundContent == null || !foundContent.Any()) + if (foundContent?.IsAlive != true || !foundContent.Any()) return; Loading.Show(); From 7cdba2f7c3a915f8f8d8c9996d8689c1ecb4d399 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 3 Jan 2022 21:50:00 +0100 Subject: [PATCH 074/996] Add test coverage of score submission if player is exited during import --- .../TestScenePlayerScoreSubmission.cs | 44 ++++++++++++++++--- 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs index 324a132120..25808d307d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs @@ -3,6 +3,7 @@ using System; using System.Linq; +using System.Threading; using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Screens; @@ -36,7 +37,9 @@ namespace osu.Game.Tests.Visual.Gameplay protected override bool HasCustomSteps => true; - protected override TestPlayer CreatePlayer(Ruleset ruleset) => new NonImportingPlayer(false); + protected override TestPlayer CreatePlayer(Ruleset ruleset) => new FakeImportingPlayer(false); + + protected new FakeImportingPlayer Player => (FakeImportingPlayer)base.Player; protected override Ruleset CreatePlayerRuleset() => createCustomRuleset?.Invoke() ?? new OsuRuleset(); @@ -207,6 +210,25 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("ensure failing submission", () => Player.SubmittedScore?.ScoreInfo.Passed == false); } + [Test] + public void TestSubmissionOnExitDuringImport() + { + prepareTokenResponse(true); + + createPlayerTest(); + AddStep("block imports", () => Player.AllowImportCompletion.Wait()); + + AddUntilStep("wait for token request", () => Player.TokenCreationRequested); + + addFakeHit(); + + AddUntilStep("wait for import to start", () => Player.ScoreImportStarted); + + AddStep("exit", () => Player.Exit()); + AddStep("allow import to proceed", () => Player.AllowImportCompletion.Release(1)); + AddAssert("ensure submission", () => Player.SubmittedScore != null && Player.ImportedScore != null); + } + [Test] public void TestNoSubmissionOnLocalBeatmap() { @@ -288,15 +310,26 @@ namespace osu.Game.Tests.Visual.Gameplay }); } - private class NonImportingPlayer : TestPlayer + protected class FakeImportingPlayer : TestPlayer { - public NonImportingPlayer(bool allowPause = true, bool showResults = true, bool pauseOnFocusLost = false) + public bool ScoreImportStarted { get; set; } + public SemaphoreSlim AllowImportCompletion { get; } + public Score ImportedScore { get; private set; } + + public FakeImportingPlayer(bool allowPause = true, bool showResults = true, bool pauseOnFocusLost = false) : base(allowPause, showResults, pauseOnFocusLost) { + AllowImportCompletion = new SemaphoreSlim(1); } - protected override Task ImportScore(Score score) + protected override async Task ImportScore(Score score) { + ScoreImportStarted = true; + + await AllowImportCompletion.WaitAsync().ConfigureAwait(false); + + ImportedScore = 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. @@ -311,8 +344,7 @@ namespace osu.Game.Tests.Visual.Gameplay // 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, importing is disabled in this test. - return Task.CompletedTask; + // For the above reasons, actual importing is disabled in this test. } } } From 031a40af6af42db5f3876e2a369c054590c9a563 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 3 Jan 2022 17:02:15 +0900 Subject: [PATCH 075/996] Replace usages of `Wait` with `WaitSafely` --- CodeAnalysis/BannedSymbols.txt | 1 + .../Collections/IO/ImportCollectionsTest.cs | 3 ++- osu.Game.Tests/Database/RealmLiveTests.cs | 19 ++++++++++--------- osu.Game.Tests/ImportTest.cs | 3 ++- ...eneOnlinePlayBeatmapAvailabilityTracker.cs | 8 ++++---- .../Background/TestSceneUserDimBackgrounds.cs | 3 ++- .../TestSceneManageCollectionsDialog.cs | 3 ++- .../Menus/TestSceneMusicActionHandling.cs | 3 ++- .../Visual/Multiplayer/QueueModeTestScene.cs | 3 ++- .../TestSceneDrawableRoomPlaylist.cs | 3 ++- .../Multiplayer/TestSceneMultiplayer.cs | 5 +++-- .../TestSceneMultiplayerMatchSongSelect.cs | 2 +- .../TestSceneMultiplayerMatchSubScreen.cs | 3 ++- .../TestSceneMultiplayerPlaylist.cs | 3 ++- .../TestSceneMultiplayerQueueList.cs | 3 ++- .../TestSceneMultiplayerReadyButton.cs | 3 ++- .../TestSceneMultiplayerSpectateButton.cs | 3 ++- .../TestScenePlaylistsSongSelect.cs | 3 ++- .../Visual/Multiplayer/TestSceneTeamVersus.cs | 3 ++- .../TestSceneMouseWheelVolumeAdjust.cs | 3 ++- .../Navigation/TestScenePerformFromScreen.cs | 3 ++- .../Navigation/TestSceneScreenNavigation.cs | 7 ++++--- .../TestScenePlaylistsRoomCreation.cs | 5 +++-- .../SongSelect/TestSceneBeatmapLeaderboard.cs | 5 +++-- .../SongSelect/TestSceneFilterControl.cs | 3 ++- .../SongSelect/TestScenePlaySongSelect.cs | 11 ++++++----- osu.Game/Screens/Play/SubmittingPlayer.cs | 3 ++- .../Multiplayer/TestMultiplayerClient.cs | 9 +++++---- 28 files changed, 76 insertions(+), 50 deletions(-) diff --git a/CodeAnalysis/BannedSymbols.txt b/CodeAnalysis/BannedSymbols.txt index c567adc0ae..2048714cb3 100644 --- a/CodeAnalysis/BannedSymbols.txt +++ b/CodeAnalysis/BannedSymbols.txt @@ -13,3 +13,4 @@ M:System.Enum.HasFlag(System.Enum);Use osu.Framework.Extensions.EnumExtensions.H M:Realms.IRealmCollection`1.SubscribeForNotifications`1(Realms.NotificationCallbackDelegate{``0});Use osu.Game.Database.RealmObjectExtensions.QueryAsyncWithNotifications(IRealmCollection,NotificationCallbackDelegate) instead. M:Realms.CollectionExtensions.SubscribeForNotifications`1(System.Linq.IQueryable{``0},Realms.NotificationCallbackDelegate{``0});Use osu.Game.Database.RealmObjectExtensions.QueryAsyncWithNotifications(IQueryable,NotificationCallbackDelegate) instead. M:Realms.CollectionExtensions.SubscribeForNotifications`1(System.Collections.Generic.IList{``0},Realms.NotificationCallbackDelegate{``0});Use osu.Game.Database.RealmObjectExtensions.QueryAsyncWithNotifications(IList,NotificationCallbackDelegate) instead. +M:System.Threading.Tasks.Task.Wait();Don't use Task.Wait. Use Task.WaitSafely() to ensure we avoid deadlocks. diff --git a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs index d4ec5e897b..53e4ef07e7 100644 --- a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs +++ b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs @@ -6,6 +6,7 @@ using System.IO; using System.Text; using System.Threading.Tasks; using NUnit.Framework; +using osu.Framework.Extensions; using osu.Framework.Platform; using osu.Framework.Testing; using osu.Game.Tests.Resources; @@ -179,7 +180,7 @@ namespace osu.Game.Tests.Collections.IO { // intentionally spin this up on a separate task to avoid disposal deadlocks. // see https://github.com/EventStore/EventStore/issues/1179 - await Task.Run(() => osu.CollectionManager.Import(stream).Wait()); + await Task.Factory.StartNew(() => osu.CollectionManager.Import(stream).WaitSafely(), TaskCreationOptions.LongRunning); } } } diff --git a/osu.Game.Tests/Database/RealmLiveTests.cs b/osu.Game.Tests/Database/RealmLiveTests.cs index 06cb5a3607..9432a56741 100644 --- a/osu.Game.Tests/Database/RealmLiveTests.cs +++ b/osu.Game.Tests/Database/RealmLiveTests.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using NUnit.Framework; +using osu.Framework.Extensions; using osu.Framework.Testing; using osu.Game.Database; using osu.Game.Models; @@ -104,7 +105,7 @@ namespace osu.Game.Tests.Database liveBeatmap = beatmap.ToLive(realmFactory); } - }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait(); + }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely(); Debug.Assert(liveBeatmap != null); @@ -115,7 +116,7 @@ namespace osu.Game.Tests.Database Assert.IsTrue(beatmap.IsValid); Assert.IsFalse(beatmap.Hidden); }); - }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait(); + }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely(); }); } @@ -133,7 +134,7 @@ namespace osu.Game.Tests.Database liveBeatmap = beatmap.ToLive(realmFactory); } - }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait(); + }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely(); Debug.Assert(liveBeatmap != null); @@ -141,7 +142,7 @@ namespace osu.Game.Tests.Database { liveBeatmap.PerformWrite(beatmap => { beatmap.Hidden = true; }); liveBeatmap.PerformRead(beatmap => { Assert.IsTrue(beatmap.Hidden); }); - }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait(); + }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely(); }); } @@ -175,7 +176,7 @@ namespace osu.Game.Tests.Database liveBeatmap = beatmap.ToLive(realmFactory); } - }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait(); + }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely(); Debug.Assert(liveBeatmap != null); @@ -195,7 +196,7 @@ namespace osu.Game.Tests.Database var __ = liveBeatmap.Value; }); } - }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait(); + }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely(); }); } @@ -213,7 +214,7 @@ namespace osu.Game.Tests.Database liveBeatmap = beatmap.ToLive(realmFactory); } - }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait(); + }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely(); Debug.Assert(liveBeatmap != null); @@ -223,7 +224,7 @@ namespace osu.Game.Tests.Database { var unused = liveBeatmap.Value; }); - }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait(); + }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely(); }); } @@ -252,7 +253,7 @@ namespace osu.Game.Tests.Database liveBeatmap = beatmap.ToLive(realmFactory); } - }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait(); + }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely(); Debug.Assert(liveBeatmap != null); diff --git a/osu.Game.Tests/ImportTest.cs b/osu.Game.Tests/ImportTest.cs index dbeb453d4d..a658a0eaeb 100644 --- a/osu.Game.Tests/ImportTest.cs +++ b/osu.Game.Tests/ImportTest.cs @@ -6,6 +6,7 @@ using System.Threading; using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Extensions; using osu.Framework.Platform; using osu.Game.Collections; using osu.Game.Tests.Resources; @@ -58,7 +59,7 @@ namespace osu.Game.Tests { // Beatmap must be imported before the collection manager is loaded. if (withBeatmap) - BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).Wait(); + BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).WaitSafely(); AddInternal(CollectionManager = new CollectionManager(Storage)); } diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index 239c787349..a7b431fb6e 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -100,7 +100,7 @@ namespace osu.Game.Tests.Online public void TestTrackerRespectsSoftDeleting() { AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true)); - AddStep("import beatmap", () => beatmaps.Import(testBeatmapFile).Wait()); + AddStep("import beatmap", () => beatmaps.Import(testBeatmapFile).WaitSafely()); addAvailabilityCheckStep("state locally available", BeatmapAvailability.LocallyAvailable); AddStep("delete beatmap", () => beatmaps.Delete(beatmaps.QueryBeatmapSet(b => b.OnlineID == testBeatmapSet.OnlineID))); @@ -114,12 +114,12 @@ namespace osu.Game.Tests.Online public void TestTrackerRespectsChecksum() { AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true)); - AddStep("import beatmap", () => beatmaps.Import(testBeatmapFile).Wait()); + AddStep("import beatmap", () => beatmaps.Import(testBeatmapFile).WaitSafely()); addAvailabilityCheckStep("initially locally available", BeatmapAvailability.LocallyAvailable); AddStep("import altered beatmap", () => { - beatmaps.Import(TestResources.GetTestBeatmapForImport(true)).Wait(); + beatmaps.Import(TestResources.GetTestBeatmapForImport(true)).WaitSafely(); }); addAvailabilityCheckStep("state not downloaded", BeatmapAvailability.NotDownloaded); @@ -129,7 +129,7 @@ namespace osu.Game.Tests.Online }); addAvailabilityCheckStep("state not downloaded as well", BeatmapAvailability.NotDownloaded); - AddStep("reimport original beatmap", () => beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait()); + AddStep("reimport original beatmap", () => beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely()); addAvailabilityCheckStep("locally available after re-import", BeatmapAvailability.LocallyAvailable); } diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs index 33b1d9a67d..54c5c252c0 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs @@ -7,6 +7,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; @@ -49,7 +50,7 @@ namespace osu.Game.Tests.Visual.Background Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(new OsuConfigManager(LocalStorage)); - manager.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); + manager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); Beatmap.SetDefault(); } diff --git a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs index 28218ea220..d2b0f7324b 100644 --- a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs +++ b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs @@ -4,6 +4,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; @@ -38,7 +39,7 @@ namespace osu.Game.Tests.Visual.Collections Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, Audio, Resources, host, Beatmap.Default)); - beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); + beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); base.Content.AddRange(new Drawable[] { diff --git a/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs b/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs index 55e453c3d3..425e7718dc 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; +using osu.Framework.Extensions; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Database; @@ -33,7 +34,7 @@ namespace osu.Game.Tests.Visual.Menus Queue<(IWorkingBeatmap working, TrackChangeDirection changeDirection)> trackChangeQueue = null; // ensure we have at least two beatmaps available to identify the direction the music controller navigated to. - AddRepeatStep("import beatmap", () => Game.BeatmapManager.Import(TestResources.CreateTestBeatmapSetInfo()).Wait(), 5); + AddRepeatStep("import beatmap", () => Game.BeatmapManager.Import(TestResources.CreateTestBeatmapSetInfo()).WaitSafely(), 5); AddStep("import beatmap with track", () => { diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs index 88c54eb2bb..c4d7bd7e6a 100644 --- a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs +++ b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs @@ -5,6 +5,7 @@ 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; @@ -56,7 +57,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("import beatmap", () => { - beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); + beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First(); InitialBeatmap = importedSet.Beatmaps.First(b => b.RulesetID == 0); OtherBeatmap = importedSet.Beatmaps.Last(b => b.RulesetID == 0); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index f9784384fd..147bbf2626 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -8,6 +8,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Platform; @@ -153,7 +154,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { var beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo; - AddStep("import beatmap", () => manager.Import(beatmap.BeatmapSet).Wait()); + AddStep("import beatmap", () => manager.Import(beatmap.BeatmapSet).WaitSafely()); createPlaylistWithBeatmaps(beatmap); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 4eebda94e9..24fc3ac5b9 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -7,6 +7,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; +using osu.Framework.Extensions; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input; @@ -67,7 +68,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("import beatmap", () => { - beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); + beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First(); }); @@ -505,7 +506,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("restore beatmap", () => { - beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); + beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First(); }); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index d671673d3c..bd4b38b9c0 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -83,7 +83,7 @@ namespace osu.Game.Tests.Visual.Multiplayer beatmapSetInfo.Beatmaps.Add(beatmap); } - manager.Import(beatmapSetInfo).Wait(); + manager.Import(beatmapSetInfo).WaitSafely(); } public override void SetUpSteps() diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index 55430fbb41..52e46ef5af 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -5,6 +5,7 @@ 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; @@ -39,7 +40,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); - beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); + beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First(); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs index 7760661232..464c0ea5b6 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs @@ -6,6 +6,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Platform; using osu.Framework.Testing; @@ -53,7 +54,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("import beatmap", () => { - beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); + beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First(); importedBeatmap = importedSet.Beatmaps.First(b => b.RulesetID == 0); }); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs index 1a646d5e7e..29daff546d 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs @@ -6,6 +6,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Platform; @@ -58,7 +59,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("import beatmap", () => { - beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); + beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First(); importedBeatmap = importedSet.Beatmaps.First(b => b.RulesetID == 0); }); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs index 93be28ad90..8f51b1e381 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs @@ -8,6 +8,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Platform; using osu.Framework.Testing; @@ -41,7 +42,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); - beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); + beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); } [SetUp] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs index eecf0dff9f..d4ff9f8c41 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs @@ -8,6 +8,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Platform; @@ -42,7 +43,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); - beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); + beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); } [SetUp] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs index 5aac228f4b..08fcac125d 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs @@ -6,6 +6,7 @@ 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.Utils; @@ -38,7 +39,7 @@ namespace osu.Game.Tests.Visual.Multiplayer var beatmapSet = TestResources.CreateTestBeatmapSetInfo(); - manager.Import(beatmapSet).Wait(); + manager.Import(beatmapSet).WaitSafely(); } public override void SetUpSteps() diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs index 81c59b90f5..d20fbd3539 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs @@ -6,6 +6,7 @@ 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.Testing; using osu.Game.Beatmaps; @@ -51,7 +52,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("import beatmap", () => { - beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); + beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First(); }); diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneMouseWheelVolumeAdjust.cs b/osu.Game.Tests/Visual/Navigation/TestSceneMouseWheelVolumeAdjust.cs index b3b80147ca..701ab480f6 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneMouseWheelVolumeAdjust.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneMouseWheelVolumeAdjust.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; +using osu.Framework.Extensions; using osu.Game.Configuration; using osu.Game.Screens.Play; using osu.Game.Tests.Beatmaps.IO; @@ -82,7 +83,7 @@ namespace osu.Game.Tests.Visual.Navigation PushAndConfirm(() => songSelect = new TestSceneScreenNavigation.TestPlaySongSelect()); AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded); - AddStep("import beatmap", () => ImportBeatmapTest.LoadOszIntoOsu(Game, virtualTrack: true).Wait()); + AddStep("import beatmap", () => ImportBeatmapTest.LoadOszIntoOsu(Game, virtualTrack: true).WaitSafely()); AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); AddStep("press enter", () => InputManager.Key(Key.Enter)); diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs b/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs index 4e1b3bb9bf..24f5808961 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs @@ -4,6 +4,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Extensions; using osu.Framework.Graphics.Containers; using osu.Framework.Screens; using osu.Framework.Testing; @@ -172,7 +173,7 @@ namespace osu.Game.Tests.Visual.Navigation private void importAndWaitForSongSelect() { - AddStep("import beatmap", () => ImportBeatmapTest.LoadQuickOszIntoOsu(Game).Wait()); + AddStep("import beatmap", () => ImportBeatmapTest.LoadQuickOszIntoOsu(Game).WaitSafely()); PushAndConfirm(() => new TestPlaySongSelect()); AddUntilStep("beatmap updated", () => Game.Beatmap.Value.BeatmapSetInfo.OnlineID == 241526); } diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index 75d8e62ca7..d094d8b688 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -4,6 +4,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Extensions; using osu.Framework.Graphics.Containers; using osu.Framework.Screens; using osu.Framework.Testing; @@ -70,7 +71,7 @@ namespace osu.Game.Tests.Visual.Navigation PushAndConfirm(() => songSelect = new TestPlaySongSelect()); AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded); - AddStep("import beatmap", () => ImportBeatmapTest.LoadQuickOszIntoOsu(Game).Wait()); + AddStep("import beatmap", () => ImportBeatmapTest.LoadQuickOszIntoOsu(Game).WaitSafely()); AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); @@ -104,7 +105,7 @@ namespace osu.Game.Tests.Visual.Navigation PushAndConfirm(() => songSelect = new TestPlaySongSelect()); AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded); - AddStep("import beatmap", () => ImportBeatmapTest.LoadQuickOszIntoOsu(Game).Wait()); + AddStep("import beatmap", () => ImportBeatmapTest.LoadQuickOszIntoOsu(Game).WaitSafely()); AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); @@ -138,7 +139,7 @@ namespace osu.Game.Tests.Visual.Navigation PushAndConfirm(() => songSelect = new TestPlaySongSelect()); AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded); - AddStep("import beatmap", () => ImportBeatmapTest.LoadOszIntoOsu(Game, virtualTrack: true).Wait()); + AddStep("import beatmap", () => ImportBeatmapTest.LoadOszIntoOsu(Game, virtualTrack: true).WaitSafely()); AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs index a426f075e1..f3d00dc076 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs @@ -7,6 +7,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Testing; @@ -142,7 +143,7 @@ namespace osu.Game.Tests.Visual.Playlists modifiedBeatmap.HitObjects.Clear(); modifiedBeatmap.HitObjects.Add(new HitCircle { StartTime = 5000 }); - manager.Import(modifiedBeatmap.BeatmapInfo.BeatmapSet).Wait(); + manager.Import(modifiedBeatmap.BeatmapInfo.BeatmapSet).WaitSafely(); }); // Create the room using the real beatmap values. @@ -184,7 +185,7 @@ namespace osu.Game.Tests.Visual.Playlists }, }; - manager.Import(originalBeatmap.BeatmapInfo.BeatmapSet).Wait(); + manager.Import(originalBeatmap.BeatmapInfo.BeatmapSet).WaitSafely(); }); AddUntilStep("match has correct beatmap", () => realHash == match.Beatmap.Value.BeatmapInfo.MD5Hash); diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index aa36bde030..605e03564d 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -6,6 +6,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Platform; using osu.Framework.Testing; @@ -74,7 +75,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep(@"Set beatmap", () => { - beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); + beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); beatmapInfo = beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps.First(); leaderboard.BeatmapInfo = beatmapInfo; @@ -175,7 +176,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep(@"Load new scores via manager", () => { foreach (var score in generateSampleScores(beatmapInfo())) - scoreManager.Import(score).Wait(); + scoreManager.Import(score).WaitSafely(); }); } diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs index 0ae4e0c5dc..1ee59eccc7 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; @@ -38,7 +39,7 @@ namespace osu.Game.Tests.Visual.SongSelect Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, Audio, Resources, host, Beatmap.Default)); - beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); + beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); base.Content.AddRange(new Drawable[] { diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 912d3f838c..bd5aeb7e08 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -8,6 +8,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Testing; @@ -256,7 +257,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("import multi-ruleset map", () => { var usableRulesets = rulesets.AvailableRulesets.Where(r => r.OnlineID != 2).ToArray(); - manager.Import(TestResources.CreateTestBeatmapSetInfo(rulesets: usableRulesets)).Wait(); + manager.Import(TestResources.CreateTestBeatmapSetInfo(rulesets: usableRulesets)).WaitSafely(); }); } else @@ -670,7 +671,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("import multi-ruleset map", () => { var usableRulesets = rulesets.AvailableRulesets.Where(r => r.OnlineID != 2).ToArray(); - manager.Import(TestResources.CreateTestBeatmapSetInfo(3, usableRulesets)).Wait(); + manager.Import(TestResources.CreateTestBeatmapSetInfo(3, usableRulesets)).WaitSafely(); }); int previousSetID = 0; @@ -710,7 +711,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("import multi-ruleset map", () => { var usableRulesets = rulesets.AvailableRulesets.Where(r => r.OnlineID != 2).ToArray(); - manager.Import(TestResources.CreateTestBeatmapSetInfo(3, usableRulesets)).Wait(); + manager.Import(TestResources.CreateTestBeatmapSetInfo(3, usableRulesets)).WaitSafely(); }); DrawableCarouselBeatmapSet set = null; @@ -868,7 +869,7 @@ namespace osu.Game.Tests.Visual.SongSelect private void addRulesetImportStep(int id) => AddStep($"import test map for ruleset {id}", () => importForRuleset(id)); - private void importForRuleset(int id) => manager.Import(TestResources.CreateTestBeatmapSetInfo(3, rulesets.AvailableRulesets.Where(r => r.OnlineID == id).ToArray())).Wait(); + private void importForRuleset(int id) => manager.Import(TestResources.CreateTestBeatmapSetInfo(3, rulesets.AvailableRulesets.Where(r => r.OnlineID == id).ToArray())).WaitSafely(); private void checkMusicPlaying(bool playing) => AddUntilStep($"music {(playing ? "" : "not ")}playing", () => music.IsPlaying == playing); @@ -898,7 +899,7 @@ namespace osu.Game.Tests.Visual.SongSelect var usableRulesets = rulesets.AvailableRulesets.Where(r => r.OnlineID != 2).ToArray(); for (int i = 0; i < 10; i++) - manager.Import(TestResources.CreateTestBeatmapSetInfo(difficultyCountPerSet, usableRulesets)).Wait(); + manager.Import(TestResources.CreateTestBeatmapSetInfo(difficultyCountPerSet, usableRulesets)).WaitSafely(); }); } diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index c613167908..2cf56be659 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Threading.Tasks; using JetBrains.Annotations; using osu.Framework.Allocation; +using osu.Framework.Extensions; using osu.Framework.Logging; using osu.Framework.Screens; using osu.Game.Online.API; @@ -75,7 +76,7 @@ namespace osu.Game.Screens.Play api.Queue(req); - tcs.Task.Wait(); + tcs.Task.WaitSafely(); return true; void handleTokenFailure(Exception exception) diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 571c3e759f..76acac3f8b 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -11,6 +11,7 @@ using System.Threading; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; @@ -77,7 +78,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private void addUser(MultiplayerRoomUser user) { - ((IMultiplayerClient)this).UserJoined(user).Wait(); + ((IMultiplayerClient)this).UserJoined(user).WaitSafely(); // We want the user to be immediately available for testing, so force a scheduler update to run the update-bound continuation. Scheduler.Update(); @@ -93,7 +94,7 @@ namespace osu.Game.Tests.Visual.Multiplayer .Select(team => (teamID: team.ID, userCount: Room.Users.Count(u => (u.MatchState as TeamVersusUserState)?.TeamID == team.ID))) .OrderBy(pair => pair.userCount) .First().teamID; - ((IMultiplayerClient)this).MatchUserStateChanged(user.UserID, new TeamVersusUserState { TeamID = bestTeam }).Wait(); + ((IMultiplayerClient)this).MatchUserStateChanged(user.UserID, new TeamVersusUserState { TeamID = bestTeam }).WaitSafely(); break; } } @@ -156,7 +157,7 @@ namespace osu.Game.Tests.Visual.Multiplayer ChangeRoomState(MultiplayerRoomState.Open); ((IMultiplayerClient)this).ResultsReady(); - FinishCurrentItem().Wait(); + FinishCurrentItem().WaitSafely(); } break; @@ -216,7 +217,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Debug.Assert(Room != null); // emulate the server sending this after the join room. scheduler required to make sure the join room event is fired first (in Join). - changeMatchType(Room.Settings.MatchType).Wait(); + changeMatchType(Room.Settings.MatchType).WaitSafely(); RoomJoined = true; } From 73b40e68337dd791c2c9bffc8c60521dd3c32958 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 3 Jan 2022 17:31:12 +0900 Subject: [PATCH 076/996] Replace usage of `.Result` with `.WaitSafelyForResult` --- CodeAnalysis/BannedSymbols.txt | 1 + .../Beatmaps/TestSceneBeatmapDifficultyCache.cs | 3 ++- osu.Game.Tests/NonVisual/TaskChainTest.cs | 11 ++++++----- osu.Game.Tests/Skins/IO/ImportSkinTest.cs | 5 +++-- .../Skins/TestSceneBeatmapSkinResources.cs | 3 ++- osu.Game.Tests/Skins/TestSceneSkinResources.cs | 3 ++- .../Visual/Editing/TestSceneDifficultySwitching.cs | 3 ++- .../Visual/Editing/TestSceneEditorTestGameplay.cs | 3 ++- .../Gameplay/TestSceneReplayDownloadButton.cs | 3 ++- .../Visual/Gameplay/TestSceneSpectator.cs | 3 ++- .../Visual/Menus/TestSceneMusicActionHandling.cs | 2 +- .../Multiplayer/TestSceneMultiSpectatorScreen.cs | 3 ++- .../TestSceneMultiplayerGameplayLeaderboard.cs | 5 +++-- ...TestSceneMultiplayerGameplayLeaderboardTeams.cs | 3 ++- .../Visual/Navigation/TestScenePresentBeatmap.cs | 3 ++- .../Visual/Navigation/TestScenePresentScore.cs | 5 +++-- .../Playlists/TestScenePlaylistsRoomCreation.cs | 2 +- .../SongSelect/TestSceneBeatmapRecommendations.cs | 3 ++- .../Visual/SongSelect/TestScenePlaySongSelect.cs | 2 +- .../UserInterface/TestSceneDeleteLocalScore.cs | 5 +++-- .../TestSceneUpdateableBeatmapBackgroundSprite.cs | 3 ++- osu.Game/Beatmaps/BeatmapDifficultyCache.cs | 8 ++++++-- osu.Game/Beatmaps/BeatmapManager.cs | 3 ++- osu.Game/Beatmaps/WorkingBeatmap.cs | 3 ++- osu.Game/Database/OnlineLookupCache.cs | 3 ++- osu.Game/Online/Chat/ChannelManager.cs | 8 ++++---- .../Overlays/BeatmapSet/Scores/ScoresContainer.cs | 9 ++++++--- .../Overlays/Dashboard/CurrentlyPlayingDisplay.cs | 11 +++++++---- .../Settings/Sections/General/UpdateSettings.cs | 5 +++-- osu.Game/Scoring/ScoreManager.cs | 3 ++- osu.Game/Screens/Menu/IntroScreen.cs | 3 ++- .../Play/HUD/MultiplayerGameplayLeaderboard.cs | 7 +++++-- .../Screens/Play/HUD/PerformancePointsCounter.cs | 7 +++++-- osu.Game/Screens/Play/Player.cs | 3 ++- .../Ranking/Expanded/ExpandedPanelMiddleContent.cs | 3 ++- .../Expanded/Statistics/PerformanceStatistic.cs | 3 ++- osu.Game/Screens/Ranking/ScorePanelList.cs | 5 +++-- osu.Game/Screens/Select/Details/AdvancedStats.cs | 14 +++++++++----- .../Select/Leaderboards/BeatmapLeaderboard.cs | 7 ++++--- osu.Game/Screens/Spectate/SpectatorScreen.cs | 7 +++++-- osu.Game/Skinning/SkinManager.cs | 3 ++- 41 files changed, 121 insertions(+), 68 deletions(-) diff --git a/CodeAnalysis/BannedSymbols.txt b/CodeAnalysis/BannedSymbols.txt index 2048714cb3..4428656301 100644 --- a/CodeAnalysis/BannedSymbols.txt +++ b/CodeAnalysis/BannedSymbols.txt @@ -14,3 +14,4 @@ M:Realms.IRealmCollection`1.SubscribeForNotifications`1(Realms.NotificationCallb M:Realms.CollectionExtensions.SubscribeForNotifications`1(System.Linq.IQueryable{``0},Realms.NotificationCallbackDelegate{``0});Use osu.Game.Database.RealmObjectExtensions.QueryAsyncWithNotifications(IQueryable,NotificationCallbackDelegate) instead. M:Realms.CollectionExtensions.SubscribeForNotifications`1(System.Collections.Generic.IList{``0},Realms.NotificationCallbackDelegate{``0});Use osu.Game.Database.RealmObjectExtensions.QueryAsyncWithNotifications(IList,NotificationCallbackDelegate) instead. M:System.Threading.Tasks.Task.Wait();Don't use Task.Wait. Use Task.WaitSafely() to ensure we avoid deadlocks. +P:System.Threading.Tasks.Task`1.Result;Don't use Task.Result. Use Task.WaitSafelyForResult() to ensure we avoid deadlocks. diff --git a/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs b/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs index 3a82cbc785..a97b372db0 100644 --- a/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs +++ b/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Rulesets; @@ -32,7 +33,7 @@ namespace osu.Game.Tests.Beatmaps [BackgroundDependencyLoader] private void load(OsuGameBase osu) { - importedSet = ImportBeatmapTest.LoadQuickOszIntoOsu(osu).Result; + importedSet = ImportBeatmapTest.LoadQuickOszIntoOsu(osu).WaitSafelyForResult(); } [SetUpSteps] diff --git a/osu.Game.Tests/NonVisual/TaskChainTest.cs b/osu.Game.Tests/NonVisual/TaskChainTest.cs index d83eaafe20..db3cecf8ea 100644 --- a/osu.Game.Tests/NonVisual/TaskChainTest.cs +++ b/osu.Game.Tests/NonVisual/TaskChainTest.cs @@ -4,6 +4,7 @@ using System.Threading; using System.Threading.Tasks; using NUnit.Framework; +using osu.Framework.Extensions; using osu.Game.Utils; namespace osu.Game.Tests.NonVisual @@ -42,9 +43,9 @@ namespace osu.Game.Tests.NonVisual await Task.WhenAll(task1.task, task2.task, task3.task); - Assert.That(task1.task.Result, Is.EqualTo(1)); - Assert.That(task2.task.Result, Is.EqualTo(2)); - Assert.That(task3.task.Result, Is.EqualTo(3)); + Assert.That(task1.task.WaitSafelyForResult(), Is.EqualTo(1)); + Assert.That(task2.task.WaitSafelyForResult(), Is.EqualTo(2)); + Assert.That(task3.task.WaitSafelyForResult(), Is.EqualTo(3)); } [Test] @@ -68,9 +69,9 @@ namespace osu.Game.Tests.NonVisual // Wait on both tasks. await Task.WhenAll(task1.task, task3.task); - Assert.That(task1.task.Result, Is.EqualTo(1)); + Assert.That(task1.task.WaitSafelyForResult(), Is.EqualTo(1)); Assert.That(task2.task.IsCompleted, Is.False); - Assert.That(task3.task.Result, Is.EqualTo(2)); + Assert.That(task3.task.WaitSafelyForResult(), Is.EqualTo(2)); } [Test] diff --git a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs index 40d2455106..55a50fb825 100644 --- a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs +++ b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs @@ -8,6 +8,7 @@ using System.Runtime.CompilerServices; using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Extensions; using osu.Framework.Platform; using osu.Game.Database; using osu.Game.IO; @@ -187,7 +188,7 @@ namespace osu.Game.Tests.Skins.IO var imported = skinManager.Import(new ImportTask(exportStream, "exported.osk")); - imported.Result.PerformRead(s => + imported.WaitSafelyForResult().PerformRead(s => { Assert.IsFalse(s.Protected); Assert.AreNotEqual(originalSkinId, s.ID); @@ -222,7 +223,7 @@ namespace osu.Game.Tests.Skins.IO var imported = skinManager.Import(new ImportTask(exportStream, "exported.osk")); - imported.Result.PerformRead(s => + imported.WaitSafelyForResult().PerformRead(s => { Assert.IsFalse(s.Protected); Assert.AreNotEqual(originalSkinId, s.ID); diff --git a/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs b/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs index 1d8b754837..a4c2ec3b34 100644 --- a/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs +++ b/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs @@ -4,6 +4,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio.Track; +using osu.Framework.Extensions; using osu.Framework.Testing; using osu.Game.Audio; using osu.Game.Beatmaps; @@ -24,7 +25,7 @@ namespace osu.Game.Tests.Skins [BackgroundDependencyLoader] private void load() { - var imported = beatmaps.Import(new ZipArchiveReader(TestResources.OpenResource("Archives/ogg-beatmap.osz"))).Result; + var imported = beatmaps.Import(new ZipArchiveReader(TestResources.OpenResource("Archives/ogg-beatmap.osz"))).WaitSafelyForResult(); beatmap = beatmaps.GetWorkingBeatmap(imported.Value.Beatmaps[0]); beatmap.LoadTrack(); } diff --git a/osu.Game.Tests/Skins/TestSceneSkinResources.cs b/osu.Game.Tests/Skins/TestSceneSkinResources.cs index 09535b76e3..a10e2436d6 100644 --- a/osu.Game.Tests/Skins/TestSceneSkinResources.cs +++ b/osu.Game.Tests/Skins/TestSceneSkinResources.cs @@ -3,6 +3,7 @@ using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Extensions; using osu.Framework.Testing; using osu.Game.Audio; using osu.Game.IO.Archives; @@ -23,7 +24,7 @@ namespace osu.Game.Tests.Skins [BackgroundDependencyLoader] private void load() { - var imported = skins.Import(new ZipArchiveReader(TestResources.OpenResource("Archives/ogg-skin.osk"))).Result; + var imported = skins.Import(new ZipArchiveReader(TestResources.OpenResource("Archives/ogg-skin.osk"))).WaitSafelyForResult(); skin = imported.PerformRead(skinInfo => skins.GetSkin(skinInfo)); } diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs b/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs index c23db5e440..5dd5bad0d2 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs @@ -5,6 +5,7 @@ using System; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Extensions; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Overlays.Dialog; @@ -32,7 +33,7 @@ namespace osu.Game.Tests.Visual.Editing public override void SetUpSteps() { - AddStep("import test beatmap", () => importedBeatmapSet = ImportBeatmapTest.LoadOszIntoOsu(game, virtualTrack: true).Result); + AddStep("import test beatmap", () => importedBeatmapSet = ImportBeatmapTest.LoadOszIntoOsu(game, virtualTrack: true).WaitSafelyForResult()); base.SetUpSteps(); } diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs index 50794f15ed..fcbae44c76 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs @@ -5,6 +5,7 @@ using System; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Extensions; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; @@ -38,7 +39,7 @@ namespace osu.Game.Tests.Visual.Editing public override void SetUpSteps() { - AddStep("import test beatmap", () => importedBeatmapSet = ImportBeatmapTest.LoadOszIntoOsu(game).Result); + AddStep("import test beatmap", () => importedBeatmapSet = ImportBeatmapTest.LoadOszIntoOsu(game).WaitSafelyForResult()); base.SetUpSteps(); } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs index 42c4f89e9d..7f5ac5cf48 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs @@ -9,6 +9,7 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Scoring; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Testing; using osu.Game.Database; using osu.Game.Graphics.UserInterface; @@ -135,7 +136,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("state is not downloaded", () => downloadButton.State.Value == DownloadState.NotDownloaded); - AddStep("import score", () => imported = scoreManager.Import(getScoreInfo(true)).Result); + AddStep("import score", () => imported = scoreManager.Import(getScoreInfo(true)).WaitSafelyForResult()); AddUntilStep("state is available", () => downloadButton.State.Value == DownloadState.LocallyAvailable); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index 9fadbe02bd..90065e3b51 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -4,6 +4,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Screens; using osu.Framework.Testing; @@ -60,7 +61,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("import beatmap", () => { - importedBeatmap = ImportBeatmapTest.LoadOszIntoOsu(game, virtualTrack: true).Result; + importedBeatmap = ImportBeatmapTest.LoadOszIntoOsu(game, virtualTrack: true).WaitSafelyForResult(); importedBeatmapId = importedBeatmap.Beatmaps.First(b => b.RulesetID == 0).OnlineID ?? -1; }); } diff --git a/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs b/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs index 425e7718dc..c82bde3444 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs @@ -38,7 +38,7 @@ namespace osu.Game.Tests.Visual.Menus AddStep("import beatmap with track", () => { - var setWithTrack = Game.BeatmapManager.Import(new ImportTask(TestResources.GetTestBeatmapForImport())).Result; + var setWithTrack = Game.BeatmapManager.Import(new ImportTask(TestResources.GetTestBeatmapForImport())).WaitSafelyForResult(); Beatmap.Value = Game.BeatmapManager.GetWorkingBeatmap(setWithTrack.Value.Beatmaps.First()); }); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index b10856b704..3f24da2c9e 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Extensions; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -46,7 +47,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load() { - importedSet = ImportBeatmapTest.LoadOszIntoOsu(game, virtualTrack: true).Result; + importedSet = ImportBeatmapTest.LoadOszIntoOsu(game, virtualTrack: true).WaitSafelyForResult(); importedBeatmap = importedSet.Beatmaps.First(b => b.RulesetID == 0); importedBeatmapId = importedBeatmap.OnlineID ?? -1; } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs index 25200560e4..6b40a4fc9a 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Extensions; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Utils; @@ -43,7 +44,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { base.SetUpSteps(); - AddStep("set local user", () => ((DummyAPIAccess)API).LocalUser.Value = LookupCache.GetUserAsync(1).Result); + AddStep("set local user", () => ((DummyAPIAccess)API).LocalUser.Value = LookupCache.GetUserAsync(1).WaitSafelyForResult()); AddStep("create leaderboard", () => { @@ -90,7 +91,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public void TestUserQuit() { foreach (int user in users) - AddStep($"mark user {user} quit", () => Client.RemoveUser(LookupCache.GetUserAsync(user).Result.AsNonNull())); + AddStep($"mark user {user} quit", () => Client.RemoveUser(LookupCache.GetUserAsync(user).WaitSafelyForResult().AsNonNull())); } [Test] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs index 16a342df8c..f5c079573d 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Utils; using osu.Game.Online.API; @@ -47,7 +48,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { base.SetUpSteps(); - AddStep("set local user", () => ((DummyAPIAccess)API).LocalUser.Value = LookupCache.GetUserAsync(1).Result); + AddStep("set local user", () => ((DummyAPIAccess)API).LocalUser.Value = LookupCache.GetUserAsync(1).WaitSafelyForResult()); AddStep("create leaderboard", () => { diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs b/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs index 3cb1ea85a9..9d495f7d05 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs @@ -4,6 +4,7 @@ using System; using System.Linq; using NUnit.Framework; +using osu.Framework.Extensions; using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Extensions; @@ -126,7 +127,7 @@ namespace osu.Game.Tests.Visual.Navigation Ruleset = ruleset ?? new OsuRuleset().RulesetInfo }, } - }).Result.Value; + }).WaitSafelyForResult().Value; }); AddAssert($"import {i} succeeded", () => imported != null); diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs index 7ee593de00..16c416fedc 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs @@ -4,6 +4,7 @@ using System; using System.Linq; using NUnit.Framework; +using osu.Framework.Extensions; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; @@ -57,7 +58,7 @@ namespace osu.Game.Tests.Visual.Navigation Ruleset = new OsuRuleset().RulesetInfo }, } - }).Result.Value; + }).WaitSafelyForResult().Value; }); } @@ -131,7 +132,7 @@ namespace osu.Game.Tests.Visual.Navigation OnlineID = i, BeatmapInfo = beatmap.Beatmaps.First(), Ruleset = ruleset ?? new OsuRuleset().RulesetInfo - }).Result.Value; + }).WaitSafelyForResult().Value; }); AddAssert($"import {i} succeeded", () => imported != null); diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs index f3d00dc076..0c5985ad67 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs @@ -202,7 +202,7 @@ namespace osu.Game.Tests.Visual.Playlists }); } - private void importBeatmap() => AddStep("import beatmap", () => importedBeatmap = manager.Import(CreateBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo.BeatmapSet).Result); + private void importBeatmap() => AddStep("import beatmap", () => importedBeatmap = manager.Import(CreateBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo.BeatmapSet).WaitSafelyForResult()); private class TestPlaylistsRoomSubScreen : PlaylistsRoomSubScreen { diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs index ef11ad4153..071e3225f0 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; +using osu.Framework.Extensions; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Extensions; @@ -181,7 +182,7 @@ namespace osu.Game.Tests.Visual.SongSelect beatmap.DifficultyName = $"SR{i + 1}"; } - return Game.BeatmapManager.Import(beatmapSet).Result.Value; + return Game.BeatmapManager.Import(beatmapSet).WaitSafelyForResult().Value; } private bool ensureAllBeatmapSetsImported(IEnumerable beatmapSets) => beatmapSets.All(set => set != null); diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index bd5aeb7e08..ea667d02f3 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -760,7 +760,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("import huge difficulty count map", () => { var usableRulesets = rulesets.AvailableRulesets.Where(r => r.OnlineID != 2).ToArray(); - imported = manager.Import(TestResources.CreateTestBeatmapSetInfo(50, usableRulesets)).Result.Value; + imported = manager.Import(TestResources.CreateTestBeatmapSetInfo(50, usableRulesets)).WaitSafelyForResult().Value; }); AddStep("select the first beatmap of import", () => Beatmap.Value = manager.GetWorkingBeatmap(imported.Beatmaps.First())); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index 2363bbbfcf..bca396b46b 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -7,6 +7,7 @@ using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Allocation; using osu.Framework.Audio; +using osu.Framework.Extensions; using osu.Framework.Graphics.Cursor; using osu.Framework.Platform; using osu.Framework.Testing; @@ -85,7 +86,7 @@ namespace osu.Game.Tests.Visual.UserInterface dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesetStore, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default)); dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, ContextFactory, Scheduler)); - beatmapInfo = beatmapManager.Import(new ImportTask(TestResources.GetQuickTestBeatmapForImport())).Result.Value.Beatmaps[0]; + beatmapInfo = beatmapManager.Import(new ImportTask(TestResources.GetQuickTestBeatmapForImport())).WaitSafelyForResult().Value.Beatmaps[0]; for (int i = 0; i < 50; i++) { @@ -101,7 +102,7 @@ namespace osu.Game.Tests.Visual.UserInterface User = new APIUser { Username = "TestUser" }, }; - importedScores.Add(scoreManager.Import(score).Result.Value); + importedScores.Add(scoreManager.Import(score).WaitSafelyForResult().Value); } return dependencies; diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs index 3fa9b8b877..f78cbdffc1 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; @@ -31,7 +32,7 @@ namespace osu.Game.Tests.Visual.UserInterface { this.api = api; - testBeatmap = ImportBeatmapTest.LoadOszIntoOsu(osu).Result; + testBeatmap = ImportBeatmapTest.LoadOszIntoOsu(osu).WaitSafelyForResult(); } [Test] diff --git a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs index 119906cadc..0e4dab2b96 100644 --- a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs +++ b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Lists; using osu.Framework.Logging; using osu.Framework.Threading; @@ -266,8 +267,11 @@ namespace osu.Game.Beatmaps // We're on a threadpool thread, but we should exit back to the update thread so consumers can safely handle value-changed events. Schedule(() => { - if (!cancellationToken.IsCancellationRequested && t.Result != null) - bindable.Value = t.Result; + if (cancellationToken.IsCancellationRequested) + return; + + if (t.WaitSafelyForResult() is StarDifficulty sd) + bindable.Value = sd; }); }, cancellationToken); } diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 8502b91096..c461135266 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -11,6 +11,7 @@ using System.Threading.Tasks; using JetBrains.Annotations; using osu.Framework.Audio; using osu.Framework.Audio.Track; +using osu.Framework.Extensions; using osu.Framework.IO.Stores; using osu.Framework.Platform; using osu.Framework.Testing; @@ -91,7 +92,7 @@ namespace osu.Game.Beatmaps } }; - var imported = beatmapModelManager.Import(set).Result.Value; + var imported = beatmapModelManager.Import(set).WaitSafelyForResult().Value; return GetWorkingBeatmap(imported.Beatmaps.First()); } diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 451b4ccac8..85e30b6e3d 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -11,6 +11,7 @@ using System.Threading.Tasks; using JetBrains.Annotations; using osu.Framework.Audio; using osu.Framework.Audio.Track; +using osu.Framework.Extensions; using osu.Framework.Graphics.Textures; using osu.Framework.Logging; using osu.Framework.Testing; @@ -185,7 +186,7 @@ namespace osu.Game.Beatmaps { try { - return loadBeatmapAsync().Result; + return loadBeatmapAsync().WaitSafelyForResult(); } catch (AggregateException ae) { diff --git a/osu.Game/Database/OnlineLookupCache.cs b/osu.Game/Database/OnlineLookupCache.cs index 5eb9fa24fa..b392336bb4 100644 --- a/osu.Game/Database/OnlineLookupCache.cs +++ b/osu.Game/Database/OnlineLookupCache.cs @@ -8,6 +8,7 @@ using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; using osu.Framework.Allocation; +using osu.Framework.Extensions; using osu.Game.Online.API; namespace osu.Game.Database @@ -58,7 +59,7 @@ namespace osu.Game.Database if (!task.IsCompletedSuccessfully) return null; - return task.Result; + return task.WaitSafelyForResult(); }, token)); } diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 2d7a0bc9fc..3b27dd5c5d 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Logging; using osu.Game.Database; using osu.Game.Input; @@ -533,11 +534,10 @@ namespace osu.Game.Online.Chat else if (lastClosedChannel.Type == ChannelType.PM) { // Try to get user in order to open PM chat - users.GetUserAsync((int)lastClosedChannel.Id).ContinueWith(u => + users.GetUserAsync((int)lastClosedChannel.Id).ContinueWith(task => { - if (u.Result == null) return; - - Schedule(() => CurrentChannel.Value = JoinChannel(new Channel(u.Result))); + if (task.WaitSafelyForResult() is APIUser u) + Schedule(() => CurrentChannel.Value = JoinChannel(new Channel(u))); }); } diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 695661d5c9..adb1adeed4 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -7,6 +7,7 @@ using System.Threading; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -79,14 +80,16 @@ namespace osu.Game.Overlays.BeatmapSet.Scores }; scoreManager.OrderByTotalScoreAsync(value.Scores.Select(s => s.CreateScoreInfo(rulesets, beatmapInfo)).ToArray(), loadCancellationSource.Token) - .ContinueWith(ordered => Schedule(() => + .ContinueWith(task => Schedule(() => { if (loadCancellationSource.IsCancellationRequested) return; - var topScore = ordered.Result.First(); + var scores = task.WaitSafelyForResult(); - scoreTable.DisplayScores(ordered.Result, apiBeatmap.Status.GrantsPerformancePoints()); + var topScore = scores.First(); + + scoreTable.DisplayScores(scores, apiBeatmap.Status.GrantsPerformancePoints()); scoreTable.Show(); var userScore = value.UserScore; diff --git a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs index 0844975906..b7d236c263 100644 --- a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs +++ b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs @@ -5,6 +5,7 @@ using System.Collections.Specialized; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Screens; @@ -61,17 +62,19 @@ namespace osu.Game.Overlays.Dashboard case NotifyCollectionChangedAction.Add: foreach (int id in e.NewItems.OfType().ToArray()) { - users.GetUserAsync(id).ContinueWith(u => + users.GetUserAsync(id).ContinueWith(task => { - if (u.Result == null) return; + var user = task.WaitSafelyForResult(); + + if (user == null) return; Schedule(() => { // user may no longer be playing. - if (!playingUsers.Contains(u.Result.Id)) + if (!playingUsers.Contains(user.Id)) return; - userFlow.Add(createUserPanel(u.Result)); + userFlow.Add(createUserPanel(user)); }); }); } diff --git a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs index 6bcb5ef715..49eb80d993 100644 --- a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using osu.Framework; using osu.Framework.Allocation; +using osu.Framework.Extensions; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; using osu.Framework.Platform; @@ -45,9 +46,9 @@ namespace osu.Game.Overlays.Settings.Sections.General Action = () => { checkForUpdatesButton.Enabled.Value = false; - Task.Run(updateManager.CheckForUpdateAsync).ContinueWith(t => Schedule(() => + Task.Run(updateManager.CheckForUpdateAsync).ContinueWith(task => Schedule(() => { - if (!t.Result) + if (!task.WaitSafelyForResult()) { notifications?.Post(new SimpleNotification { diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 6de6b57066..9715cd64a7 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -9,6 +9,7 @@ using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Platform; using osu.Framework.Threading; using osu.Game.Beatmaps; @@ -112,7 +113,7 @@ namespace osu.Game.Scoring public void GetTotalScore([NotNull] ScoreInfo score, [NotNull] Action callback, ScoringMode mode = ScoringMode.Standardised, CancellationToken cancellationToken = default) { GetTotalScoreAsync(score, mode, cancellationToken) - .ContinueWith(s => scheduler.Add(() => callback(s.Result)), TaskContinuationOptions.OnlyOnRanToCompletion); + .ContinueWith(task => scheduler.Add(() => callback(task.WaitSafelyForResult())), TaskContinuationOptions.OnlyOnRanToCompletion); } /// diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index eb8d3dfea6..e7731631ff 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -9,6 +9,7 @@ using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Audio.Track; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Screens; using osu.Framework.Utils; @@ -110,7 +111,7 @@ namespace osu.Game.Screens.Menu { // if we detect that the theme track or beatmap is unavailable this is either first startup or things are in a bad state. // this could happen if a user has nuked their files store. for now, reimport to repair this. - var import = beatmaps.Import(new ZipArchiveReader(game.Resources.GetStream($"Tracks/{BeatmapFile}"), BeatmapFile)).Result; + var import = beatmaps.Import(new ZipArchiveReader(game.Resources.GetStream($"Tracks/{BeatmapFile}"), BeatmapFile)).WaitSafelyForResult(); import.PerformWrite(b => { diff --git a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs index e019ee9a3d..6f2a76b3cb 100644 --- a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs @@ -7,6 +7,7 @@ using System.Collections.Specialized; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Extensions.Color4Extensions; using osu.Game.Configuration; using osu.Game.Database; @@ -76,9 +77,11 @@ namespace osu.Game.Screens.Play.HUD TeamScores.Add(team, new BindableInt()); } - userLookupCache.GetUsersAsync(playingUsers.Select(u => u.UserID).ToArray()).ContinueWith(users => Schedule(() => + userLookupCache.GetUsersAsync(playingUsers.Select(u => u.UserID).ToArray()).ContinueWith(task => Schedule(() => { - foreach (var user in users.Result) + var users = task.WaitSafelyForResult(); + + foreach (var user in users) { if (user == null) continue; diff --git a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs index 908e6a27b8..f543cea15c 100644 --- a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs +++ b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio.Track; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; @@ -73,10 +74,12 @@ namespace osu.Game.Screens.Play.HUD var gameplayWorkingBeatmap = new GameplayWorkingBeatmap(gameplayState.Beatmap); difficultyCache.GetTimedDifficultyAttributesAsync(gameplayWorkingBeatmap, gameplayState.Ruleset, clonedMods, loadCancellationSource.Token) - .ContinueWith(r => Schedule(() => + .ContinueWith(task => Schedule(() => { - timedAttributes = r.Result; + timedAttributes = task.WaitSafelyForResult(); + IsValid = true; + if (lastJudgement != null) onJudgementChanged(lastJudgement); }), TaskContinuationOptions.OnlyOnRanToCompletion); diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 77a6b27114..262424258a 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -9,6 +9,7 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; @@ -770,7 +771,7 @@ namespace osu.Game.Screens.Play // This player instance may already be in the process of exiting. return; - this.Push(CreateResults(prepareScoreForDisplayTask.Result)); + this.Push(CreateResults(prepareScoreForDisplayTask.WaitSafelyForResult())); }, Time.Current + delay, 50); Scheduler.Add(resultsDisplayDelegate); diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs index 3ab2658f97..87940c61aa 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Localisation; @@ -79,7 +80,7 @@ namespace osu.Game.Screens.Ranking.Expanded statisticDisplays.AddRange(topStatistics); statisticDisplays.AddRange(bottomStatistics); - var starDifficulty = beatmapDifficultyCache.GetDifficultyAsync(beatmap, score.Ruleset, score.Mods).Result; + var starDifficulty = beatmapDifficultyCache.GetDifficultyAsync(beatmap, score.Ruleset, score.Mods).WaitSafelyForResult(); AddInternal(new FillFlowContainer { diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs index 68da4ec724..90ecd3642b 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs @@ -5,6 +5,7 @@ using System; using System.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Game.Graphics.UserInterface; using osu.Game.Scoring; @@ -37,7 +38,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics else { performanceCache.CalculatePerformanceAsync(score, cancellationTokenSource.Token) - .ContinueWith(t => Schedule(() => setPerformanceValue(t.Result)), cancellationTokenSource.Token); + .ContinueWith(t => Schedule(() => setPerformanceValue(t.WaitSafelyForResult())), cancellationTokenSource.Token); } } diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index f3de48dcf0..b29f2302d5 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; @@ -151,9 +152,9 @@ namespace osu.Game.Screens.Ranking // Calculating score can take a while in extreme scenarios, so only display scores after the process completes. scoreManager.GetTotalScoreAsync(score) - .ContinueWith(totalScore => Schedule(() => + .ContinueWith(task => Schedule(() => { - flow.SetLayoutPosition(trackingContainer, totalScore.Result); + flow.SetLayoutPosition(trackingContainer, task.WaitSafelyForResult()); trackingContainer.Show(); diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index edbaba40bc..e7764b23be 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -16,6 +16,7 @@ using osu.Game.Rulesets.Mods; using System.Linq; using System.Threading; using System.Threading.Tasks; +using osu.Framework.Extensions; using osu.Framework.Localisation; using osu.Framework.Threading; using osu.Framework.Utils; @@ -147,15 +148,18 @@ namespace osu.Game.Screens.Select.Details starDifficultyCancellationSource = new CancellationTokenSource(); - var normalStarDifficulty = difficultyCache.GetDifficultyAsync(BeatmapInfo, ruleset.Value, null, starDifficultyCancellationSource.Token); - var moddedStarDifficulty = difficultyCache.GetDifficultyAsync(BeatmapInfo, ruleset.Value, mods.Value, starDifficultyCancellationSource.Token); + var normalStarDifficultyTask = difficultyCache.GetDifficultyAsync(BeatmapInfo, ruleset.Value, null, starDifficultyCancellationSource.Token); + var moddedStarDifficultyTask = difficultyCache.GetDifficultyAsync(BeatmapInfo, ruleset.Value, mods.Value, starDifficultyCancellationSource.Token); - Task.WhenAll(normalStarDifficulty, moddedStarDifficulty).ContinueWith(_ => Schedule(() => + Task.WhenAll(normalStarDifficultyTask, moddedStarDifficultyTask).ContinueWith(_ => Schedule(() => { - if (normalStarDifficulty.Result == null || moddedStarDifficulty.Result == null) + var normalDifficulty = normalStarDifficultyTask.WaitSafelyForResult(); + var moddeDifficulty = moddedStarDifficultyTask.WaitSafelyForResult(); + + if (normalDifficulty == null || moddeDifficulty == null) return; - starDifficulty.Value = ((float)normalStarDifficulty.Result.Value.Stars, (float)moddedStarDifficulty.Result.Value.Stars); + starDifficulty.Value = ((float)normalDifficulty.Value.Stars, (float)moddeDifficulty.Value.Stars); }), starDifficultyCancellationSource.Token, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Current); } diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 29c8907526..6943f2a3b6 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -8,6 +8,7 @@ using System.Threading; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Online.API.Requests; @@ -142,7 +143,7 @@ namespace osu.Game.Screens.Select.Leaderboards } scoreManager.OrderByTotalScoreAsync(scores.ToArray(), cancellationToken) - .ContinueWith(ordered => scoresCallback?.Invoke(ordered.Result), TaskContinuationOptions.OnlyOnRanToCompletion); + .ContinueWith(ordered => scoresCallback?.Invoke(ordered.WaitSafelyForResult()), TaskContinuationOptions.OnlyOnRanToCompletion); return null; } @@ -178,12 +179,12 @@ namespace osu.Game.Screens.Select.Leaderboards req.Success += r => { scoreManager.OrderByTotalScoreAsync(r.Scores.Select(s => s.CreateScoreInfo(rulesets, fetchBeatmapInfo)).ToArray(), cancellationToken) - .ContinueWith(ordered => Schedule(() => + .ContinueWith(task => Schedule(() => { if (cancellationToken.IsCancellationRequested) return; - scoresCallback?.Invoke(ordered.Result); + scoresCallback?.Invoke(task.WaitSafelyForResult()); TopScore = r.UserScore?.CreateScoreInfo(rulesets, fetchBeatmapInfo); }), TaskContinuationOptions.OnlyOnRanToCompletion); }; diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs index ca56366927..f361b0cee4 100644 --- a/osu.Game/Screens/Spectate/SpectatorScreen.cs +++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs @@ -7,6 +7,7 @@ using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Extensions.ObjectExtensions; using osu.Game.Beatmaps; using osu.Game.Database; @@ -57,9 +58,11 @@ namespace osu.Game.Screens.Spectate { base.LoadComplete(); - userLookupCache.GetUsersAsync(users.ToArray()).ContinueWith(users => Schedule(() => + userLookupCache.GetUsersAsync(users.ToArray()).ContinueWith(task => Schedule(() => { - foreach (var u in users.Result) + var foundUsers = task.WaitSafelyForResult(); + + foreach (var u in foundUsers) { if (u == null) continue; diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index bb2f0a37b4..a55cc332c5 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -11,6 +11,7 @@ using JetBrains.Annotations; using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.OpenGL.Textures; using osu.Framework.Graphics.Textures; @@ -153,7 +154,7 @@ namespace osu.Game.Skinning Name = s.Name + @" (modified)", Creator = s.Creator, InstantiationInfo = s.InstantiationInfo, - }).Result; + }).WaitSafelyForResult(); if (result != null) { From f3e889d0f612ca15db77853bf283087f77b9389c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 3 Jan 2022 17:51:56 +0900 Subject: [PATCH 077/996] Fix incorrect nested result retrieval in `ImportBeatmapTest` --- .../Beatmaps/IO/ImportBeatmapTest.cs | 54 ++++++++++--------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 9f3709f7a3..2a2cc2e086 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -93,7 +93,7 @@ namespace osu.Game.Tests.Beatmaps.IO using (var stream = File.OpenRead(tempPath)) { importedSet = await manager.Import(new ImportTask(stream, Path.GetFileName(tempPath))); - ensureLoaded(osu); + await ensureLoaded(osu); } Assert.IsTrue(File.Exists(tempPath), "Stream source file somehow went missing"); @@ -171,7 +171,7 @@ namespace osu.Game.Tests.Beatmaps.IO var importedSecondTime = await osu.Dependencies.Get().Import(new ImportTask(temp)); - ensureLoaded(osu); + await ensureLoaded(osu); // but contents doesn't, so existing should still be used. Assert.IsTrue(imported.ID == importedSecondTime.Value.ID); @@ -225,7 +225,7 @@ namespace osu.Game.Tests.Beatmaps.IO var importedSecondTime = await osu.Dependencies.Get().Import(new ImportTask(temp)); - ensureLoaded(osu); + await ensureLoaded(osu); // check the newly "imported" beatmap is not the original. Assert.IsTrue(imported.ID != importedSecondTime.Value.ID); @@ -277,7 +277,7 @@ namespace osu.Game.Tests.Beatmaps.IO var importedSecondTime = await osu.Dependencies.Get().Import(new ImportTask(temp)); - ensureLoaded(osu); + await ensureLoaded(osu); // check the newly "imported" beatmap is not the original. Assert.IsTrue(imported.ID != importedSecondTime.Value.ID); @@ -328,7 +328,7 @@ namespace osu.Game.Tests.Beatmaps.IO var importedSecondTime = await osu.Dependencies.Get().Import(new ImportTask(temp)); - ensureLoaded(osu); + await ensureLoaded(osu); // check the newly "imported" beatmap is not the original. Assert.IsTrue(imported.ID != importedSecondTime.Value.ID); @@ -637,7 +637,7 @@ namespace osu.Game.Tests.Beatmaps.IO if (!importer.ImportAsync(temp).Wait(10000)) Assert.Fail(@"IPC took too long to send"); - ensureLoaded(osu); + ensureLoaded(osu).WaitSafely(); waitForOrAssert(() => !File.Exists(temp), "Temporary still exists after IPC import", 5000); } @@ -659,7 +659,7 @@ namespace osu.Game.Tests.Beatmaps.IO string temp = TestResources.GetTestBeatmapForImport(); using (File.OpenRead(temp)) await osu.Dependencies.Get().Import(temp); - ensureLoaded(osu); + await ensureLoaded(osu); File.Delete(temp); Assert.IsFalse(File.Exists(temp), "We likely held a read lock on the file when we shouldn't"); } @@ -698,7 +698,7 @@ namespace osu.Game.Tests.Beatmaps.IO await osu.Dependencies.Get().Import(temp); - ensureLoaded(osu); + await ensureLoaded(osu); } finally { @@ -741,7 +741,7 @@ namespace osu.Game.Tests.Beatmaps.IO var imported = await osu.Dependencies.Get().Import(new ImportTask(temp)); - ensureLoaded(osu); + await ensureLoaded(osu); Assert.IsFalse(imported.Value.Files.Any(f => f.Filename.Contains("subfolder")), "Files contain common subfolder"); } @@ -794,7 +794,7 @@ namespace osu.Game.Tests.Beatmaps.IO var imported = await osu.Dependencies.Get().Import(new ImportTask(temp)); - ensureLoaded(osu); + await ensureLoaded(osu); Assert.IsFalse(imported.Value.Files.Any(f => f.Filename.Contains("__MACOSX")), "Files contain resource fork folder, which should be ignored"); Assert.IsFalse(imported.Value.Files.Any(f => f.Filename.Contains("actual_data")), "Files contain common subfolder"); @@ -812,7 +812,7 @@ namespace osu.Game.Tests.Beatmaps.IO } [Test] - public async Task TestUpdateBeatmapInfo() + public void TestUpdateBeatmapInfo() { using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { @@ -822,7 +822,8 @@ namespace osu.Game.Tests.Beatmaps.IO var manager = osu.Dependencies.Get(); string temp = TestResources.GetTestBeatmapForImport(); - await osu.Dependencies.Get().Import(temp); + + osu.Dependencies.Get().Import(temp).WaitSafely(); // Update via the beatmap, not the beatmap info, to ensure correct linking BeatmapSetInfo setToUpdate = manager.GetAllUsableBeatmapSets()[0]; @@ -842,7 +843,7 @@ namespace osu.Game.Tests.Beatmaps.IO } [Test] - public async Task TestUpdateBeatmapFile() + public void TestUpdateBeatmapFile() { using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { @@ -852,7 +853,8 @@ namespace osu.Game.Tests.Beatmaps.IO var manager = osu.Dependencies.Get(); string temp = TestResources.GetTestBeatmapForImport(); - await osu.Dependencies.Get().Import(temp); + + osu.Dependencies.Get().Import(temp).WaitSafely(); BeatmapSetInfo setToUpdate = manager.GetAllUsableBeatmapSets()[0]; @@ -976,35 +978,35 @@ namespace osu.Game.Tests.Beatmaps.IO } } - public static async Task LoadQuickOszIntoOsu(OsuGameBase osu) + public static Task LoadQuickOszIntoOsu(OsuGameBase osu) => Task.Factory.StartNew(() => { string temp = TestResources.GetQuickTestBeatmapForImport(); var manager = osu.Dependencies.Get(); - var importedSet = await manager.Import(new ImportTask(temp)).ConfigureAwait(false); + var importedSet = manager.Import(new ImportTask(temp)).WaitSafelyForResult(); - ensureLoaded(osu); + ensureLoaded(osu).WaitSafely(); waitForOrAssert(() => !File.Exists(temp), "Temporary file still exists after standard import", 5000); return manager.GetAllUsableBeatmapSets().Find(beatmapSet => beatmapSet.ID == importedSet.Value.ID); - } + }, TaskCreationOptions.LongRunning); - public static async Task LoadOszIntoOsu(OsuGameBase osu, string path = null, bool virtualTrack = false) + public static Task LoadOszIntoOsu(OsuGameBase osu, string path = null, bool virtualTrack = false) => Task.Factory.StartNew(() => { string temp = path ?? TestResources.GetTestBeatmapForImport(virtualTrack); var manager = osu.Dependencies.Get(); - var importedSet = await manager.Import(new ImportTask(temp)).ConfigureAwait(false); + var importedSet = manager.Import(new ImportTask(temp)).WaitSafelyForResult(); - ensureLoaded(osu); + ensureLoaded(osu).WaitSafely(); waitForOrAssert(() => !File.Exists(temp), "Temporary file still exists after standard import", 5000); return manager.GetAllUsableBeatmapSets().Find(beatmapSet => beatmapSet.ID == importedSet.Value.ID); - } + }, TaskCreationOptions.LongRunning); private void deleteBeatmapSet(BeatmapSetInfo imported, OsuGameBase osu) { @@ -1053,7 +1055,7 @@ namespace osu.Game.Tests.Beatmaps.IO Assert.AreEqual(expected, osu.Dependencies.Get().Get().FileInfo.Count(f => f.ReferenceCount == 1)); } - private static void ensureLoaded(OsuGameBase osu, int timeout = 60000) + private static Task ensureLoaded(OsuGameBase osu, int timeout = 60000) => Task.Factory.StartNew(() => { IEnumerable resultSets = null; var store = osu.Dependencies.Get(); @@ -1089,14 +1091,14 @@ namespace osu.Game.Tests.Beatmaps.IO Assert.IsTrue(beatmap?.HitObjects.Any() == true); beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 3))?.Beatmap; Assert.IsTrue(beatmap?.HitObjects.Any() == true); - } + }, TaskCreationOptions.LongRunning); private static void waitForOrAssert(Func result, string failureMessage, int timeout = 60000) { - Task task = Task.Run(() => + Task task = Task.Factory.StartNew(() => { while (!result()) Thread.Sleep(200); - }); + }, TaskCreationOptions.LongRunning); Assert.IsTrue(task.Wait(timeout), failureMessage); } From f9713b8895595c06b35f98a80859904ba7683739 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 3 Jan 2022 18:00:28 +0900 Subject: [PATCH 078/996] Replace usage of `TimeoutAttribute` to fix beatmap conversion test failures --- .../CatchBeatmapConversionTest.cs | 1 - .../ManiaBeatmapConversionTest.cs | 1 - .../OsuBeatmapConversionTest.cs | 1 - .../TaikoBeatmapConversionTest.cs | 1 - .../Tests/Beatmaps/BeatmapConversionTest.cs | 58 +++++++++++-------- 5 files changed, 34 insertions(+), 28 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs index be1885cfa6..baca8166d1 100644 --- a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs @@ -14,7 +14,6 @@ using osu.Game.Tests.Beatmaps; namespace osu.Game.Rulesets.Catch.Tests { [TestFixture] - [Timeout(10000)] public class CatchBeatmapConversionTest : BeatmapConversionTest { protected override string ResourceAssembly => "osu.Game.Rulesets.Catch"; diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs index 948f088b4e..837474ad9e 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs @@ -14,7 +14,6 @@ using osu.Game.Tests.Beatmaps; namespace osu.Game.Rulesets.Mania.Tests { [TestFixture] - [Timeout(10000)] public class ManiaBeatmapConversionTest : BeatmapConversionTest { protected override string ResourceAssembly => "osu.Game.Rulesets.Mania"; diff --git a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs index 5f44e1b6b6..4c11efcc7c 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs @@ -12,7 +12,6 @@ using osu.Game.Tests.Beatmaps; namespace osu.Game.Rulesets.Osu.Tests { [TestFixture] - [Timeout(10000)] public class OsuBeatmapConversionTest : BeatmapConversionTest { protected override string ResourceAssembly => "osu.Game.Rulesets.Osu"; diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs index b6db333dc9..b3f6a733d3 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs @@ -12,7 +12,6 @@ using osu.Game.Tests.Beatmaps; namespace osu.Game.Rulesets.Taiko.Tests { [TestFixture] - [Timeout(10000)] public class TaikoBeatmapConversionTest : BeatmapConversionTest { protected override string ResourceAssembly => "osu.Game.Rulesets.Taiko"; diff --git a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs index 651874e4de..1b3e49de37 100644 --- a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs +++ b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs @@ -6,9 +6,11 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; +using System.Threading.Tasks; using Newtonsoft.Json; using NUnit.Framework; using osu.Framework.Audio.Track; +using osu.Framework.Extensions; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics.Textures; using osu.Game.Beatmaps; @@ -108,37 +110,45 @@ namespace osu.Game.Tests.Beatmaps private ConvertResult convert(string name, Mod[] mods) { - var beatmap = GetBeatmap(name); - - string beforeConversion = beatmap.Serialize(); - - var converterResult = new Dictionary>(); - - var working = new ConversionWorkingBeatmap(beatmap) + var conversionTask = Task.Factory.StartNew(() => { - ConversionGenerated = (o, r, c) => + var beatmap = GetBeatmap(name); + + string beforeConversion = beatmap.Serialize(); + + var converterResult = new Dictionary>(); + + var working = new ConversionWorkingBeatmap(beatmap) { - converterResult[o] = r; - OnConversionGenerated(o, r, c); - } - }; + ConversionGenerated = (o, r, c) => + { + converterResult[o] = r; + OnConversionGenerated(o, r, c); + } + }; - working.GetPlayableBeatmap(CreateRuleset().RulesetInfo, mods); + working.GetPlayableBeatmap(CreateRuleset().RulesetInfo, mods); - string afterConversion = beatmap.Serialize(); + string afterConversion = beatmap.Serialize(); - Assert.AreEqual(beforeConversion, afterConversion, "Conversion altered original beatmap"); + Assert.AreEqual(beforeConversion, afterConversion, "Conversion altered original beatmap"); - return new ConvertResult - { - Mappings = converterResult.Select(r => + return new ConvertResult { - var mapping = CreateConvertMapping(r.Key); - mapping.StartTime = r.Key.StartTime; - mapping.Objects.AddRange(r.Value.SelectMany(CreateConvertValue)); - return mapping; - }).ToList() - }; + Mappings = converterResult.Select(r => + { + var mapping = CreateConvertMapping(r.Key); + mapping.StartTime = r.Key.StartTime; + mapping.Objects.AddRange(r.Value.SelectMany(CreateConvertValue)); + return mapping; + }).ToList() + }; + }, TaskCreationOptions.LongRunning); + + if (!conversionTask.Wait(10000)) + Assert.Fail("Conversion timed out"); + + return conversionTask.WaitSafelyForResult(); } protected virtual void OnConversionGenerated(HitObject original, IEnumerable result, IBeatmapConverter beatmapConverter) From 374dac57f2b3c4ea34a24929700119fa9a4eb52a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 4 Jan 2022 13:22:00 +0900 Subject: [PATCH 079/996] Change expanded card content height to 200 --- .../Beatmaps/Drawables/Cards/ExpandedContentScrollContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Drawables/Cards/ExpandedContentScrollContainer.cs b/osu.Game/Beatmaps/Drawables/Cards/ExpandedContentScrollContainer.cs index 7c3fbdaf1c..edf4c5328c 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/ExpandedContentScrollContainer.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/ExpandedContentScrollContainer.cs @@ -10,7 +10,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards { public class ExpandedContentScrollContainer : OsuScrollContainer { - public const float HEIGHT = 400; + public const float HEIGHT = 200; public ExpandedContentScrollContainer() { From 1c899e4402bcc891f3e5a46126fae65151372928 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 4 Jan 2022 16:46:42 +0900 Subject: [PATCH 080/996] Fix post-merge issues --- .../Visual/UserInterface/TestScenePageSelector.cs | 10 ---------- .../UserInterface/PageSelector/DrawablePage.cs | 2 +- .../UserInterface/PageSelector/PageSelector.cs | 1 + .../UserInterface/PageSelector/PageSelectorButton.cs | 4 ++-- 4 files changed, 4 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs index 6494486d4e..8a15dd9b46 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.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. -using System; -using System.Collections.Generic; using NUnit.Framework; using osu.Framework.Graphics; using osu.Game.Graphics.UserInterface.PageSelector; @@ -11,14 +9,6 @@ namespace osu.Game.Tests.Visual.UserInterface { public class TestScenePageSelector : OsuTestScene { - public override IReadOnlyList RequiredTypes => new[] - { - typeof(PageSelector), - typeof(DrawablePage), - typeof(PageSelectorButton), - typeof(PageSelectorItem) - }; - private readonly PageSelector pageSelector; private readonly DrawablePage drawablePage; diff --git a/osu.Game/Graphics/UserInterface/PageSelector/DrawablePage.cs b/osu.Game/Graphics/UserInterface/PageSelector/DrawablePage.cs index 20f418085d..4ea610bd89 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector/DrawablePage.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector/DrawablePage.cs @@ -57,7 +57,7 @@ namespace osu.Game.Graphics.UserInterface.PageSelector private void onSelectedChanged(ValueChangedEvent selected) { Background.FadeTo(selected.NewValue ? 1 : 0, DURATION, Easing.OutQuint); - text.FadeColour(selected.NewValue ? Colours.GreySeafoamDarker : Colours.Lime, DURATION, Easing.OutQuint); + text.FadeColour(selected.NewValue ? Colours.GreySeaFoamDarker : Colours.Lime, DURATION, Easing.OutQuint); } protected override void UpdateHoverState() diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs index 8e055faea3..612cf82b9f 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs @@ -21,6 +21,7 @@ namespace osu.Game.Graphics.UserInterface.PageSelector public PageSelector() { AutoSizeAxes = Axes.Both; + InternalChild = new FillFlowContainer { AutoSizeAxes = Axes.Both, diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorButton.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorButton.cs index e81ce20d27..4eba549dc3 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorButton.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorButton.cs @@ -63,7 +63,7 @@ namespace osu.Game.Graphics.UserInterface.PageSelector [BackgroundDependencyLoader] private void load() { - Background.Colour = Colours.GreySeafoamDark; + Background.Colour = Colours.GreySeaFoamDark; name.Colour = icon.Colour = Colours.Lime; } @@ -73,6 +73,6 @@ namespace osu.Game.Graphics.UserInterface.PageSelector Enabled.BindValueChanged(enabled => fadeBox.FadeTo(enabled.NewValue ? 0 : 1, DURATION), true); } - protected override void UpdateHoverState() => Background.FadeColour(IsHovered ? Colours.GreySeafoam : Colours.GreySeafoamDark, DURATION, Easing.OutQuint); + protected override void UpdateHoverState() => Background.FadeColour(IsHovered ? Colours.GreySeaFoam : Colours.GreySeaFoamDark, DURATION, Easing.OutQuint); } } From 5736b7d978521ebc2010f295ee238f55b1d78356 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 4 Jan 2022 17:12:34 +0900 Subject: [PATCH 081/996] Fix cursors sent to osu-web being potentially string formatted in incorrect culture Fixed as per solution at https://github.com/JamesNK/Newtonsoft.Json/issues/874. Note that due to the use of `JsonExtensionDataAttribute` it's not feasible to change the actual specification to `JValue` in the `Dictionary`. In discussion with the osu-web team, it may be worthwhile to change the cursor to a string format where parsing is not required at our end. We could already do this in fact, but there are tests that rely on it being a `JToken` so the switch to `JValue` seems like the easier path right now. --- osu.Game/Extensions/WebRequestExtensions.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Extensions/WebRequestExtensions.cs b/osu.Game/Extensions/WebRequestExtensions.cs index b940c7498b..50837a648d 100644 --- a/osu.Game/Extensions/WebRequestExtensions.cs +++ b/osu.Game/Extensions/WebRequestExtensions.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. +using System.Globalization; +using Newtonsoft.Json.Linq; using osu.Framework.IO.Network; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Game.Online.API.Requests; @@ -16,7 +18,7 @@ namespace osu.Game.Extensions { cursor?.Properties.ForEach(x => { - webRequest.AddParameter("cursor[" + x.Key + "]", x.Value.ToString()); + webRequest.AddParameter("cursor[" + x.Key + "]", (x.Value as JValue)?.ToString(CultureInfo.InvariantCulture) ?? x.Value.ToString()); }); } } From db58f5de8e6ea8c6dd14695b59a39aed33c82c9d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 4 Jan 2022 16:58:32 +0900 Subject: [PATCH 082/996] Clean up unnecessary complexity --- .../PageSelector/DrawablePage.cs | 8 +- .../PageSelector/PageSelectorButton.cs | 80 +++++++++++-------- .../PageSelector/PageSelectorItem.cs | 12 ++- 3 files changed, 56 insertions(+), 44 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/PageSelector/DrawablePage.cs b/osu.Game/Graphics/UserInterface/PageSelector/DrawablePage.cs index 4ea610bd89..2610ae7571 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector/DrawablePage.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector/DrawablePage.cs @@ -15,20 +15,16 @@ namespace osu.Game.Graphics.UserInterface.PageSelector public bool Selected { - get => selected.Value; set => selected.Value = value; } - public int Page { get; private set; } + public int Page { get; } private OsuSpriteText text; public DrawablePage(int page) { Page = page; - text.Text = page.ToString(); - - Background.Alpha = 0; Action = () => { @@ -40,12 +36,14 @@ namespace osu.Game.Graphics.UserInterface.PageSelector protected override Drawable CreateContent() => text = new OsuSpriteText { Font = OsuFont.GetFont(size: 12), + Text = Page.ToString(), }; [BackgroundDependencyLoader] private void load() { Background.Colour = Colours.Lime; + Background.Alpha = 0; } protected override void LoadComplete() diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorButton.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorButton.cs index 4eba549dc3..1492319fc9 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorButton.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorButton.cs @@ -1,61 +1,64 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics; using osu.Framework.Allocation; -using osu.Framework.Graphics.Shapes; using osu.Framework.Extensions.Color4Extensions; -using osu.Game.Graphics.Sprites; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; -using osuTK.Graphics; +using osu.Game.Graphics.Sprites; using osuTK; -using osu.Framework.Extensions.IEnumerableExtensions; +using osuTK.Graphics; namespace osu.Game.Graphics.UserInterface.PageSelector { public class PageSelectorButton : PageSelectorItem { - private readonly Box fadeBox; + private readonly string text; + + private Box fadeBox; private SpriteIcon icon; private OsuSpriteText name; - private FillFlowContainer buttonContent; + + private readonly Anchor alignment; public PageSelectorButton(bool rightAligned, string text) { - Add(fadeBox = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black.Opacity(100) - }); - - var alignment = rightAligned ? Anchor.x0 : Anchor.x2; - - buttonContent.ForEach(drawable => - { - drawable.Anchor = Anchor.y1 | alignment; - drawable.Origin = Anchor.y1 | alignment; - }); - - icon.Icon = alignment == Anchor.x2 ? FontAwesome.Solid.ChevronLeft : FontAwesome.Solid.ChevronRight; - - name.Text = text.ToUpper(); + this.text = text; + alignment = rightAligned ? Anchor.x0 : Anchor.x2; } - protected override Drawable CreateContent() => buttonContent = new FillFlowContainer + protected override Drawable CreateContent() => new Container { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(3, 0), + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, Children = new Drawable[] { - name = new OsuSpriteText + new FillFlowContainer { - Font = OsuFont.GetFont(size: 12), - }, - icon = new SpriteIcon - { - Size = new Vector2(8), + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Spacing = new Vector2(3, 0), + Children = new Drawable[] + { + name = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = text.ToUpper(), + }, + icon = new SpriteIcon + { + Icon = alignment == Anchor.x2 ? FontAwesome.Solid.ChevronLeft : FontAwesome.Solid.ChevronRight, + Size = new Vector2(8), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + } }, } }; @@ -70,6 +73,13 @@ namespace osu.Game.Graphics.UserInterface.PageSelector protected override void LoadComplete() { base.LoadComplete(); + + CircularContent.Add(fadeBox = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black.Opacity(100) + }); + Enabled.BindValueChanged(enabled => fadeBox.FadeTo(enabled.NewValue ? 0 : 1, DURATION), true); } diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorItem.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorItem.cs index cd61961dbe..92bf958ca9 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorItem.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorItem.cs @@ -18,16 +18,20 @@ namespace osu.Game.Graphics.UserInterface.PageSelector [Resolved] protected OsuColour Colours { get; private set; } - protected override Container Content => content; + protected Box Background; - protected readonly Box Background; - private readonly CircularContainer content; + public CircularContainer CircularContent { get; private set; } protected PageSelectorItem() { AutoSizeAxes = Axes.X; Height = 20; - base.Content.Add(content = new CircularContainer + } + + [BackgroundDependencyLoader] + private void load() + { + Add(CircularContent = new CircularContainer { RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, From ee4f5c0e79e11c2f8e9a9c574beddb16a2174652 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 4 Jan 2022 17:52:40 +0900 Subject: [PATCH 083/996] Rename button classes to make more sense --- .../UserInterface/TestScenePageSelector.cs | 8 +- .../PageSelector/PageSelector.cs | 14 +-- .../PageSelector/PageSelectorButton.cs | 112 ++++++++---------- .../PageSelector/PageSelectorItem.cs | 72 ----------- ...wablePage.cs => PageSelectorPageButton.cs} | 4 +- .../PageSelectorPrevNextButton.cs | 88 ++++++++++++++ 6 files changed, 149 insertions(+), 149 deletions(-) delete mode 100644 osu.Game/Graphics/UserInterface/PageSelector/PageSelectorItem.cs rename osu.Game/Graphics/UserInterface/PageSelector/{DrawablePage.cs => PageSelectorPageButton.cs} (94%) create mode 100644 osu.Game/Graphics/UserInterface/PageSelector/PageSelectorPrevNextButton.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs index 8a15dd9b46..000e75be31 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs @@ -10,7 +10,7 @@ namespace osu.Game.Tests.Visual.UserInterface public class TestScenePageSelector : OsuTestScene { private readonly PageSelector pageSelector; - private readonly DrawablePage drawablePage; + private readonly PageSelectorPageButton pageSelectorPageButton; public TestScenePageSelector() { @@ -21,7 +21,7 @@ namespace osu.Game.Tests.Visual.UserInterface Anchor = Anchor.Centre, Origin = Anchor.Centre, }, - drawablePage = new DrawablePage(1234) + pageSelectorPageButton = new PageSelectorPageButton(1234) { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, @@ -61,8 +61,8 @@ namespace osu.Game.Tests.Visual.UserInterface [Test] public void TestDrawablePage() { - AddStep("Select", () => drawablePage.Selected = true); - AddStep("Deselect", () => drawablePage.Selected = false); + AddStep("Select", () => pageSelectorPageButton.Selected = true); + AddStep("Deselect", () => pageSelectorPageButton.Selected = false); } private void setMaxPages(int maxPages) => pageSelector.MaxPages.Value = maxPages; diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs index 612cf82b9f..4089fb5511 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs @@ -13,10 +13,10 @@ namespace osu.Game.Graphics.UserInterface.PageSelector public readonly BindableInt CurrentPage = new BindableInt(1); public readonly BindableInt MaxPages = new BindableInt(1); - private readonly FillFlowContainer itemsFlow; + private readonly FillFlowContainer itemsFlow; - private readonly PageSelectorButton previousPageButton; - private readonly PageSelectorButton nextPageButton; + private readonly PageSelectorPrevNextButton previousPageButton; + private readonly PageSelectorPrevNextButton nextPageButton; public PageSelector() { @@ -28,16 +28,16 @@ namespace osu.Game.Graphics.UserInterface.PageSelector Direction = FillDirection.Horizontal, Children = new Drawable[] { - previousPageButton = new PageSelectorButton(false, "prev") + previousPageButton = new PageSelectorPrevNextButton(false, "prev") { Action = () => CurrentPage.Value -= 1, }, - itemsFlow = new FillFlowContainer + itemsFlow = new FillFlowContainer { AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, }, - nextPageButton = new PageSelectorButton(true, "next") + nextPageButton = new PageSelectorPrevNextButton(true, "next") { Action = () => CurrentPage.Value += 1 } @@ -100,7 +100,7 @@ namespace osu.Game.Graphics.UserInterface.PageSelector nextPageButton.Enabled.Value = newPage != maxPages; } - private void addDrawablePage(int page) => itemsFlow.Add(new DrawablePage(page) + private void addDrawablePage(int page) => itemsFlow.Add(new PageSelectorPageButton(page) { Action = () => CurrentPage.Value = page, }); diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorButton.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorButton.cs index 1492319fc9..bdfe66f1d0 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorButton.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorButton.cs @@ -1,88 +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 osu.Framework.Allocation; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics; +using osu.Framework.Allocation; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; -using osu.Game.Graphics.Sprites; -using osuTK; -using osuTK.Graphics; +using osu.Game.Graphics.Containers; +using osu.Framework.Input.Events; +using JetBrains.Annotations; namespace osu.Game.Graphics.UserInterface.PageSelector { - public class PageSelectorButton : PageSelectorItem + public abstract class PageSelectorButton : OsuClickableContainer { - private readonly string text; + protected const int DURATION = 200; - private Box fadeBox; - private SpriteIcon icon; - private OsuSpriteText name; + [Resolved] + protected OsuColour Colours { get; private set; } - private readonly Anchor alignment; + protected Box Background; - public PageSelectorButton(bool rightAligned, string text) + public CircularContainer CircularContent { get; private set; } + + protected PageSelectorButton() { - this.text = text; - alignment = rightAligned ? Anchor.x0 : Anchor.x2; + AutoSizeAxes = Axes.X; + Height = 20; } - protected override Drawable CreateContent() => new Container - { - RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X, - Children = new Drawable[] - { - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Spacing = new Vector2(3, 0), - Children = new Drawable[] - { - name = new OsuSpriteText - { - Font = OsuFont.GetFont(size: 12), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = text.ToUpper(), - }, - icon = new SpriteIcon - { - Icon = alignment == Anchor.x2 ? FontAwesome.Solid.ChevronLeft : FontAwesome.Solid.ChevronRight, - Size = new Vector2(8), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - } - }, - } - }; - [BackgroundDependencyLoader] private void load() { - Background.Colour = Colours.GreySeaFoamDark; - name.Colour = icon.Colour = Colours.Lime; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - CircularContent.Add(fadeBox = new Box + Add(CircularContent = new CircularContainer { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black.Opacity(100) + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Masking = true, + Children = new[] + { + Background = new Box + { + RelativeSizeAxes = Axes.Both + }, + CreateContent().With(content => + { + content.Anchor = Anchor.Centre; + content.Origin = Anchor.Centre; + content.Margin = new MarginPadding { Horizontal = 10 }; + }) + } }); - - Enabled.BindValueChanged(enabled => fadeBox.FadeTo(enabled.NewValue ? 0 : 1, DURATION), true); } - protected override void UpdateHoverState() => Background.FadeColour(IsHovered ? Colours.GreySeaFoam : Colours.GreySeaFoamDark, DURATION, Easing.OutQuint); + [NotNull] + protected abstract Drawable CreateContent(); + + protected override bool OnHover(HoverEvent e) + { + UpdateHoverState(); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + base.OnHoverLost(e); + UpdateHoverState(); + } + + protected abstract void UpdateHoverState(); } } diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorItem.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorItem.cs deleted file mode 100644 index 92bf958ca9..0000000000 --- a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorItem.cs +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics; -using osu.Framework.Allocation; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics.Containers; -using osu.Framework.Input.Events; -using JetBrains.Annotations; - -namespace osu.Game.Graphics.UserInterface.PageSelector -{ - public abstract class PageSelectorItem : OsuClickableContainer - { - protected const int DURATION = 200; - - [Resolved] - protected OsuColour Colours { get; private set; } - - protected Box Background; - - public CircularContainer CircularContent { get; private set; } - - protected PageSelectorItem() - { - AutoSizeAxes = Axes.X; - Height = 20; - } - - [BackgroundDependencyLoader] - private void load() - { - Add(CircularContent = new CircularContainer - { - RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X, - Masking = true, - Children = new[] - { - Background = new Box - { - RelativeSizeAxes = Axes.Both - }, - CreateContent().With(content => - { - content.Anchor = Anchor.Centre; - content.Origin = Anchor.Centre; - content.Margin = new MarginPadding { Horizontal = 10 }; - }) - } - }); - } - - [NotNull] - protected abstract Drawable CreateContent(); - - protected override bool OnHover(HoverEvent e) - { - UpdateHoverState(); - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - base.OnHoverLost(e); - UpdateHoverState(); - } - - protected abstract void UpdateHoverState(); - } -} diff --git a/osu.Game/Graphics/UserInterface/PageSelector/DrawablePage.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorPageButton.cs similarity index 94% rename from osu.Game/Graphics/UserInterface/PageSelector/DrawablePage.cs rename to osu.Game/Graphics/UserInterface/PageSelector/PageSelectorPageButton.cs index 2610ae7571..81ab7c7155 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector/DrawablePage.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorPageButton.cs @@ -9,7 +9,7 @@ using osu.Game.Graphics.Sprites; namespace osu.Game.Graphics.UserInterface.PageSelector { - public class DrawablePage : PageSelectorItem + public class PageSelectorPageButton : PageSelectorButton { private readonly BindableBool selected = new BindableBool(); @@ -22,7 +22,7 @@ namespace osu.Game.Graphics.UserInterface.PageSelector private OsuSpriteText text; - public DrawablePage(int page) + public PageSelectorPageButton(int page) { Page = page; diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorPrevNextButton.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorPrevNextButton.cs new file mode 100644 index 0000000000..eaa3714fea --- /dev/null +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorPrevNextButton.cs @@ -0,0 +1,88 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics.Sprites; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Graphics.UserInterface.PageSelector +{ + public class PageSelectorPrevNextButton : PageSelectorButton + { + private readonly string text; + + private Box fadeBox; + private SpriteIcon icon; + private OsuSpriteText name; + + private readonly Anchor alignment; + + public PageSelectorPrevNextButton(bool rightAligned, string text) + { + this.text = text; + alignment = rightAligned ? Anchor.x0 : Anchor.x2; + } + + protected override Drawable CreateContent() => new Container + { + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Children = new Drawable[] + { + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Spacing = new Vector2(3, 0), + Children = new Drawable[] + { + name = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = text.ToUpper(), + }, + icon = new SpriteIcon + { + Icon = alignment == Anchor.x2 ? FontAwesome.Solid.ChevronLeft : FontAwesome.Solid.ChevronRight, + Size = new Vector2(8), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + } + }, + } + }; + + [BackgroundDependencyLoader] + private void load() + { + Background.Colour = Colours.GreySeaFoamDark; + name.Colour = icon.Colour = Colours.Lime; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + CircularContent.Add(fadeBox = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black.Opacity(100) + }); + + Enabled.BindValueChanged(enabled => fadeBox.FadeTo(enabled.NewValue ? 0 : 1, DURATION), true); + } + + protected override void UpdateHoverState() => Background.FadeColour(IsHovered ? Colours.GreySeaFoam : Colours.GreySeaFoamDark, DURATION, Easing.OutQuint); + } +} From d10b8c79b39682f144f7c117a7c8278822485bfb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 4 Jan 2022 17:53:23 +0900 Subject: [PATCH 084/996] Remove pointless test coverage of `DrawablePage` --- .../Visual/UserInterface/TestScenePageSelector.cs | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs index 000e75be31..b65af6b1f5 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs @@ -10,7 +10,6 @@ namespace osu.Game.Tests.Visual.UserInterface public class TestScenePageSelector : OsuTestScene { private readonly PageSelector pageSelector; - private readonly PageSelectorPageButton pageSelectorPageButton; public TestScenePageSelector() { @@ -21,12 +20,6 @@ namespace osu.Game.Tests.Visual.UserInterface Anchor = Anchor.Centre, Origin = Anchor.Centre, }, - pageSelectorPageButton = new PageSelectorPageButton(1234) - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Margin = new MarginPadding { Top = 50 }, - } }); } @@ -58,13 +51,6 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("Max is 1", () => pageSelector.MaxPages.Value == 1); } - [Test] - public void TestDrawablePage() - { - AddStep("Select", () => pageSelectorPageButton.Selected = true); - AddStep("Deselect", () => pageSelectorPageButton.Selected = false); - } - private void setMaxPages(int maxPages) => pageSelector.MaxPages.Value = maxPages; private void setCurrentPage(int currentPage) => pageSelector.CurrentPage.Value = currentPage; From 5a11ee7810b705461360c2b3daa2e27b1e3b05ff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 4 Jan 2022 18:14:42 +0900 Subject: [PATCH 085/996] Use `OverlayColourProvider` and fix font weight --- .../UserInterface/TestScenePageSelector.cs | 5 +++ .../PageSelector/PageSelectorButton.cs | 7 ++-- .../PageSelector/PageSelectorPageButton.cs | 9 +++-- .../PageSelectorPrevNextButton.cs | 34 +++++++------------ 4 files changed, 24 insertions(+), 31 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs index b65af6b1f5..1595a7f22d 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs @@ -2,13 +2,18 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Graphics.UserInterface.PageSelector; +using osu.Game.Overlays; namespace osu.Game.Tests.Visual.UserInterface { public class TestScenePageSelector : OsuTestScene { + [Cached] + private OverlayColourProvider provider { get; } = new OverlayColourProvider(OverlayColourScheme.Green); + private readonly PageSelector pageSelector; public TestScenePageSelector() diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorButton.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorButton.cs index bdfe66f1d0..a2c6e8532b 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorButton.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorButton.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics.Shapes; using osu.Game.Graphics.Containers; using osu.Framework.Input.Events; using JetBrains.Annotations; +using osu.Game.Overlays; namespace osu.Game.Graphics.UserInterface.PageSelector { @@ -16,12 +17,10 @@ namespace osu.Game.Graphics.UserInterface.PageSelector protected const int DURATION = 200; [Resolved] - protected OsuColour Colours { get; private set; } + protected OverlayColourProvider ColourProvider { get; private set; } protected Box Background; - public CircularContainer CircularContent { get; private set; } - protected PageSelectorButton() { AutoSizeAxes = Axes.X; @@ -31,7 +30,7 @@ namespace osu.Game.Graphics.UserInterface.PageSelector [BackgroundDependencyLoader] private void load() { - Add(CircularContent = new CircularContainer + Add(new CircularContainer { RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorPageButton.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorPageButton.cs index 81ab7c7155..31aca0d2f2 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorPageButton.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorPageButton.cs @@ -4,7 +4,6 @@ using osu.Framework.Graphics; using osu.Framework.Bindables; using osu.Framework.Allocation; -using osu.Framework.Extensions.Color4Extensions; using osu.Game.Graphics.Sprites; namespace osu.Game.Graphics.UserInterface.PageSelector @@ -35,14 +34,14 @@ namespace osu.Game.Graphics.UserInterface.PageSelector protected override Drawable CreateContent() => text = new OsuSpriteText { - Font = OsuFont.GetFont(size: 12), + Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), Text = Page.ToString(), }; [BackgroundDependencyLoader] private void load() { - Background.Colour = Colours.Lime; + Background.Colour = ColourProvider.Highlight1; Background.Alpha = 0; } @@ -55,7 +54,7 @@ namespace osu.Game.Graphics.UserInterface.PageSelector private void onSelectedChanged(ValueChangedEvent selected) { Background.FadeTo(selected.NewValue ? 1 : 0, DURATION, Easing.OutQuint); - text.FadeColour(selected.NewValue ? Colours.GreySeaFoamDarker : Colours.Lime, DURATION, Easing.OutQuint); + text.FadeColour(selected.NewValue ? ColourProvider.Dark4 : ColourProvider.Light3, DURATION, Easing.OutQuint); } protected override void UpdateHoverState() @@ -63,7 +62,7 @@ namespace osu.Game.Graphics.UserInterface.PageSelector if (selected.Value) return; - text.FadeColour(IsHovered ? Colours.Lime.Lighten(20f) : Colours.Lime, DURATION, Easing.OutQuint); + text.FadeColour(IsHovered ? ColourProvider.Light2 : ColourProvider.Light1, DURATION, Easing.OutQuint); } } } diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorPrevNextButton.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorPrevNextButton.cs index eaa3714fea..7503ab8135 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorPrevNextButton.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorPrevNextButton.cs @@ -2,31 +2,26 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics.Sprites; using osuTK; -using osuTK.Graphics; namespace osu.Game.Graphics.UserInterface.PageSelector { public class PageSelectorPrevNextButton : PageSelectorButton { + private readonly bool rightAligned; private readonly string text; - private Box fadeBox; private SpriteIcon icon; private OsuSpriteText name; - private readonly Anchor alignment; - public PageSelectorPrevNextButton(bool rightAligned, string text) { + this.rightAligned = rightAligned; this.text = text; - alignment = rightAligned ? Anchor.x0 : Anchor.x2; } protected override Drawable CreateContent() => new Container @@ -47,16 +42,16 @@ namespace osu.Game.Graphics.UserInterface.PageSelector name = new OsuSpriteText { Font = OsuFont.GetFont(size: 12), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, + Anchor = rightAligned ? Anchor.CentreLeft : Anchor.CentreRight, + Origin = rightAligned ? Anchor.CentreLeft : Anchor.CentreRight, Text = text.ToUpper(), }, icon = new SpriteIcon { - Icon = alignment == Anchor.x2 ? FontAwesome.Solid.ChevronLeft : FontAwesome.Solid.ChevronRight, + Icon = rightAligned ? FontAwesome.Solid.ChevronRight : FontAwesome.Solid.ChevronLeft, Size = new Vector2(8), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, + Anchor = rightAligned ? Anchor.CentreLeft : Anchor.CentreRight, + Origin = rightAligned ? Anchor.CentreLeft : Anchor.CentreRight, }, } }, @@ -66,23 +61,18 @@ namespace osu.Game.Graphics.UserInterface.PageSelector [BackgroundDependencyLoader] private void load() { - Background.Colour = Colours.GreySeaFoamDark; - name.Colour = icon.Colour = Colours.Lime; + Background.Colour = ColourProvider.Dark4; + name.Colour = icon.Colour = ColourProvider.Light1; } protected override void LoadComplete() { base.LoadComplete(); - CircularContent.Add(fadeBox = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black.Opacity(100) - }); - - Enabled.BindValueChanged(enabled => fadeBox.FadeTo(enabled.NewValue ? 0 : 1, DURATION), true); + Enabled.BindValueChanged(enabled => Background.FadeTo(enabled.NewValue ? 1 : 0.5f, DURATION), true); } - protected override void UpdateHoverState() => Background.FadeColour(IsHovered ? Colours.GreySeaFoam : Colours.GreySeaFoamDark, DURATION, Easing.OutQuint); + protected override void UpdateHoverState() => + Background.FadeColour(IsHovered ? ColourProvider.Dark3 : ColourProvider.Dark4, DURATION, Easing.OutQuint); } } From e75c9519f32f8e44e1beac1e57b83798a1f8d547 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 4 Jan 2022 18:19:23 +0900 Subject: [PATCH 086/996] Adjust font weighting on selection --- .../UserInterface/PageSelector/PageSelectorPageButton.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorPageButton.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorPageButton.cs index 31aca0d2f2..6aac75565e 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorPageButton.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorPageButton.cs @@ -54,7 +54,9 @@ namespace osu.Game.Graphics.UserInterface.PageSelector private void onSelectedChanged(ValueChangedEvent selected) { Background.FadeTo(selected.NewValue ? 1 : 0, DURATION, Easing.OutQuint); + text.FadeColour(selected.NewValue ? ColourProvider.Dark4 : ColourProvider.Light3, DURATION, Easing.OutQuint); + text.Font = text.Font.With(weight: IsHovered ? FontWeight.SemiBold : FontWeight.Regular); } protected override void UpdateHoverState() From 86f72b71b1229c165d30d419047165b00cdaafc7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 4 Jan 2022 18:46:44 +0900 Subject: [PATCH 087/996] Prepare tests and general structure to support omission of pages --- .../UserInterface/TestScenePageSelector.cs | 42 ++++++------ .../PageSelector/PageSelector.cs | 66 +++++++------------ .../PageSelector/PageSelectorPageButton.cs | 8 +-- 3 files changed, 52 insertions(+), 64 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs index 1595a7f22d..3b32a2a0dc 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs @@ -1,9 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Testing; using osu.Game.Graphics.UserInterface.PageSelector; using osu.Game.Overlays; @@ -28,36 +30,38 @@ namespace osu.Game.Tests.Visual.UserInterface }); } + [Test] + public void TestOmittedPages() + { + setAvailablePages(100); + + AddAssert("Correct page buttons", () => pageSelector.ChildrenOfType().Select(p => p.PageNumber).SequenceEqual(new[] { 1, 2, 3, 100 })); + } + [Test] public void TestResetCurrentPage() { - AddStep("Set 10 pages", () => setMaxPages(10)); - AddStep("Select page 5", () => setCurrentPage(5)); - AddStep("Set 11 pages", () => setMaxPages(11)); - AddAssert("Page 1 is current", () => pageSelector.CurrentPage.Value == 1); + setAvailablePages(10); + selectPage(6); + setAvailablePages(11); + AddAssert("Page 1 is current", () => pageSelector.CurrentPage.Value == 0); } [Test] public void TestOutOfBoundsSelection() { - AddStep("Set 10 pages", () => setMaxPages(10)); - AddStep("Select page 11", () => setCurrentPage(11)); - AddAssert("Page 10 is current", () => pageSelector.CurrentPage.Value == pageSelector.MaxPages.Value); + setAvailablePages(10); + selectPage(11); + AddAssert("Page 10 is current", () => pageSelector.CurrentPage.Value == pageSelector.AvailablePages.Value - 1); - AddStep("Select page -1", () => setCurrentPage(-1)); - AddAssert("Page 1 is current", () => pageSelector.CurrentPage.Value == 1); + selectPage(-1); + AddAssert("Page 1 is current", () => pageSelector.CurrentPage.Value == 0); } - [Test] - public void TestNegativeMaxPages() - { - AddStep("Set -10 pages", () => setMaxPages(-10)); - AddAssert("Page 1 is current", () => pageSelector.CurrentPage.Value == 1); - AddAssert("Max is 1", () => pageSelector.MaxPages.Value == 1); - } + private void selectPage(int pageIndex) => + AddStep($"Select page {pageIndex}", () => pageSelector.CurrentPage.Value = pageIndex); - private void setMaxPages(int maxPages) => pageSelector.MaxPages.Value = maxPages; - - private void setCurrentPage(int currentPage) => pageSelector.CurrentPage.Value = currentPage; + private void setAvailablePages(int availablePages) => + AddStep($"Set available pages to {availablePages}", () => pageSelector.AvailablePages.Value = availablePages); } } diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs index 4089fb5511..ae644bb852 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs @@ -1,19 +1,20 @@ // 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 osu.Framework.Graphics.Containers; using osu.Framework.Graphics; using osu.Framework.Bindables; -using osu.Framework.Extensions.IEnumerableExtensions; namespace osu.Game.Graphics.UserInterface.PageSelector { public class PageSelector : CompositeDrawable { - public readonly BindableInt CurrentPage = new BindableInt(1); - public readonly BindableInt MaxPages = new BindableInt(1); + public readonly BindableInt CurrentPage = new BindableInt { MinValue = 0, }; - private readonly FillFlowContainer itemsFlow; + public readonly BindableInt AvailablePages = new BindableInt(1) { MinValue = 1, }; + + private readonly FillFlowContainer itemsFlow; private readonly PageSelectorPrevNextButton previousPageButton; private readonly PageSelectorPrevNextButton nextPageButton; @@ -32,7 +33,7 @@ namespace osu.Game.Graphics.UserInterface.PageSelector { Action = () => CurrentPage.Value -= 1, }, - itemsFlow = new FillFlowContainer + itemsFlow = new FillFlowContainer { AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, @@ -49,60 +50,43 @@ namespace osu.Game.Graphics.UserInterface.PageSelector { base.LoadComplete(); - MaxPages.BindValueChanged(_ => redraw()); - CurrentPage.BindValueChanged(page => onCurrentPageChanged(page.NewValue)); - redraw(); + CurrentPage.BindValueChanged(onCurrentPageChanged); + AvailablePages.BindValueChanged(_ => redraw(), true); } - private void onCurrentPageChanged(int newPage) + private void onCurrentPageChanged(ValueChangedEvent currentPage) { - if (newPage < 1) + if (currentPage.NewValue >= AvailablePages.Value) { - CurrentPage.Value = 1; + CurrentPage.Value = AvailablePages.Value - 1; return; } - if (newPage > MaxPages.Value) - { - CurrentPage.Value = MaxPages.Value; - return; - } + foreach (var page in itemsFlow.OfType()) + page.Selected = page.PageNumber == currentPage.NewValue + 1; - itemsFlow.ForEach(page => page.Selected = page.Page == newPage); - updateButtonsState(); + previousPageButton.Enabled.Value = currentPage.NewValue != 0; + nextPageButton.Enabled.Value = currentPage.NewValue < AvailablePages.Value - 1; } private void redraw() { itemsFlow.Clear(); - if (MaxPages.Value < 1) + for (int i = 0; i < AvailablePages.Value; i++) { - MaxPages.Value = 1; - return; + int pageIndex = i; + + itemsFlow.Add(new PageSelectorPageButton(pageIndex + 1) + { + Action = () => CurrentPage.Value = pageIndex, + }); } - for (int i = 1; i <= MaxPages.Value; i++) - addDrawablePage(i); - - if (CurrentPage.Value == 1) - CurrentPage.TriggerChange(); + if (CurrentPage.Value != 0) + CurrentPage.Value = 0; else - CurrentPage.Value = 1; + CurrentPage.TriggerChange(); } - - private void updateButtonsState() - { - int newPage = CurrentPage.Value; - int maxPages = MaxPages.Value; - - previousPageButton.Enabled.Value = newPage != 1; - nextPageButton.Enabled.Value = newPage != maxPages; - } - - private void addDrawablePage(int page) => itemsFlow.Add(new PageSelectorPageButton(page) - { - Action = () => CurrentPage.Value = page, - }); } } diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorPageButton.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorPageButton.cs index 6aac75565e..247a003492 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorPageButton.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorPageButton.cs @@ -17,13 +17,13 @@ namespace osu.Game.Graphics.UserInterface.PageSelector set => selected.Value = value; } - public int Page { get; } + public int PageNumber { get; } private OsuSpriteText text; - public PageSelectorPageButton(int page) + public PageSelectorPageButton(int pageNumber) { - Page = page; + PageNumber = pageNumber; Action = () => { @@ -35,7 +35,7 @@ namespace osu.Game.Graphics.UserInterface.PageSelector protected override Drawable CreateContent() => text = new OsuSpriteText { Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), - Text = Page.ToString(), + Text = PageNumber.ToString(), }; [BackgroundDependencyLoader] From 5ed69338a662165d7374e3de1940ad2997f35faa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 4 Jan 2022 19:05:14 +0900 Subject: [PATCH 088/996] Add omission of pages when there are too many --- .../UserInterface/TestScenePageSelector.cs | 22 +++++++-- .../PageSelector/PageEllipsis.cs | 33 +++++++++++++ .../PageSelector/PageSelector.cs | 48 ++++++++++--------- 3 files changed, 76 insertions(+), 27 deletions(-) create mode 100644 osu.Game/Graphics/UserInterface/PageSelector/PageEllipsis.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs index 3b32a2a0dc..c99ac52cb1 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs @@ -35,14 +35,24 @@ namespace osu.Game.Tests.Visual.UserInterface { setAvailablePages(100); - AddAssert("Correct page buttons", () => pageSelector.ChildrenOfType().Select(p => p.PageNumber).SequenceEqual(new[] { 1, 2, 3, 100 })); + selectPageIndex(0); + checkVisiblePageNumbers(new[] { 1, 2, 3, 100 }); + + selectPageIndex(6); + checkVisiblePageNumbers(new[] { 1, 5, 6, 7, 8, 9, 100 }); + + selectPageIndex(49); + checkVisiblePageNumbers(new[] { 1, 48, 49, 50, 51, 52, 100 }); + + selectPageIndex(99); + checkVisiblePageNumbers(new[] { 1, 98, 99, 100 }); } [Test] public void TestResetCurrentPage() { setAvailablePages(10); - selectPage(6); + selectPageIndex(6); setAvailablePages(11); AddAssert("Page 1 is current", () => pageSelector.CurrentPage.Value == 0); } @@ -51,14 +61,16 @@ namespace osu.Game.Tests.Visual.UserInterface public void TestOutOfBoundsSelection() { setAvailablePages(10); - selectPage(11); + selectPageIndex(11); AddAssert("Page 10 is current", () => pageSelector.CurrentPage.Value == pageSelector.AvailablePages.Value - 1); - selectPage(-1); + selectPageIndex(-1); AddAssert("Page 1 is current", () => pageSelector.CurrentPage.Value == 0); } - private void selectPage(int pageIndex) => + private void checkVisiblePageNumbers(int[] expected) => AddAssert($"Sequence is {string.Join(',', expected.Select(i => i.ToString()))}", () => pageSelector.ChildrenOfType().Select(p => p.PageNumber).SequenceEqual(expected)); + + private void selectPageIndex(int pageIndex) => AddStep($"Select page {pageIndex}", () => pageSelector.CurrentPage.Value = pageIndex); private void setAvailablePages(int availablePages) => diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageEllipsis.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageEllipsis.cs new file mode 100644 index 0000000000..d73d9f5824 --- /dev/null +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageEllipsis.cs @@ -0,0 +1,33 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Overlays; + +namespace osu.Game.Graphics.UserInterface.PageSelector +{ + internal class PageEllipsis : CompositeDrawable + { + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + RelativeSizeAxes = Axes.Y; + AutoSizeAxes = Axes.X; + + InternalChildren = new Drawable[] + { + new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), + Text = "...", + Colour = colourProvider.Light3, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } + }; + } + } +} diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs index ae644bb852..ce74259289 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs @@ -1,7 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Linq; +using System; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics; using osu.Framework.Bindables; @@ -50,43 +50,47 @@ namespace osu.Game.Graphics.UserInterface.PageSelector { base.LoadComplete(); - CurrentPage.BindValueChanged(onCurrentPageChanged); + CurrentPage.BindValueChanged(_ => redraw()); AvailablePages.BindValueChanged(_ => redraw(), true); } - private void onCurrentPageChanged(ValueChangedEvent currentPage) + private void redraw() { - if (currentPage.NewValue >= AvailablePages.Value) + if (CurrentPage.Value >= AvailablePages.Value) { CurrentPage.Value = AvailablePages.Value - 1; return; } - foreach (var page in itemsFlow.OfType()) - page.Selected = page.PageNumber == currentPage.NewValue + 1; + previousPageButton.Enabled.Value = CurrentPage.Value != 0; + nextPageButton.Enabled.Value = CurrentPage.Value < AvailablePages.Value - 1; - previousPageButton.Enabled.Value = currentPage.NewValue != 0; - nextPageButton.Enabled.Value = currentPage.NewValue < AvailablePages.Value - 1; - } - - private void redraw() - { itemsFlow.Clear(); - for (int i = 0; i < AvailablePages.Value; i++) + int totalPages = AvailablePages.Value; + bool lastWasEllipsis = false; + + for (int i = 0; i < totalPages; i++) { int pageIndex = i; - itemsFlow.Add(new PageSelectorPageButton(pageIndex + 1) - { - Action = () => CurrentPage.Value = pageIndex, - }); - } + bool shouldShowPage = pageIndex == 0 || pageIndex == totalPages - 1 || Math.Abs(pageIndex - CurrentPage.Value) <= 2; - if (CurrentPage.Value != 0) - CurrentPage.Value = 0; - else - CurrentPage.TriggerChange(); + if (shouldShowPage) + { + lastWasEllipsis = false; + itemsFlow.Add(new PageSelectorPageButton(pageIndex + 1) + { + Action = () => CurrentPage.Value = pageIndex, + Selected = CurrentPage.Value == pageIndex, + }); + } + else if (!lastWasEllipsis) + { + lastWasEllipsis = true; + itemsFlow.Add(new PageEllipsis()); + } + } } } } From 612f69782b14e6d0397c9fd248b9f77ffd48e01e Mon Sep 17 00:00:00 2001 From: MaxOhn Date: Tue, 4 Jan 2022 14:29:44 +0100 Subject: [PATCH 089/996] use Playfield.HitObjectContainer.AliveObjects --- osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs | 93 ++++++++++--------- 1 file changed, 49 insertions(+), 44 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs index f70ad2ac7c..1849b48073 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs @@ -7,54 +7,55 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.Osu.UI; using osuTK; +using System.Linq; namespace osu.Game.Rulesets.Osu.Mods { - internal class OsuModAimAssist : Mod, IApplicableToDrawableHitObjects, IUpdatableByPlayfield + internal class OsuModAimAssist : Mod, IUpdatableByPlayfield { public override string Name => "Aim Assist"; public override string Acronym => "AA"; - public override IconUsage Icon => FontAwesome.Solid.MousePointer; + public override IconUsage? Icon => FontAwesome.Solid.MousePointer; public override ModType Type => ModType.Fun; public override string Description => "No need to chase the circle, the circle chases you"; public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay) }; - private readonly List movingObjects = new List(); + public const float SPIN_RADIUS = 50; // same as OsuAutoGeneratorBase.SPIN_RADIUS + private DrawableSpinner activeSpinner; - private double spinnerAngle; // in radians + private float spinnerAngle; // in radians public void Update(Playfield playfield) { var cursorPos = playfield.Cursor.ActiveCursor.DrawPosition; - var currentTime = playfield.Clock.CurrentTime; + double currentTime = playfield.Clock.CurrentTime; - // Avoid relocating judgment displays and hide follow points + // Judgment displays would all be cramped onto the cursor playfield.DisplayJudgements.Value = false; - (playfield as OsuPlayfield)?.ConnectionLayer.Hide(); + + // FIXME: Hide follow points + //(playfield as OsuPlayfield)?.ConnectionLayer.Hide(); // If object too old, remove from movingObjects list, otherwise move to new destination - movingObjects.RemoveAll(d => + foreach (var drawable in playfield.HitObjectContainer.AliveObjects.OfType()) { - var h = d.HitObject; - var endTime = (h as IHasEndTime)?.EndTime ?? h.StartTime; + var h = drawable.HitObject; + double endTime = h.GetEndTime(); - // Object no longer required to be moved -> remove from list - if (currentTime > endTime) - return true; - - switch (d) + switch (drawable) { case DrawableHitCircle circle: // 10ms earlier on the note to reduce chance of missing when clicking early / cursor moves fast circle.MoveTo(cursorPos, Math.Max(0, endTime - currentTime - 10)); - return false; + + break; case DrawableSlider slider: @@ -66,11 +67,12 @@ namespace osu.Game.Rulesets.Osu.Mods // Move slider so that sliderball stays on the cursor else { - slider.HeadCircle.Hide(); // temporary solution to supress HeadCircle's explosion, flash, ... at wrong location + // FIXME: Hide flashes + //slider.HeadCircle.Hide(); slider.MoveTo(cursorPos - slider.Ball.DrawPosition); } - return false; + break; case DrawableSpinner spinner: @@ -78,23 +80,32 @@ namespace osu.Game.Rulesets.Osu.Mods if (currentTime < h.StartTime) { spinner.MoveTo(cursorPos, Math.Max(0, h.StartTime - currentTime - 10)); - return false; } else { - spinnerAngle = 0; - activeSpinner = spinner; - return true; + // TODO: + // - get current angle to cursor + // - move clockwise(?) + // - call spinner.RotationTracker.AddRotation + + // TODO: Remove + //spinnerAngle = 0; + //activeSpinner = spinner; } - default: - return true; - } - }); + break; + default: + continue; + } + } + + // Move active spinner around the cursor if (activeSpinner != null) { - if (currentTime > (activeSpinner.HitObject as IHasEndTime)?.EndTime) + double spinnerEndTime = activeSpinner.HitObject.GetEndTime(); + + if (currentTime > spinnerEndTime) { activeSpinner = null; spinnerAngle = 0; @@ -102,29 +113,23 @@ namespace osu.Game.Rulesets.Osu.Mods else { const float additional_degrees = 4; - const int dist_from_cursor = 30; - spinnerAngle += additional_degrees * Math.PI / 180; + float added_degrees = additional_degrees * (float)Math.PI / 180; + spinnerAngle += added_degrees; + + //int spinsRequired = activeSpinner.HitObject.SpinsRequired; + //float spunDegrees = activeSpinner.Result.RateAdjustedRotation; + //double timeLeft = spinnerEndTime - currentTime; // Visual progress - activeSpinner.MoveTo(new Vector2((float)(dist_from_cursor * Math.Cos(spinnerAngle) + cursorPos.X), (float)(dist_from_cursor * Math.Sin(spinnerAngle) + cursorPos.Y))); + activeSpinner.MoveTo(new Vector2((float)(SPIN_RADIUS * Math.Cos(spinnerAngle) + cursorPos.X), (float)(SPIN_RADIUS * Math.Sin(spinnerAngle) + cursorPos.Y))); // Logical progress - activeSpinner.Disc.RotationAbsolute += additional_degrees; + activeSpinner.RotationTracker.AddRotation(added_degrees); + Console.WriteLine($"added_degrees={added_degrees}"); + //activeSpinner.Disc.RotationAbsolute += additional_degrees; } } } - - public void ApplyToDrawableHitObjects(IEnumerable drawables) - { - foreach (var drawable in drawables) - drawable.ApplyCustomUpdateState += drawableOnApplyCustomUpdateState; - } - - private void drawableOnApplyCustomUpdateState(DrawableHitObject drawable, ArmedState state) - { - if (drawable is DrawableOsuHitObject hitobject) - movingObjects.Add(hitobject); - } } /* From 27a8bfa4968672c44c64e7beb1e842894755ecf5 Mon Sep 17 00:00:00 2001 From: MaxOhn Date: Tue, 4 Jan 2022 22:17:50 +0100 Subject: [PATCH 090/996] handle spinners and follow points --- osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs | 80 +++++-------------- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 9 ++- 2 files changed, 26 insertions(+), 63 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs index 1849b48073..306e7a8b24 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs @@ -2,11 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.UI; @@ -26,23 +24,19 @@ 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 const float SPIN_RADIUS = 50; // same as OsuAutoGeneratorBase.SPIN_RADIUS - - private DrawableSpinner activeSpinner; - private float spinnerAngle; // in radians + private const float spin_radius = 30; + private Vector2? prevCursorPos; public void Update(Playfield playfield) { var cursorPos = playfield.Cursor.ActiveCursor.DrawPosition; double currentTime = playfield.Clock.CurrentTime; - // Judgment displays would all be cramped onto the cursor + // Hide judgment displays and follow points playfield.DisplayJudgements.Value = false; + (playfield as OsuPlayfield)?.FollowPoints.Clear(); - // FIXME: Hide follow points - //(playfield as OsuPlayfield)?.ConnectionLayer.Hide(); - - // If object too old, remove from movingObjects list, otherwise move to new destination + // Move all currently alive object to new destination foreach (var drawable in playfield.HitObjectContainer.AliveObjects.OfType()) { var h = drawable.HitObject; @@ -79,64 +73,32 @@ namespace osu.Game.Rulesets.Osu.Mods // Move spinner to cursor if (currentTime < h.StartTime) { - spinner.MoveTo(cursorPos, Math.Max(0, h.StartTime - currentTime - 10)); + spinner.MoveTo(cursorPos + new Vector2(0, -spin_radius), Math.Max(0, h.StartTime - currentTime - 10)); } else { - // TODO: - // - get current angle to cursor - // - move clockwise(?) - // - call spinner.RotationTracker.AddRotation + // Move spinner visually + Vector2 delta = spin_radius * (spinner.Position - prevCursorPos ?? cursorPos).Normalized(); + const float angle = 3 * MathF.PI / 180; // radians per update, arbitrary value - // TODO: Remove - //spinnerAngle = 0; - //activeSpinner = spinner; + // Rotation matrix + var targetPos = new Vector2( + delta.X * MathF.Cos(angle) - delta.Y * MathF.Sin(angle) + cursorPos.X, + delta.X * MathF.Sin(angle) + delta.Y * MathF.Cos(angle) + cursorPos.Y + ); + + spinner.MoveTo(targetPos); + + // Logically finish spinner immediatly, no need for the user to click. + // Temporary workaround until spinner rotations are easier to handle, similar as Autopilot mod. + spinner.Result.RateAdjustedRotation = spinner.HitObject.SpinsRequired * 360; } break; - - default: - continue; } } - // Move active spinner around the cursor - if (activeSpinner != null) - { - double spinnerEndTime = activeSpinner.HitObject.GetEndTime(); - - if (currentTime > spinnerEndTime) - { - activeSpinner = null; - spinnerAngle = 0; - } - else - { - const float additional_degrees = 4; - float added_degrees = additional_degrees * (float)Math.PI / 180; - spinnerAngle += added_degrees; - - //int spinsRequired = activeSpinner.HitObject.SpinsRequired; - //float spunDegrees = activeSpinner.Result.RateAdjustedRotation; - //double timeLeft = spinnerEndTime - currentTime; - - // Visual progress - activeSpinner.MoveTo(new Vector2((float)(SPIN_RADIUS * Math.Cos(spinnerAngle) + cursorPos.X), (float)(SPIN_RADIUS * Math.Sin(spinnerAngle) + cursorPos.Y))); - - // Logical progress - activeSpinner.RotationTracker.AddRotation(added_degrees); - Console.WriteLine($"added_degrees={added_degrees}"); - //activeSpinner.Disc.RotationAbsolute += additional_degrees; - } - } + prevCursorPos = cursorPos; } } - - /* - * TODOs - * - fix sliders reappearing at original position after their EndTime (see https://puu.sh/E7zT4/111cf9cdc8.gif) - * - find nicer way to handle slider headcircle explosion, flash, ... - * - add Aim Assist as incompatible mod for Autoplay (?) - * - */ } diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 2233a547b9..bc1e80cd12 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -31,7 +31,8 @@ namespace osu.Game.Rulesets.Osu.UI private readonly ProxyContainer approachCircles; private readonly ProxyContainer spinnerProxies; private readonly JudgementContainer judgementLayer; - private readonly FollowPointRenderer followPoints; + + public FollowPointRenderer FollowPoints { get; } public static readonly Vector2 BASE_SIZE = new Vector2(512, 384); @@ -50,7 +51,7 @@ namespace osu.Game.Rulesets.Osu.UI { playfieldBorder = new PlayfieldBorder { RelativeSizeAxes = Axes.Both }, spinnerProxies = new ProxyContainer { RelativeSizeAxes = Axes.Both }, - followPoints = new FollowPointRenderer { RelativeSizeAxes = Axes.Both }, + FollowPoints = new FollowPointRenderer { RelativeSizeAxes = Axes.Both }, judgementLayer = new JudgementContainer { RelativeSizeAxes = Axes.Both }, HitObjectContainer, judgementAboveHitObjectLayer = new Container { RelativeSizeAxes = Axes.Both }, @@ -131,13 +132,13 @@ namespace osu.Game.Rulesets.Osu.UI protected override void OnHitObjectAdded(HitObject hitObject) { base.OnHitObjectAdded(hitObject); - followPoints.AddFollowPoints((OsuHitObject)hitObject); + FollowPoints.AddFollowPoints((OsuHitObject)hitObject); } protected override void OnHitObjectRemoved(HitObject hitObject) { base.OnHitObjectRemoved(hitObject); - followPoints.RemoveFollowPoints((OsuHitObject)hitObject); + FollowPoints.RemoveFollowPoints((OsuHitObject)hitObject); } private void onNewResult(DrawableHitObject judgedObject, JudgementResult result) From 2bf6b55b19a26add1ad4f3f6e19bc4b553025b95 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 Jan 2022 14:53:32 +0900 Subject: [PATCH 091/996] Fix failing test due to changed reset page logic --- .../UserInterface/PageSelector/PageSelector.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs index ce74259289..005729580c 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs @@ -50,8 +50,14 @@ namespace osu.Game.Graphics.UserInterface.PageSelector { base.LoadComplete(); - CurrentPage.BindValueChanged(_ => redraw()); - AvailablePages.BindValueChanged(_ => redraw(), true); + CurrentPage.BindValueChanged(_ => Scheduler.AddOnce(redraw)); + AvailablePages.BindValueChanged(_ => + { + CurrentPage.Value = 0; + + // AddOnce as the reset of CurrentPage may also trigger a redraw. + Scheduler.AddOnce(redraw); + }, true); } private void redraw() From ef2a4aed9a2600b3be2eb86215711c718030bde3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 Jan 2022 16:05:15 +0900 Subject: [PATCH 092/996] Fix editor playfield not being centered correctly This has come up multiple times, with mappers citing that they have muscle memory for mapping based on the centre of the playfield being in the centre of the window. The original plan was to have a second toolbar on the right hand side of the screen to balance the padding, but we're not at that point yet. Easiest solution is to do what stable does and allow the left-hand toolbar items to overlap the playfield underneath it. In edge cases where the user is running at an aspect ratio that causes overlaps, they can choose to collapse the toolbars down. We can probably work on this UI/UX a bit more as we update designs to be more friendly to such cases. --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 4bbfe26d7b..cbc2415603 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -105,7 +105,6 @@ namespace osu.Game.Rulesets.Edit new Container { Name = "Content", - Padding = new MarginPadding { Left = toolbar_width }, RelativeSizeAxes = Axes.Both, Children = new Drawable[] { From 13cce50fa74726c3dc016fe43e2c67e1fe630ba6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 Jan 2022 16:30:42 +0900 Subject: [PATCH 093/996] Remove existing handling of flip hotkeys --- .../Screens/Edit/Compose/Components/SelectionBox.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs index be52a968bb..732c6a5dd4 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs @@ -174,12 +174,6 @@ namespace osu.Game.Screens.Edit.Compose.Components { case Key.G: return CanReverse && runOperationFromHotkey(OnReverse); - - case Key.H: - return CanFlipX && runOperationFromHotkey(() => OnFlip?.Invoke(Direction.Horizontal) ?? false); - - case Key.J: - return CanFlipY && runOperationFromHotkey(() => OnFlip?.Invoke(Direction.Vertical) ?? false); } return base.OnKeyDown(e); @@ -287,12 +281,12 @@ namespace osu.Game.Screens.Edit.Compose.Components private void addXFlipComponents() { - addButton(FontAwesome.Solid.ArrowsAltH, "Flip horizontally (Ctrl-H)", () => OnFlip?.Invoke(Direction.Horizontal)); + addButton(FontAwesome.Solid.ArrowsAltH, "Flip horizontally", () => OnFlip?.Invoke(Direction.Horizontal)); } private void addYFlipComponents() { - addButton(FontAwesome.Solid.ArrowsAltV, "Flip vertically (Ctrl-J)", () => OnFlip?.Invoke(Direction.Vertical)); + addButton(FontAwesome.Solid.ArrowsAltV, "Flip vertically", () => OnFlip?.Invoke(Direction.Vertical)); } private void addButton(IconUsage icon, string tooltip, Action action) From 866ae3472bb654af5bde50f79ecb0ae2cbd884e5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 Jan 2022 16:46:34 +0900 Subject: [PATCH 094/996] Add global flip hotkeys --- .../Edit/CatchSelectionHandler.cs | 12 ++++--- .../Edit/OsuSelectionHandler.cs | 7 +++-- .../Input/Bindings/GlobalActionContainer.cs | 10 +++++- .../GlobalActionKeyBindingStrings.cs | 10 ++++++ .../Edit/Compose/Components/SelectionBox.cs | 6 ++-- .../Compose/Components/SelectionHandler.cs | 31 +++++++++++++++++-- .../Timeline/TimelineSelectionHandler.cs | 12 +++---- .../Skinning/Editor/SkinSelectionHandler.cs | 4 +-- 8 files changed, 68 insertions(+), 24 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs index 8cb0804ab7..41a2584acc 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs @@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Catch.Edit return true; } - public override bool HandleFlip(Direction direction) + public override bool HandleFlip(Direction direction, bool flipOverOrigin) { var selectionRange = CatchHitObjectUtils.GetPositionRange(SelectedItems); @@ -60,7 +60,7 @@ namespace osu.Game.Rulesets.Catch.Edit EditorBeatmap.PerformOnSelection(h => { if (h is CatchHitObject catchObject) - changed |= handleFlip(selectionRange, catchObject); + changed |= handleFlip(selectionRange, catchObject, flipOverOrigin); }); return changed; } @@ -116,7 +116,7 @@ namespace osu.Game.Rulesets.Catch.Edit return Math.Clamp(deltaX, lowerBound, upperBound); } - private bool handleFlip(PositionRange selectionRange, CatchHitObject hitObject) + private bool handleFlip(PositionRange selectionRange, CatchHitObject hitObject, bool flipOverOrigin) { switch (hitObject) { @@ -124,7 +124,7 @@ namespace osu.Game.Rulesets.Catch.Edit return false; case JuiceStream juiceStream: - juiceStream.OriginalX = selectionRange.GetFlippedPosition(juiceStream.OriginalX); + juiceStream.OriginalX = getFlippedPosition(juiceStream.OriginalX); foreach (var point in juiceStream.Path.ControlPoints) point.Position *= new Vector2(-1, 1); @@ -133,9 +133,11 @@ namespace osu.Game.Rulesets.Catch.Edit return true; default: - hitObject.OriginalX = selectionRange.GetFlippedPosition(hitObject.OriginalX); + hitObject.OriginalX = getFlippedPosition(hitObject.OriginalX); return true; } + + float getFlippedPosition(float original) => flipOverOrigin ? CatchPlayfield.WIDTH - original : selectionRange.GetFlippedPosition(original); } } } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 4a57d36eb4..d172fa5398 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -10,6 +10,7 @@ using osu.Game.Extensions; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.UI; using osu.Game.Screens.Edit.Compose.Components; using osuTK; @@ -84,15 +85,15 @@ namespace osu.Game.Rulesets.Osu.Edit return true; } - public override bool HandleFlip(Direction direction) + public override bool HandleFlip(Direction direction, bool flipOverOrigin) { var hitObjects = selectedMovableObjects; - var selectedObjectsQuad = getSurroundingQuad(hitObjects); + var flipQuad = flipOverOrigin ? new Quad(0, 0, OsuPlayfield.BASE_SIZE.X, OsuPlayfield.BASE_SIZE.Y) : getSurroundingQuad(hitObjects); foreach (var h in hitObjects) { - h.Position = GetFlippedPosition(direction, selectedObjectsQuad, h.Position); + h.Position = GetFlippedPosition(direction, flipQuad, h.Position); if (h is Slider slider) { diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index c71cb6a00a..47cb7be2cf 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -77,6 +77,8 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.K }, GlobalAction.EditorNudgeRight), new KeyBinding(new[] { InputKey.G }, GlobalAction.EditorCycleGridDisplayMode), new KeyBinding(new[] { InputKey.F5 }, GlobalAction.EditorTestGameplay), + new KeyBinding(new[] { InputKey.Control, InputKey.H }, GlobalAction.EditorFlipHorizontally), + new KeyBinding(new[] { InputKey.Control, InputKey.J }, GlobalAction.EditorFlipVertically), }; public IEnumerable InGameKeyBindings => new[] @@ -292,6 +294,12 @@ namespace osu.Game.Input.Bindings EditorCycleGridDisplayMode, [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorTestGameplay))] - EditorTestGameplay + EditorTestGameplay, + + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorFlipHorizontally))] + EditorFlipHorizontally, + + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorFlipVertically))] + EditorFlipVertically, } } diff --git a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs index 35a0c2ae74..777e97d1e3 100644 --- a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs +++ b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs @@ -229,6 +229,16 @@ namespace osu.Game.Localisation /// public static LocalisableString EditorNudgeRight => new TranslatableString(getKey(@"editor_nudge_right"), @"Nudge selection right"); + /// + /// "Flip selection horizontally" + /// + public static LocalisableString EditorFlipHorizontally => new TranslatableString(getKey(@"editor_flip_horizontally"), @"Flip selection horizontally"); + + /// + /// "Flip selection vertically" + /// + public static LocalisableString EditorFlipVertically => new TranslatableString(getKey(@"editor_flip_vertically"), @"Flip selection vertically"); + /// /// "Toggle skin editor" /// diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs index 732c6a5dd4..3a31f6ea8c 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs @@ -21,7 +21,7 @@ namespace osu.Game.Screens.Edit.Compose.Components public Func OnRotation; public Func OnScale; - public Func OnFlip; + public Func OnFlip; public Func OnReverse; public Action OperationStarted; @@ -281,12 +281,12 @@ namespace osu.Game.Screens.Edit.Compose.Components private void addXFlipComponents() { - addButton(FontAwesome.Solid.ArrowsAltH, "Flip horizontally", () => OnFlip?.Invoke(Direction.Horizontal)); + addButton(FontAwesome.Solid.ArrowsAltH, "Flip horizontally", () => OnFlip?.Invoke(Direction.Horizontal, false)); } private void addYFlipComponents() { - addButton(FontAwesome.Solid.ArrowsAltV, "Flip vertically", () => OnFlip?.Invoke(Direction.Vertical)); + addButton(FontAwesome.Solid.ArrowsAltV, "Flip vertically", () => OnFlip?.Invoke(Direction.Vertical, false)); } private void addButton(IconUsage icon, string tooltip, Action action) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index ee35b6a47c..d9d310c72c 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -17,6 +17,7 @@ using osu.Framework.Input.Events; using osu.Framework.Utils; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; +using osu.Game.Input.Bindings; using osu.Game.Rulesets.Edit; using osuTK; using osuTK.Input; @@ -26,7 +27,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// A component which outlines items and handles movement of selections. /// - public abstract class SelectionHandler : CompositeDrawable, IKeyBindingHandler, IHasContextMenu + public abstract class SelectionHandler : CompositeDrawable, IKeyBindingHandler, IKeyBindingHandler, IHasContextMenu { /// /// The currently selected blueprints. @@ -127,9 +128,10 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// Handles the selected items being flipped. /// - /// The direction to flip + /// The direction to flip. + /// Whether the flip operation should be global to the playfield's origin or local to the selected pattern. /// Whether any items could be flipped. - public virtual bool HandleFlip(Direction direction) => false; + public virtual bool HandleFlip(Direction direction, bool flipOverOrigin) => false; /// /// Handles the selected items being reversed pattern-wise. @@ -137,6 +139,29 @@ namespace osu.Game.Screens.Edit.Compose.Components /// Whether any items could be reversed. public virtual bool HandleReverse() => false; + public virtual bool OnPressed(KeyBindingPressEvent e) + { + if (e.Repeat) + return false; + + switch (e.Action) + { + case GlobalAction.EditorFlipHorizontally: + HandleFlip(Direction.Horizontal, true); + return true; + + case GlobalAction.EditorFlipVertically: + HandleFlip(Direction.Vertical, true); + return true; + } + + return false; + } + + public void OnReleased(KeyBindingReleaseEvent e) + { + } + public bool OnPressed(KeyBindingPressEvent e) { switch (e.Action) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineSelectionHandler.cs index 845a671e2c..8b7ff45765 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineSelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineSelectionHandler.cs @@ -7,7 +7,6 @@ using System.Diagnostics; using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; -using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Input.Bindings; using osu.Game.Rulesets.Edit; @@ -17,7 +16,7 @@ using osuTK.Input; namespace osu.Game.Screens.Edit.Compose.Components.Timeline { - internal class TimelineSelectionHandler : EditorSelectionHandler, IKeyBindingHandler + internal class TimelineSelectionHandler : EditorSelectionHandler { [Resolved] private Timeline timeline { get; set; } @@ -27,8 +26,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline // for now we always allow movement. snapping is provided by the Timeline's "distance" snap implementation public override bool HandleMovement(MoveSelectionEvent moveEvent) => true; - public bool OnPressed(KeyBindingPressEvent e) + public override bool OnPressed(KeyBindingPressEvent e) { + // Importantly, we block the base call here. + // Other key operations will be handled by the composer view's SelectionHandler instead. + switch (e.Action) { case GlobalAction.EditorNudgeLeft: @@ -43,10 +45,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline return false; } - public void OnReleased(KeyBindingReleaseEvent e) - { - } - /// /// Nudge the current selection by the specified multiple of beat divisor lengths, /// based on the timing at the first object in the selection. diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index 01bd5e8196..a54590a0ea 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -126,9 +126,9 @@ namespace osu.Game.Skinning.Editor return true; } - public override bool HandleFlip(Direction direction) + public override bool HandleFlip(Direction direction, bool flipOverOrigin) { - var selectionQuad = getSelectionQuad(); + var selectionQuad = flipOverOrigin ? ScreenSpaceDrawQuad : getSelectionQuad(); Vector2 scaleFactor = direction == Direction.Horizontal ? new Vector2(-1, 1) : new Vector2(1, -1); foreach (var b in SelectedBlueprints) From 6779503e574e717c571d27ddef15939b6255945f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 Jan 2022 16:56:54 +0900 Subject: [PATCH 095/996] Refactor logic to avoid `TimelineSelectionHandler` having to block base calls --- osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs | 5 +++++ osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs | 3 +++ .../Screens/Edit/Compose/Components/SelectionHandler.cs | 6 ++---- .../Compose/Components/Timeline/TimelineSelectionHandler.cs | 5 +---- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs index 41a2584acc..d39f1d3c86 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs @@ -54,14 +54,19 @@ namespace osu.Game.Rulesets.Catch.Edit public override bool HandleFlip(Direction direction, bool flipOverOrigin) { + if (SelectedItems.Count == 0 && !flipOverOrigin) + return false; + var selectionRange = CatchHitObjectUtils.GetPositionRange(SelectedItems); bool changed = false; + EditorBeatmap.PerformOnSelection(h => { if (h is CatchHitObject catchObject) changed |= handleFlip(selectionRange, catchObject, flipOverOrigin); }); + return changed; } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index d172fa5398..d51f08ad68 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -89,6 +89,9 @@ namespace osu.Game.Rulesets.Osu.Edit { var hitObjects = selectedMovableObjects; + if (hitObjects.Length == 1 && !flipOverOrigin) + return false; + var flipQuad = flipOverOrigin ? new Quad(0, 0, OsuPlayfield.BASE_SIZE.X, OsuPlayfield.BASE_SIZE.Y) : getSurroundingQuad(hitObjects); foreach (var h in hitObjects) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index d9d310c72c..39de13899d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -147,12 +147,10 @@ namespace osu.Game.Screens.Edit.Compose.Components switch (e.Action) { case GlobalAction.EditorFlipHorizontally: - HandleFlip(Direction.Horizontal, true); - return true; + return HandleFlip(Direction.Horizontal, true); case GlobalAction.EditorFlipVertically: - HandleFlip(Direction.Vertical, true); - return true; + return HandleFlip(Direction.Vertical, true); } return false; diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineSelectionHandler.cs index 8b7ff45765..e98cf8332f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineSelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineSelectionHandler.cs @@ -28,9 +28,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline public override bool OnPressed(KeyBindingPressEvent e) { - // Importantly, we block the base call here. - // Other key operations will be handled by the composer view's SelectionHandler instead. - switch (e.Action) { case GlobalAction.EditorNudgeLeft: @@ -42,7 +39,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline return true; } - return false; + return base.OnPressed(e); } /// From df6a755c3653a4c473c9031eea52198158cdf304 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 Jan 2022 18:29:09 +0900 Subject: [PATCH 096/996] Update player loader screen mouse disable text to use localised version --- osu.Game/Screens/Play/PlayerSettings/InputSettings.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/PlayerSettings/InputSettings.cs b/osu.Game/Screens/Play/PlayerSettings/InputSettings.cs index 725a6e86bf..b1063966da 100644 --- a/osu.Game/Screens/Play/PlayerSettings/InputSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/InputSettings.cs @@ -4,6 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Configuration; +using osu.Game.Localisation; namespace osu.Game.Screens.Play.PlayerSettings { @@ -18,7 +19,7 @@ namespace osu.Game.Screens.Play.PlayerSettings { mouseButtonsCheckbox = new PlayerCheckbox { - LabelText = "Disable mouse buttons" + LabelText = MouseSettingsStrings.DisableMouseButtons } }; } From 88602ec1b4f26d7985e8990fd9ad54a8deb30674 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 Jan 2022 18:29:32 +0900 Subject: [PATCH 097/996] Fix mouse button disable not disabling touch input mappings --- osu.Game/Rulesets/UI/RulesetInputManager.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 6564ff9e23..370c99ffaf 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -127,6 +127,17 @@ namespace osu.Game.Rulesets.UI return base.Handle(e); } + protected override bool HandleMouseTouchStateChange(TouchStateChangeEvent e) + { + if (mouseDisabled.Value) + { + // Only propagate positional data when mouse buttons are disabled. + e = new TouchStateChangeEvent(e.State, e.Input, e.Touch, false, e.LastPosition); + } + + return base.HandleMouseTouchStateChange(e); + } + #endregion #region Key Counter Attachment From b317a95fe1df3999b3071d62d8ad8ca51e854fd9 Mon Sep 17 00:00:00 2001 From: StanR Date: Wed, 5 Jan 2022 14:36:07 +0300 Subject: [PATCH 098/996] Don't floor `effectiveMissCount` --- .../Difficulty/OsuPerformanceCalculator.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index d7d294df47..a38d9299aa 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty private int countMeh; private int countMiss; - private int effectiveMissCount; + private double effectiveMissCount; public OsuPerformanceCalculator(Ruleset ruleset, DifficultyAttributes attributes, ScoreInfo score) : base(ruleset, attributes, score) @@ -244,7 +244,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty return flashlightValue; } - private int calculateEffectiveMissCount() + private double calculateEffectiveMissCount() { // Guess the number of misses + slider breaks from combo double comboBasedMissCount = 0.0; @@ -256,10 +256,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty comboBasedMissCount = fullComboThreshold / Math.Max(1.0, scoreMaxCombo); } - // Clamp misscount since it's derived from combo and can be higher than total hits and that breaks some calculations + // Clamp miss count since it's derived from combo and can be higher than total hits and that breaks some calculations comboBasedMissCount = Math.Min(comboBasedMissCount, totalHits); - return Math.Max(countMiss, (int)Math.Floor(comboBasedMissCount)); + return Math.Max(countMiss, comboBasedMissCount); } private double getComboScalingFactor() => Attributes.MaxCombo <= 0 ? 1.0 : Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0); From 5a62760fe4f301d7f6a736f35feee14c847f5c96 Mon Sep 17 00:00:00 2001 From: MaxOhn Date: Wed, 5 Jan 2022 13:05:22 +0100 Subject: [PATCH 099/996] hold spinners & minor adjustments --- osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs | 33 ++++++++++++------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs index 306e7a8b24..cde86c8868 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs @@ -5,16 +5,16 @@ using System; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.Osu.UI; using osuTK; using System.Linq; +using osu.Game.Rulesets.Osu.Objects; namespace osu.Game.Rulesets.Osu.Mods { - internal class OsuModAimAssist : Mod, IUpdatableByPlayfield + internal class OsuModAimAssist : Mod, IUpdatableByPlayfield, IApplicableToDrawableRuleset { public override string Name => "Aim Assist"; public override string Acronym => "AA"; @@ -25,7 +25,15 @@ namespace osu.Game.Rulesets.Osu.Mods public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay) }; private const float spin_radius = 30; + private Vector2? prevCursorPos; + private OsuInputManager inputManager; + + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) + { + // Grab the input manager for future use + inputManager = (OsuInputManager)drawableRuleset.KeyBindingInputManager; + } public void Update(Playfield playfield) { @@ -34,20 +42,20 @@ namespace osu.Game.Rulesets.Osu.Mods // Hide judgment displays and follow points playfield.DisplayJudgements.Value = false; - (playfield as OsuPlayfield)?.FollowPoints.Clear(); + (playfield as OsuPlayfield)?.FollowPoints.Hide(); // Move all currently alive object to new destination foreach (var drawable in playfield.HitObjectContainer.AliveObjects.OfType()) { var h = drawable.HitObject; - double endTime = h.GetEndTime(); switch (drawable) { case DrawableHitCircle circle: // 10ms earlier on the note to reduce chance of missing when clicking early / cursor moves fast - circle.MoveTo(cursorPos, Math.Max(0, endTime - currentTime - 10)); + circle.MoveTo(cursorPos, Math.Max(0, h.StartTime - currentTime - 10)); + // FIXME: some circles cause flash at original(?) position when clicked too early break; @@ -61,16 +69,16 @@ namespace osu.Game.Rulesets.Osu.Mods // Move slider so that sliderball stays on the cursor else { - // FIXME: Hide flashes - //slider.HeadCircle.Hide(); + slider.HeadCircle.Hide(); // hide flash, triangles, ... so they don't move with slider slider.MoveTo(cursorPos - slider.Ball.DrawPosition); + // FIXME: some sliders re-appearing at their original position for a single frame when they're done } break; case DrawableSpinner spinner: - // Move spinner to cursor + // Move spinner _next_ to cursor if (currentTime < h.StartTime) { spinner.MoveTo(cursorPos + new Vector2(0, -spin_radius), Math.Max(0, h.StartTime - currentTime - 10)); @@ -89,9 +97,12 @@ namespace osu.Game.Rulesets.Osu.Mods spinner.MoveTo(targetPos); - // Logically finish spinner immediatly, no need for the user to click. - // Temporary workaround until spinner rotations are easier to handle, similar as Autopilot mod. - spinner.Result.RateAdjustedRotation = spinner.HitObject.SpinsRequired * 360; + // Move spinner logically + if (inputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false) + { + // Arbitrary value, might lead to some inconsistencies depending on clock rate, replay, ... + spinner.RotationTracker.AddRotation(2 * MathF.PI); + } } break; From dc755f4a7fbb3e85d967e75bfa16361c07ef8898 Mon Sep 17 00:00:00 2001 From: StanR Date: Wed, 5 Jan 2022 15:07:02 +0300 Subject: [PATCH 100/996] Remove redundant casts --- .../Difficulty/OsuPerformanceCalculator.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index a38d9299aa..31818d7d52 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -97,7 +97,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty // Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses. if (effectiveMissCount > 0) - aimValue *= 0.97 * Math.Pow(1 - Math.Pow((double)effectiveMissCount / totalHits, 0.775), effectiveMissCount); + aimValue *= 0.97 * Math.Pow(1 - Math.Pow(effectiveMissCount / totalHits, 0.775), effectiveMissCount); aimValue *= getComboScalingFactor(); @@ -144,7 +144,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty // Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses. if (effectiveMissCount > 0) - speedValue *= 0.97 * Math.Pow(1 - Math.Pow((double)effectiveMissCount / totalHits, 0.775), Math.Pow(effectiveMissCount, .875)); + speedValue *= 0.97 * Math.Pow(1 - Math.Pow(effectiveMissCount / totalHits, 0.775), Math.Pow(effectiveMissCount, .875)); speedValue *= getComboScalingFactor(); @@ -228,7 +228,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty // Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses. if (effectiveMissCount > 0) - flashlightValue *= 0.97 * Math.Pow(1 - Math.Pow((double)effectiveMissCount / totalHits, 0.775), Math.Pow(effectiveMissCount, .875)); + flashlightValue *= 0.97 * Math.Pow(1 - Math.Pow(effectiveMissCount / totalHits, 0.775), Math.Pow(effectiveMissCount, .875)); flashlightValue *= getComboScalingFactor(); From ee24713002d91838e4531428517de38e5251ec66 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 Jan 2022 14:37:13 +0900 Subject: [PATCH 101/996] Fix single sliders not being flippable due to incorrect precondition --- .../Edit/OsuSelectionHandler.cs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index d51f08ad68..071ecf6329 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -89,17 +89,24 @@ namespace osu.Game.Rulesets.Osu.Edit { var hitObjects = selectedMovableObjects; - if (hitObjects.Length == 1 && !flipOverOrigin) - return false; - var flipQuad = flipOverOrigin ? new Quad(0, 0, OsuPlayfield.BASE_SIZE.X, OsuPlayfield.BASE_SIZE.Y) : getSurroundingQuad(hitObjects); + bool didFlip = false; + foreach (var h in hitObjects) { - h.Position = GetFlippedPosition(direction, flipQuad, h.Position); + var flippedPosition = GetFlippedPosition(direction, flipQuad, h.Position); + + if (!Precision.AlmostEquals(flippedPosition, h.Position)) + { + h.Position = flippedPosition; + didFlip = true; + } if (h is Slider slider) { + didFlip = true; + foreach (var point in slider.Path.ControlPoints) { point.Position = new Vector2( @@ -110,7 +117,7 @@ namespace osu.Game.Rulesets.Osu.Edit } } - return true; + return didFlip; } public override bool HandleScale(Vector2 scale, Anchor reference) From 5c0494f3bae6468e0b7a2791f99c3e02b4a3216b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 Jan 2022 14:39:00 +0900 Subject: [PATCH 102/996] Remove unnecessary precondition check and disallow vertical catch flips for now --- osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs index d39f1d3c86..dd5835b4ed 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs @@ -54,7 +54,11 @@ namespace osu.Game.Rulesets.Catch.Edit public override bool HandleFlip(Direction direction, bool flipOverOrigin) { - if (SelectedItems.Count == 0 && !flipOverOrigin) + if (SelectedItems.Count == 0) + return false; + + // This could be implemented in the future if there's a requirement for it. + if (direction == Direction.Vertical) return false; var selectionRange = CatchHitObjectUtils.GetPositionRange(SelectedItems); From 243a1a3cf7ce26f11f6aea7305a04996be916dcc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 Jan 2022 14:47:44 +0900 Subject: [PATCH 103/996] Fix incorrect origin specification for `SkinSelectionHandler` flips --- osu.Game/Skinning/Editor/SkinSelectionHandler.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index a54590a0ea..bd6d097eb2 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -128,14 +128,14 @@ namespace osu.Game.Skinning.Editor public override bool HandleFlip(Direction direction, bool flipOverOrigin) { - var selectionQuad = flipOverOrigin ? ScreenSpaceDrawQuad : getSelectionQuad(); + var selectionQuad = getSelectionQuad(); Vector2 scaleFactor = direction == Direction.Horizontal ? new Vector2(-1, 1) : new Vector2(1, -1); foreach (var b in SelectedBlueprints) { var drawableItem = (Drawable)b.Item; - var flippedPosition = GetFlippedPosition(direction, selectionQuad, b.ScreenSpaceSelectionPoint); + var flippedPosition = GetFlippedPosition(direction, flipOverOrigin ? drawableItem.Parent.ScreenSpaceDrawQuad : selectionQuad, b.ScreenSpaceSelectionPoint); updateDrawablePosition(drawableItem, flippedPosition); From 04d060aba3f95cb75899d642a964c168679524a4 Mon Sep 17 00:00:00 2001 From: MaxOhn Date: Thu, 6 Jan 2022 10:38:30 +0100 Subject: [PATCH 104/996] update general playfield only once --- osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs index cde86c8868..f983ece71b 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs @@ -33,6 +33,10 @@ namespace osu.Game.Rulesets.Osu.Mods { // Grab the input manager for future use inputManager = (OsuInputManager)drawableRuleset.KeyBindingInputManager; + + // Hide judgment displays and follow points + drawableRuleset.Playfield.DisplayJudgements.Value = false; + (drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide(); } public void Update(Playfield playfield) @@ -40,10 +44,6 @@ namespace osu.Game.Rulesets.Osu.Mods var cursorPos = playfield.Cursor.ActiveCursor.DrawPosition; double currentTime = playfield.Clock.CurrentTime; - // Hide judgment displays and follow points - playfield.DisplayJudgements.Value = false; - (playfield as OsuPlayfield)?.FollowPoints.Hide(); - // Move all currently alive object to new destination foreach (var drawable in playfield.HitObjectContainer.AliveObjects.OfType()) { From 84765b99b36bb66f7aded738867593ad9cb5936b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 6 Jan 2022 12:57:26 +0100 Subject: [PATCH 105/996] Handle score submission request in submission test scene Was previously not handled at all, therefore displaying request failures in the test log output. While that was mostly a red herring and shouldn't have caused any actual *test* failures, it is still better to handle this explicitly in a realistic manner. --- .../TestScenePlayerScoreSubmission.cs | 51 ++++++++++++++----- .../Online/Solo/SubmitSoloScoreRequest.cs | 8 +-- 2 files changed, 42 insertions(+), 17 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs index 25808d307d..5ee1f9dd8f 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs @@ -57,7 +57,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestNoSubmissionOnResultsWithNoToken() { - prepareTokenResponse(false); + prepareTestAPI(false); createPlayerTest(); @@ -77,7 +77,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestSubmissionOnResults() { - prepareTokenResponse(true); + prepareTestAPI(true); createPlayerTest(); @@ -96,7 +96,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestSubmissionForDifferentRuleset() { - prepareTokenResponse(true); + prepareTestAPI(true); createPlayerTest(createRuleset: () => new TaikoRuleset()); @@ -116,7 +116,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestSubmissionForConvertedBeatmap() { - prepareTokenResponse(true); + prepareTestAPI(true); createPlayerTest(createRuleset: () => new ManiaRuleset(), createBeatmap: _ => createTestBeatmap(new OsuRuleset().RulesetInfo)); @@ -136,7 +136,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestNoSubmissionOnExitWithNoToken() { - prepareTokenResponse(false); + prepareTestAPI(false); createPlayerTest(); @@ -153,7 +153,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestNoSubmissionOnEmptyFail() { - prepareTokenResponse(true); + prepareTestAPI(true); createPlayerTest(true); @@ -168,7 +168,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestSubmissionOnFail() { - prepareTokenResponse(true); + prepareTestAPI(true); createPlayerTest(true); @@ -185,7 +185,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestNoSubmissionOnEmptyExit() { - prepareTokenResponse(true); + prepareTestAPI(true); createPlayerTest(); @@ -198,7 +198,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestSubmissionOnExit() { - prepareTokenResponse(true); + prepareTestAPI(true); createPlayerTest(); @@ -213,7 +213,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestSubmissionOnExitDuringImport() { - prepareTokenResponse(true); + prepareTestAPI(true); createPlayerTest(); AddStep("block imports", () => Player.AllowImportCompletion.Wait()); @@ -232,7 +232,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestNoSubmissionOnLocalBeatmap() { - prepareTokenResponse(true); + prepareTestAPI(true); createPlayerTest(false, r => { @@ -253,7 +253,7 @@ namespace osu.Game.Tests.Visual.Gameplay [TestCase(10)] public void TestNoSubmissionOnCustomRuleset(int? rulesetId) { - prepareTokenResponse(true); + prepareTestAPI(true); createPlayerTest(false, createRuleset: () => new OsuRuleset { RulesetInfo = { OnlineID = rulesetId ?? -1 } }); @@ -275,7 +275,7 @@ namespace osu.Game.Tests.Visual.Gameplay })); } - private void prepareTokenResponse(bool validToken) + private void prepareTestAPI(bool validToken) { AddStep("Prepare test API", () => { @@ -289,6 +289,31 @@ namespace osu.Game.Tests.Visual.Gameplay else tokenRequest.TriggerFailure(new APIException("something went wrong!", null)); return true; + + case SubmitSoloScoreRequest submissionRequest: + if (validToken) + { + var requestScore = submissionRequest.Score; + + submissionRequest.TriggerSuccess(new MultiplayerScore + { + ID = 1234, + User = dummyAPI.LocalUser.Value, + Rank = requestScore.Rank, + TotalScore = requestScore.TotalScore, + Accuracy = requestScore.Accuracy, + MaxCombo = requestScore.MaxCombo, + Mods = requestScore.Mods, + Statistics = requestScore.Statistics, + Passed = requestScore.Passed, + EndedAt = DateTimeOffset.Now, + Position = 1 + }); + + return true; + } + + break; } return false; diff --git a/osu.Game/Online/Solo/SubmitSoloScoreRequest.cs b/osu.Game/Online/Solo/SubmitSoloScoreRequest.cs index 99cf5ceff5..78ebddb2e6 100644 --- a/osu.Game/Online/Solo/SubmitSoloScoreRequest.cs +++ b/osu.Game/Online/Solo/SubmitSoloScoreRequest.cs @@ -12,17 +12,17 @@ namespace osu.Game.Online.Solo { public class SubmitSoloScoreRequest : APIRequest { + public readonly SubmittableScore Score; + private readonly long scoreId; private readonly int beatmapId; - private readonly SubmittableScore score; - public SubmitSoloScoreRequest(int beatmapId, long scoreId, ScoreInfo scoreInfo) { this.beatmapId = beatmapId; this.scoreId = scoreId; - score = new SubmittableScore(scoreInfo); + Score = new SubmittableScore(scoreInfo); } protected override WebRequest CreateWebRequest() @@ -33,7 +33,7 @@ namespace osu.Game.Online.Solo req.Method = HttpMethod.Put; req.Timeout = 30000; - req.AddRaw(JsonConvert.SerializeObject(score, new JsonSerializerSettings + req.AddRaw(JsonConvert.SerializeObject(Score, new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore })); From 77980196c5e6473ae1c9ff70ac95f85050a107a0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 Jan 2022 17:15:42 +0900 Subject: [PATCH 106/996] Split out expanding container logic from settings sidebar --- ...Sidebar.cs => ExpandingButtonContainer.cs} | 56 +++++++------------ osu.Game/Overlays/Settings/SettingsSidebar.cs | 37 ++++++++++++ .../Overlays/Settings/SidebarIconButton.cs | 4 +- osu.Game/Overlays/SettingsPanel.cs | 8 +-- osu.Game/Overlays/SettingsSubPanel.cs | 2 +- 5 files changed, 65 insertions(+), 42 deletions(-) rename osu.Game/Overlays/{Settings/Sidebar.cs => ExpandingButtonContainer.cs} (67%) create mode 100644 osu.Game/Overlays/Settings/SettingsSidebar.cs diff --git a/osu.Game/Overlays/Settings/Sidebar.cs b/osu.Game/Overlays/ExpandingButtonContainer.cs similarity index 67% rename from osu.Game/Overlays/Settings/Sidebar.cs rename to osu.Game/Overlays/ExpandingButtonContainer.cs index 93b1b19b17..a49f0493f6 100644 --- a/osu.Game/Overlays/Settings/Sidebar.cs +++ b/osu.Game/Overlays/ExpandingButtonContainer.cs @@ -1,47 +1,47 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; using System.Linq; using osu.Framework; -using osu.Framework.Allocation; 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.Containers; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays.Settings; using osuTK; -namespace osu.Game.Overlays.Settings +namespace osu.Game.Overlays { - public class Sidebar : Container, IStateful + public abstract class ExpandingButtonContainer : Container, IStateful { - private readonly Box background; - private readonly FillFlowContainer content; - public const float DEFAULT_WIDTH = 70; - public const int EXPANDED_WIDTH = 200; + private readonly float contractedWidth; + private readonly float expandedWidth; public event Action StateChanged; - protected override Container Content => content; + protected override Container Content => FillFlow; - public Sidebar() + protected FillFlowContainer FillFlow { get; } + + protected ExpandingButtonContainer(float contractedWidth, float expandedWidth) { + this.contractedWidth = contractedWidth; + this.expandedWidth = expandedWidth; + RelativeSizeAxes = Axes.Y; + Width = contractedWidth; + InternalChildren = new Drawable[] { - background = new Box - { - Colour = OsuColour.Gray(0.02f), - RelativeSizeAxes = Axes.Both, - }, new SidebarScrollContainer { Children = new[] { - content = new FillFlowContainer + FillFlow = new FillFlowContainer { Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, @@ -54,12 +54,6 @@ namespace osu.Game.Overlays.Settings }; } - [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) - { - background.Colour = colourProvider.Background5; - } - private ScheduledDelegate expandEvent; private ExpandedState state; @@ -107,11 +101,11 @@ namespace osu.Game.Overlays.Settings switch (state) { default: - this.ResizeTo(new Vector2(DEFAULT_WIDTH, Height), 500, Easing.OutQuint); + this.ResizeTo(new Vector2(contractedWidth, Height), 500, Easing.OutQuint); break; case ExpandedState.Expanded: - this.ResizeTo(new Vector2(EXPANDED_WIDTH, Height), 500, Easing.OutQuint); + this.ResizeTo(new Vector2(expandedWidth, Height), 500, Easing.OutQuint); break; } @@ -121,15 +115,13 @@ namespace osu.Game.Overlays.Settings private Drawable lastHoveredButton; - private Drawable hoveredButton => content.Children.FirstOrDefault(c => c.IsHovered); + private Drawable hoveredButton => FillFlow.ChildrenOfType().FirstOrDefault(c => c.IsHovered); private void queueExpandIfHovering() { // only expand when we hover a different button. if (lastHoveredButton == hoveredButton) return; - if (!IsHovered) return; - if (State != ExpandedState.Expanded) { expandEvent?.Cancel(); @@ -139,10 +131,4 @@ namespace osu.Game.Overlays.Settings lastHoveredButton = hoveredButton; } } - - public enum ExpandedState - { - Contracted, - Expanded, - } } diff --git a/osu.Game/Overlays/Settings/SettingsSidebar.cs b/osu.Game/Overlays/Settings/SettingsSidebar.cs new file mode 100644 index 0000000000..5aa2e61162 --- /dev/null +++ b/osu.Game/Overlays/Settings/SettingsSidebar.cs @@ -0,0 +1,37 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; + +namespace osu.Game.Overlays.Settings +{ + public class SettingsSidebar : ExpandingButtonContainer + { + public const float DEFAULT_WIDTH = 70; + public const int EXPANDED_WIDTH = 200; + + public SettingsSidebar() + : base(DEFAULT_WIDTH, EXPANDED_WIDTH) + { + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + AddInternal(new Box + { + Colour = colourProvider.Background5, + RelativeSizeAxes = Axes.Both, + Depth = float.MaxValue + }); + } + } + + public enum ExpandedState + { + Contracted, + Expanded, + } +} diff --git a/osu.Game/Overlays/Settings/SidebarIconButton.cs b/osu.Game/Overlays/Settings/SidebarIconButton.cs index fd57996b1b..6f3d3d5d52 100644 --- a/osu.Game/Overlays/Settings/SidebarIconButton.cs +++ b/osu.Game/Overlays/Settings/SidebarIconButton.cs @@ -62,14 +62,14 @@ namespace osu.Game.Overlays.Settings { textIconContent = new Container { - Width = Sidebar.DEFAULT_WIDTH, + Width = SettingsSidebar.DEFAULT_WIDTH, RelativeSizeAxes = Axes.Y, Colour = OsuColour.Gray(0.6f), Children = new Drawable[] { headerText = new OsuSpriteText { - Position = new Vector2(Sidebar.DEFAULT_WIDTH + 10, 0), + Position = new Vector2(SettingsSidebar.DEFAULT_WIDTH + 10, 0), Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, }, diff --git a/osu.Game/Overlays/SettingsPanel.cs b/osu.Game/Overlays/SettingsPanel.cs index 0ceb7fc50d..ba7118cffe 100644 --- a/osu.Game/Overlays/SettingsPanel.cs +++ b/osu.Game/Overlays/SettingsPanel.cs @@ -27,7 +27,7 @@ namespace osu.Game.Overlays public const float TRANSITION_LENGTH = 600; - private const float sidebar_width = Sidebar.DEFAULT_WIDTH; + private const float sidebar_width = SettingsSidebar.DEFAULT_WIDTH; /// /// The width of the settings panel content, excluding the sidebar. @@ -43,7 +43,7 @@ namespace osu.Game.Overlays protected override Container Content => ContentContainer; - protected Sidebar Sidebar; + protected SettingsSidebar Sidebar; private SidebarIconButton selectedSidebarButton; public SettingsSectionsContainer SectionsContainer { get; private set; } @@ -129,7 +129,7 @@ namespace osu.Game.Overlays if (showSidebar) { - AddInternal(Sidebar = new Sidebar { Width = sidebar_width }); + AddInternal(Sidebar = new SettingsSidebar { Width = sidebar_width }); } CreateSections()?.ForEach(AddSection); @@ -244,7 +244,7 @@ namespace osu.Game.Overlays if (selectedSidebarButton != null) selectedSidebarButton.Selected = false; - selectedSidebarButton = Sidebar.Children.FirstOrDefault(b => b.Section == section.NewValue); + selectedSidebarButton = Sidebar.Children.OfType().FirstOrDefault(b => b.Section == section.NewValue); if (selectedSidebarButton != null) selectedSidebarButton.Selected = true; diff --git a/osu.Game/Overlays/SettingsSubPanel.cs b/osu.Game/Overlays/SettingsSubPanel.cs index a65d792a9f..da806c09d3 100644 --- a/osu.Game/Overlays/SettingsSubPanel.cs +++ b/osu.Game/Overlays/SettingsSubPanel.cs @@ -39,7 +39,7 @@ namespace osu.Game.Overlays [BackgroundDependencyLoader] private void load() { - Size = new Vector2(Sidebar.DEFAULT_WIDTH); + Size = new Vector2(SettingsSidebar.DEFAULT_WIDTH); AddRange(new Drawable[] { From 5baaf356aac038145197dc8f5524a83ae5ad50d4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 Jan 2022 21:05:00 +0900 Subject: [PATCH 107/996] Split out `SettingsToolboxGroup` from `PlayerSettingsGroup` --- osu.Game/Overlays/SettingsToolboxGroup.cs | 165 ++++++++++++++++++ .../PlayerSettings/PlayerSettingsGroup.cs | 155 +--------------- 2 files changed, 172 insertions(+), 148 deletions(-) create mode 100644 osu.Game/Overlays/SettingsToolboxGroup.cs diff --git a/osu.Game/Overlays/SettingsToolboxGroup.cs b/osu.Game/Overlays/SettingsToolboxGroup.cs new file mode 100644 index 0000000000..c154284fb9 --- /dev/null +++ b/osu.Game/Overlays/SettingsToolboxGroup.cs @@ -0,0 +1,165 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Overlays +{ + public abstract class SettingsToolboxGroup : Container + { + private const float transition_duration = 250; + private const int container_width = 270; + private const int border_thickness = 2; + private const int header_height = 30; + private const int corner_radius = 5; + + private readonly FillFlowContainer content; + private readonly IconButton button; + + private bool expanded = true; + + public bool Expanded + { + get => expanded; + set + { + if (expanded == value) return; + + expanded = value; + + content.ClearTransforms(); + + if (expanded) + content.AutoSizeAxes = Axes.Y; + else + { + content.AutoSizeAxes = Axes.None; + content.ResizeHeightTo(0, transition_duration, Easing.OutQuint); + } + + updateExpanded(); + } + } + + private Color4 expandedColour; + + /// + /// Create a new instance. + /// + /// The title to be displayed in the header of this group. + protected SettingsToolboxGroup(string title) + { + AutoSizeAxes = Axes.Y; + Width = container_width; + Masking = true; + CornerRadius = corner_radius; + BorderColour = Color4.Black; + BorderThickness = border_thickness; + + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + Alpha = 0.5f, + }, + new FillFlowContainer + { + Direction = FillDirection.Vertical, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + new Container + { + Name = @"Header", + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + Height = header_height, + Children = new Drawable[] + { + new OsuSpriteText + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Text = title.ToUpperInvariant(), + Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 17), + Margin = new MarginPadding { Left = 10 }, + }, + button = new IconButton + { + Origin = Anchor.Centre, + Anchor = Anchor.CentreRight, + Position = new Vector2(-15, 0), + Icon = FontAwesome.Solid.Bars, + Scale = new Vector2(0.75f), + Action = () => Expanded = !Expanded, + }, + } + }, + content = new FillFlowContainer + { + Name = @"Content", + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Direction = FillDirection.Vertical, + RelativeSizeAxes = Axes.X, + AutoSizeDuration = transition_duration, + AutoSizeEasing = Easing.OutQuint, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding(15), + Spacing = new Vector2(0, 15), + } + } + }, + }; + } + + private const float fade_duration = 800; + private const float inactive_alpha = 0.5f; + + protected override void LoadComplete() + { + base.LoadComplete(); + this.Delay(600).FadeTo(inactive_alpha, fade_duration, Easing.OutQuint); + } + + protected override bool OnHover(HoverEvent e) + { + this.FadeIn(fade_duration, Easing.OutQuint); + return false; + } + + protected override void OnHoverLost(HoverLostEvent e) + { + this.FadeTo(inactive_alpha, fade_duration, Easing.OutQuint); + base.OnHoverLost(e); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + expandedColour = colours.Yellow; + + updateExpanded(); + } + + private void updateExpanded() => button.FadeColour(expanded ? expandedColour : Color4.White, 200, Easing.InOutQuint); + + protected override Container Content => content; + + protected override bool OnMouseDown(MouseDownEvent e) => true; + } +} diff --git a/osu.Game/Screens/Play/PlayerSettings/PlayerSettingsGroup.cs b/osu.Game/Screens/Play/PlayerSettings/PlayerSettingsGroup.cs index 7928d41e3b..0bbe6902f4 100644 --- a/osu.Game/Screens/Play/PlayerSettings/PlayerSettingsGroup.cs +++ b/osu.Game/Screens/Play/PlayerSettings/PlayerSettingsGroup.cs @@ -1,165 +1,24 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using 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.Input.Events; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; -using osuTK; -using osuTK.Graphics; +using osu.Game.Overlays; namespace osu.Game.Screens.Play.PlayerSettings { - public abstract class PlayerSettingsGroup : Container + public class PlayerSettingsGroup : SettingsToolboxGroup { - private const float transition_duration = 250; - private const int container_width = 270; - private const int border_thickness = 2; - private const int header_height = 30; - private const int corner_radius = 5; - - private readonly FillFlowContainer content; - private readonly IconButton button; - - private bool expanded = true; - - public bool Expanded + public PlayerSettingsGroup(string title) + : base(title) { - get => expanded; - set - { - if (expanded == value) return; - - expanded = value; - - content.ClearTransforms(); - - if (expanded) - content.AutoSizeAxes = Axes.Y; - else - { - content.AutoSizeAxes = Axes.None; - content.ResizeHeightTo(0, transition_duration, Easing.OutQuint); - } - - updateExpanded(); - } - } - - private Color4 expandedColour; - - /// - /// Create a new instance. - /// - /// The title to be displayed in the header of this group. - protected PlayerSettingsGroup(string title) - { - AutoSizeAxes = Axes.Y; - Width = container_width; - Masking = true; - CornerRadius = corner_radius; - BorderColour = Color4.Black; - BorderThickness = border_thickness; - - InternalChildren = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - Alpha = 0.5f, - }, - new FillFlowContainer - { - Direction = FillDirection.Vertical, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Children = new Drawable[] - { - new Container - { - Name = @"Header", - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - RelativeSizeAxes = Axes.X, - Height = header_height, - Children = new Drawable[] - { - new OsuSpriteText - { - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - Text = title.ToUpperInvariant(), - Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 17), - Margin = new MarginPadding { Left = 10 }, - }, - button = new IconButton - { - Origin = Anchor.Centre, - Anchor = Anchor.CentreRight, - Position = new Vector2(-15, 0), - Icon = FontAwesome.Solid.Bars, - Scale = new Vector2(0.75f), - Action = () => Expanded = !Expanded, - }, - } - }, - content = new FillFlowContainer - { - Name = @"Content", - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - Direction = FillDirection.Vertical, - RelativeSizeAxes = Axes.X, - AutoSizeDuration = transition_duration, - AutoSizeEasing = Easing.OutQuint, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding(15), - Spacing = new Vector2(0, 15), - } - } - }, - }; - } - - private const float fade_duration = 800; - private const float inactive_alpha = 0.5f; - - protected override void LoadComplete() - { - base.LoadComplete(); - this.Delay(600).FadeTo(inactive_alpha, fade_duration, Easing.OutQuint); } protected override bool OnHover(HoverEvent e) { - this.FadeIn(fade_duration, Easing.OutQuint); + base.OnHover(e); + + // Importantly, return true to correctly take focus away from PlayerLoader. return true; } - - protected override void OnHoverLost(HoverLostEvent e) - { - this.FadeTo(inactive_alpha, fade_duration, Easing.OutQuint); - base.OnHoverLost(e); - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - expandedColour = colours.Yellow; - - updateExpanded(); - } - - private void updateExpanded() => button.FadeColour(expanded ? expandedColour : Color4.White, 200, Easing.InOutQuint); - - protected override Container Content => content; - - protected override bool OnMouseDown(MouseDownEvent e) => true; } } From 66613cbaa4551c055a5e756787cb36ab0fe9072c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 6 Jan 2022 13:09:25 +0100 Subject: [PATCH 108/996] Wait for async continuation in score submission test The previous assert step was optimistically assuming that the async continuation that writes the value of `FakeImportingPlayer.ImportedScore` always completes before it, but that's not necessarily true even if the continuation is instant (it is still subject to things like task scheduling and TPL thread pool limits). To ensure no spurious failures, swap out the assert step for an until step instead. --- .../Visual/Gameplay/TestScenePlayerScoreSubmission.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs index 5ee1f9dd8f..cf5aadde6d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs @@ -226,7 +226,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("exit", () => Player.Exit()); AddStep("allow import to proceed", () => Player.AllowImportCompletion.Release(1)); - AddAssert("ensure submission", () => Player.SubmittedScore != null && Player.ImportedScore != null); + AddUntilStep("ensure submission", () => Player.SubmittedScore != null && Player.ImportedScore != null); } [Test] From cea9cab4dcca4da45b98cb868e89a65e391869c9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 Jan 2022 21:10:45 +0900 Subject: [PATCH 109/996] Use `ExpandingButtonContainer` in editor composer --- osu.Game/Overlays/SettingsToolboxGroup.cs | 4 ++- ...{ToolboxGroup.cs => EditorToolboxGroup.cs} | 6 ++--- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 26 ++++++++++++------- .../Rulesets/Edit/ScrollingToolboxGroup.cs | 2 +- 4 files changed, 23 insertions(+), 15 deletions(-) rename osu.Game/Rulesets/Edit/{ToolboxGroup.cs => EditorToolboxGroup.cs} (71%) diff --git a/osu.Game/Overlays/SettingsToolboxGroup.cs b/osu.Game/Overlays/SettingsToolboxGroup.cs index c154284fb9..02b5bdc1e1 100644 --- a/osu.Game/Overlays/SettingsToolboxGroup.cs +++ b/osu.Game/Overlays/SettingsToolboxGroup.cs @@ -94,9 +94,11 @@ namespace osu.Game.Overlays { Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, + RelativeSizeAxes = Axes.X, + Truncate = true, Text = title.ToUpperInvariant(), Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 17), - Margin = new MarginPadding { Left = 10 }, + Padding = new MarginPadding { Left = 10, Right = 30 }, }, button = new IconButton { diff --git a/osu.Game/Rulesets/Edit/ToolboxGroup.cs b/osu.Game/Rulesets/Edit/EditorToolboxGroup.cs similarity index 71% rename from osu.Game/Rulesets/Edit/ToolboxGroup.cs rename to osu.Game/Rulesets/Edit/EditorToolboxGroup.cs index 22b2b05657..bde426f56a 100644 --- a/osu.Game/Rulesets/Edit/ToolboxGroup.cs +++ b/osu.Game/Rulesets/Edit/EditorToolboxGroup.cs @@ -2,13 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; -using osu.Game.Screens.Play.PlayerSettings; +using osu.Game.Overlays; namespace osu.Game.Rulesets.Edit { - public class ToolboxGroup : PlayerSettingsGroup + public class EditorToolboxGroup : SettingsToolboxGroup { - public ToolboxGroup(string title) + public EditorToolboxGroup(string title) : base(title) { RelativeSizeAxes = Axes.X; diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index cbc2415603..92ea2db338 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -13,6 +13,7 @@ using osu.Framework.Input; using osu.Framework.Input.Events; using osu.Framework.Logging; using osu.Game.Beatmaps; +using osu.Game.Overlays; using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Mods; @@ -98,8 +99,6 @@ namespace osu.Game.Rulesets.Edit dependencies.CacheAs(Playfield); - const float toolbar_width = 200; - InternalChildren = new Drawable[] { new Container @@ -116,20 +115,15 @@ namespace osu.Game.Rulesets.Edit .WithChild(BlueprintContainer = CreateBlueprintContainer()) } }, - new FillFlowContainer + new LeftToolboxFlow { - Name = "Sidebar", - RelativeSizeAxes = Axes.Y, - Width = toolbar_width, - Padding = new MarginPadding { Right = 10 }, - Spacing = new Vector2(10), Children = new Drawable[] { - new ToolboxGroup("toolbox (1-9)") + new EditorToolboxGroup("toolbox (1-9)") { Child = toolboxCollection = new EditorRadioButtonCollection { RelativeSizeAxes = Axes.X } }, - new ToolboxGroup("toggles (Q~P)") + new EditorToolboxGroup("toggles (Q~P)") { Child = togglesCollection = new FillFlowContainer { @@ -426,6 +420,18 @@ namespace osu.Game.Rulesets.Edit } #endregion + + private class LeftToolboxFlow : ExpandingButtonContainer + { + public LeftToolboxFlow() + : base(80, 200) + { + RelativeSizeAxes = Axes.Y; + Padding = new MarginPadding { Right = 10 }; + + FillFlow.Spacing = new Vector2(10); + } + } } /// diff --git a/osu.Game/Rulesets/Edit/ScrollingToolboxGroup.cs b/osu.Game/Rulesets/Edit/ScrollingToolboxGroup.cs index a54f574bff..9998a997b3 100644 --- a/osu.Game/Rulesets/Edit/ScrollingToolboxGroup.cs +++ b/osu.Game/Rulesets/Edit/ScrollingToolboxGroup.cs @@ -7,7 +7,7 @@ using osu.Game.Graphics.Containers; namespace osu.Game.Rulesets.Edit { - public class ScrollingToolboxGroup : ToolboxGroup + public class ScrollingToolboxGroup : EditorToolboxGroup { protected readonly OsuScrollContainer Scroll; From 3ea7588a91261fce0969d56f4032ea217c34f925 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 Jan 2022 15:54:10 +0900 Subject: [PATCH 110/996] Update continuation usages to use `GetCompletedResult` --- osu.Game/Beatmaps/BeatmapDifficultyCache.cs | 8 +++++--- osu.Game/Database/OnlineLookupCache.cs | 2 +- osu.Game/Online/Chat/ChannelManager.cs | 6 ++++-- osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs | 2 +- osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs | 2 +- .../Overlays/Settings/Sections/General/UpdateSettings.cs | 2 +- osu.Game/Scoring/ScoreManager.cs | 2 +- .../Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs | 2 +- osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs | 2 +- .../Ranking/Expanded/Statistics/PerformanceStatistic.cs | 2 +- osu.Game/Screens/Ranking/ScorePanelList.cs | 2 +- osu.Game/Screens/Select/Details/AdvancedStats.cs | 4 ++-- .../Screens/Select/Leaderboards/BeatmapLeaderboard.cs | 4 ++-- osu.Game/Screens/Spectate/SpectatorScreen.cs | 2 +- osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs | 2 +- 15 files changed, 24 insertions(+), 20 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs index 0e4dab2b96..45dadc4e3f 100644 --- a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs +++ b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs @@ -262,7 +262,7 @@ namespace osu.Game.Beatmaps // GetDifficultyAsync will fall back to existing data from IBeatmapInfo if not locally available // (contrary to GetAsync) GetDifficultyAsync(bindable.BeatmapInfo, rulesetInfo, mods, cancellationToken) - .ContinueWith(t => + .ContinueWith(task => { // We're on a threadpool thread, but we should exit back to the update thread so consumers can safely handle value-changed events. Schedule(() => @@ -270,8 +270,10 @@ namespace osu.Game.Beatmaps if (cancellationToken.IsCancellationRequested) return; - if (t.WaitSafelyForResult() is StarDifficulty sd) - bindable.Value = sd; + var starDifficulty = task.GetCompletedResult(); + + if (starDifficulty != null) + bindable.Value = starDifficulty.Value; }); }, cancellationToken); } diff --git a/osu.Game/Database/OnlineLookupCache.cs b/osu.Game/Database/OnlineLookupCache.cs index b392336bb4..f5847428ee 100644 --- a/osu.Game/Database/OnlineLookupCache.cs +++ b/osu.Game/Database/OnlineLookupCache.cs @@ -59,7 +59,7 @@ namespace osu.Game.Database if (!task.IsCompletedSuccessfully) return null; - return task.WaitSafelyForResult(); + return task.GetCompletedResult(); }, token)); } diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 3b27dd5c5d..52d5fa1642 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -536,8 +536,10 @@ namespace osu.Game.Online.Chat // Try to get user in order to open PM chat users.GetUserAsync((int)lastClosedChannel.Id).ContinueWith(task => { - if (task.WaitSafelyForResult() is APIUser u) - Schedule(() => CurrentChannel.Value = JoinChannel(new Channel(u))); + var user = task.GetCompletedResult(); + + if (user != null) + Schedule(() => CurrentChannel.Value = JoinChannel(new Channel(user))); }); } diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index adb1adeed4..0f25be2d3c 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -85,7 +85,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores if (loadCancellationSource.IsCancellationRequested) return; - var scores = task.WaitSafelyForResult(); + var scores = task.GetCompletedResult(); var topScore = scores.First(); diff --git a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs index b7d236c263..73b199f159 100644 --- a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs +++ b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs @@ -64,7 +64,7 @@ namespace osu.Game.Overlays.Dashboard { users.GetUserAsync(id).ContinueWith(task => { - var user = task.WaitSafelyForResult(); + var user = task.GetCompletedResult(); if (user == null) return; diff --git a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs index 49eb80d993..8dbae420ac 100644 --- a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs @@ -48,7 +48,7 @@ namespace osu.Game.Overlays.Settings.Sections.General checkForUpdatesButton.Enabled.Value = false; Task.Run(updateManager.CheckForUpdateAsync).ContinueWith(task => Schedule(() => { - if (!task.WaitSafelyForResult()) + if (!task.GetCompletedResult()) { notifications?.Post(new SimpleNotification { diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 9715cd64a7..b4edb23689 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -113,7 +113,7 @@ namespace osu.Game.Scoring public void GetTotalScore([NotNull] ScoreInfo score, [NotNull] Action callback, ScoringMode mode = ScoringMode.Standardised, CancellationToken cancellationToken = default) { GetTotalScoreAsync(score, mode, cancellationToken) - .ContinueWith(task => scheduler.Add(() => callback(task.WaitSafelyForResult())), TaskContinuationOptions.OnlyOnRanToCompletion); + .ContinueWith(task => scheduler.Add(() => callback(task.GetCompletedResult())), TaskContinuationOptions.OnlyOnRanToCompletion); } /// diff --git a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs index 6f2a76b3cb..c384d97229 100644 --- a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs @@ -79,7 +79,7 @@ namespace osu.Game.Screens.Play.HUD userLookupCache.GetUsersAsync(playingUsers.Select(u => u.UserID).ToArray()).ContinueWith(task => Schedule(() => { - var users = task.WaitSafelyForResult(); + var users = task.GetCompletedResult(); foreach (var user in users) { diff --git a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs index f543cea15c..897678640f 100644 --- a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs +++ b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs @@ -76,7 +76,7 @@ namespace osu.Game.Screens.Play.HUD difficultyCache.GetTimedDifficultyAttributesAsync(gameplayWorkingBeatmap, gameplayState.Ruleset, clonedMods, loadCancellationSource.Token) .ContinueWith(task => Schedule(() => { - timedAttributes = task.WaitSafelyForResult(); + timedAttributes = task.GetCompletedResult(); IsValid = true; diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs index 90ecd3642b..1d00043244 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs @@ -38,7 +38,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics else { performanceCache.CalculatePerformanceAsync(score, cancellationTokenSource.Token) - .ContinueWith(t => Schedule(() => setPerformanceValue(t.WaitSafelyForResult())), cancellationTokenSource.Token); + .ContinueWith(t => Schedule(() => setPerformanceValue(t.GetCompletedResult())), cancellationTokenSource.Token); } } diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index b29f2302d5..5ae6b9d417 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -154,7 +154,7 @@ namespace osu.Game.Screens.Ranking scoreManager.GetTotalScoreAsync(score) .ContinueWith(task => Schedule(() => { - flow.SetLayoutPosition(trackingContainer, task.WaitSafelyForResult()); + flow.SetLayoutPosition(trackingContainer, task.GetCompletedResult()); trackingContainer.Show(); diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index e7764b23be..dacc8c11e1 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -153,8 +153,8 @@ namespace osu.Game.Screens.Select.Details Task.WhenAll(normalStarDifficultyTask, moddedStarDifficultyTask).ContinueWith(_ => Schedule(() => { - var normalDifficulty = normalStarDifficultyTask.WaitSafelyForResult(); - var moddeDifficulty = moddedStarDifficultyTask.WaitSafelyForResult(); + var normalDifficulty = normalStarDifficultyTask.GetCompletedResult(); + var moddeDifficulty = moddedStarDifficultyTask.GetCompletedResult(); if (normalDifficulty == null || moddeDifficulty == null) return; diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 6943f2a3b6..fbfca130ba 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -143,7 +143,7 @@ namespace osu.Game.Screens.Select.Leaderboards } scoreManager.OrderByTotalScoreAsync(scores.ToArray(), cancellationToken) - .ContinueWith(ordered => scoresCallback?.Invoke(ordered.WaitSafelyForResult()), TaskContinuationOptions.OnlyOnRanToCompletion); + .ContinueWith(task => scoresCallback?.Invoke(task.GetCompletedResult()), TaskContinuationOptions.OnlyOnRanToCompletion); return null; } @@ -184,7 +184,7 @@ namespace osu.Game.Screens.Select.Leaderboards if (cancellationToken.IsCancellationRequested) return; - scoresCallback?.Invoke(task.WaitSafelyForResult()); + scoresCallback?.Invoke(task.GetCompletedResult()); TopScore = r.UserScore?.CreateScoreInfo(rulesets, fetchBeatmapInfo); }), TaskContinuationOptions.OnlyOnRanToCompletion); }; diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs index f361b0cee4..44a31d89ed 100644 --- a/osu.Game/Screens/Spectate/SpectatorScreen.cs +++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs @@ -60,7 +60,7 @@ namespace osu.Game.Screens.Spectate userLookupCache.GetUsersAsync(users.ToArray()).ContinueWith(task => Schedule(() => { - var foundUsers = task.WaitSafelyForResult(); + var foundUsers = task.GetCompletedResult(); foreach (var u in foundUsers) { diff --git a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs index 1b3e49de37..589857b458 100644 --- a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs +++ b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs @@ -148,7 +148,7 @@ namespace osu.Game.Tests.Beatmaps if (!conversionTask.Wait(10000)) Assert.Fail("Conversion timed out"); - return conversionTask.WaitSafelyForResult(); + return conversionTask.GetCompletedResult(); } protected virtual void OnConversionGenerated(HitObject original, IEnumerable result, IBeatmapConverter beatmapConverter) From 00177a3ae195130dcabe9657c76c78a4b809f481 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 Jan 2022 22:54:43 +0900 Subject: [PATCH 111/996] Update usages to new naming --- CodeAnalysis/BannedSymbols.txt | 2 +- osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs | 4 ++-- .../Beatmaps/TestSceneBeatmapDifficultyCache.cs | 2 +- osu.Game.Tests/NonVisual/TaskChainTest.cs | 10 +++++----- osu.Game.Tests/Skins/IO/ImportSkinTest.cs | 4 ++-- osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs | 2 +- osu.Game.Tests/Skins/TestSceneSkinResources.cs | 2 +- .../Visual/Editing/TestSceneDifficultySwitching.cs | 2 +- .../Visual/Editing/TestSceneEditorTestGameplay.cs | 2 +- .../Visual/Gameplay/TestSceneReplayDownloadButton.cs | 2 +- osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs | 2 +- .../Visual/Menus/TestSceneMusicActionHandling.cs | 2 +- .../Multiplayer/TestSceneMultiSpectatorScreen.cs | 2 +- .../TestSceneMultiplayerGameplayLeaderboard.cs | 4 ++-- .../TestSceneMultiplayerGameplayLeaderboardTeams.cs | 2 +- .../Visual/Navigation/TestScenePresentBeatmap.cs | 2 +- .../Visual/Navigation/TestScenePresentScore.cs | 4 ++-- .../Visual/Playlists/TestScenePlaylistsRoomCreation.cs | 2 +- .../SongSelect/TestSceneBeatmapRecommendations.cs | 2 +- .../Visual/SongSelect/TestScenePlaySongSelect.cs | 2 +- .../Visual/UserInterface/TestSceneDeleteLocalScore.cs | 4 ++-- .../TestSceneUpdateableBeatmapBackgroundSprite.cs | 2 +- osu.Game/Beatmaps/BeatmapDifficultyCache.cs | 2 +- osu.Game/Beatmaps/BeatmapManager.cs | 2 +- osu.Game/Beatmaps/WorkingBeatmap.cs | 2 +- osu.Game/Database/OnlineLookupCache.cs | 2 +- osu.Game/Online/Chat/ChannelManager.cs | 2 +- osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs | 2 +- osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs | 2 +- .../Settings/Sections/General/UpdateSettings.cs | 2 +- osu.Game/Scoring/ScoreManager.cs | 2 +- osu.Game/Screens/Menu/IntroScreen.cs | 2 +- .../Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs | 2 +- osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs | 2 +- osu.Game/Screens/Play/Player.cs | 2 +- .../Ranking/Expanded/ExpandedPanelMiddleContent.cs | 2 +- .../Expanded/Statistics/PerformanceStatistic.cs | 2 +- osu.Game/Screens/Select/Details/AdvancedStats.cs | 4 ++-- .../Screens/Select/Leaderboards/BeatmapLeaderboard.cs | 4 ++-- osu.Game/Screens/Spectate/SpectatorScreen.cs | 2 +- osu.Game/Skinning/SkinManager.cs | 2 +- osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs | 2 +- 42 files changed, 53 insertions(+), 53 deletions(-) diff --git a/CodeAnalysis/BannedSymbols.txt b/CodeAnalysis/BannedSymbols.txt index 4428656301..e96ad48325 100644 --- a/CodeAnalysis/BannedSymbols.txt +++ b/CodeAnalysis/BannedSymbols.txt @@ -14,4 +14,4 @@ M:Realms.IRealmCollection`1.SubscribeForNotifications`1(Realms.NotificationCallb M:Realms.CollectionExtensions.SubscribeForNotifications`1(System.Linq.IQueryable{``0},Realms.NotificationCallbackDelegate{``0});Use osu.Game.Database.RealmObjectExtensions.QueryAsyncWithNotifications(IQueryable,NotificationCallbackDelegate) instead. M:Realms.CollectionExtensions.SubscribeForNotifications`1(System.Collections.Generic.IList{``0},Realms.NotificationCallbackDelegate{``0});Use osu.Game.Database.RealmObjectExtensions.QueryAsyncWithNotifications(IList,NotificationCallbackDelegate) instead. M:System.Threading.Tasks.Task.Wait();Don't use Task.Wait. Use Task.WaitSafely() to ensure we avoid deadlocks. -P:System.Threading.Tasks.Task`1.Result;Don't use Task.Result. Use Task.WaitSafelyForResult() to ensure we avoid deadlocks. +P:System.Threading.Tasks.Task`1.Result;Don't use Task.Result. Use Task.GetResultSafely() to ensure we avoid deadlocks. diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 2a2cc2e086..c02141bf9f 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -984,7 +984,7 @@ namespace osu.Game.Tests.Beatmaps.IO var manager = osu.Dependencies.Get(); - var importedSet = manager.Import(new ImportTask(temp)).WaitSafelyForResult(); + var importedSet = manager.Import(new ImportTask(temp)).GetResultSafely(); ensureLoaded(osu).WaitSafely(); @@ -999,7 +999,7 @@ namespace osu.Game.Tests.Beatmaps.IO var manager = osu.Dependencies.Get(); - var importedSet = manager.Import(new ImportTask(temp)).WaitSafelyForResult(); + var importedSet = manager.Import(new ImportTask(temp)).GetResultSafely(); ensureLoaded(osu).WaitSafely(); diff --git a/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs b/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs index a97b372db0..26ab8808b9 100644 --- a/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs +++ b/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs @@ -33,7 +33,7 @@ namespace osu.Game.Tests.Beatmaps [BackgroundDependencyLoader] private void load(OsuGameBase osu) { - importedSet = ImportBeatmapTest.LoadQuickOszIntoOsu(osu).WaitSafelyForResult(); + importedSet = ImportBeatmapTest.LoadQuickOszIntoOsu(osu).GetResultSafely(); } [SetUpSteps] diff --git a/osu.Game.Tests/NonVisual/TaskChainTest.cs b/osu.Game.Tests/NonVisual/TaskChainTest.cs index db3cecf8ea..3678279035 100644 --- a/osu.Game.Tests/NonVisual/TaskChainTest.cs +++ b/osu.Game.Tests/NonVisual/TaskChainTest.cs @@ -43,9 +43,9 @@ namespace osu.Game.Tests.NonVisual await Task.WhenAll(task1.task, task2.task, task3.task); - Assert.That(task1.task.WaitSafelyForResult(), Is.EqualTo(1)); - Assert.That(task2.task.WaitSafelyForResult(), Is.EqualTo(2)); - Assert.That(task3.task.WaitSafelyForResult(), Is.EqualTo(3)); + Assert.That(task1.task.GetResultSafely(), Is.EqualTo(1)); + Assert.That(task2.task.GetResultSafely(), Is.EqualTo(2)); + Assert.That(task3.task.GetResultSafely(), Is.EqualTo(3)); } [Test] @@ -69,9 +69,9 @@ namespace osu.Game.Tests.NonVisual // Wait on both tasks. await Task.WhenAll(task1.task, task3.task); - Assert.That(task1.task.WaitSafelyForResult(), Is.EqualTo(1)); + Assert.That(task1.task.GetResultSafely(), Is.EqualTo(1)); Assert.That(task2.task.IsCompleted, Is.False); - Assert.That(task3.task.WaitSafelyForResult(), Is.EqualTo(2)); + Assert.That(task3.task.GetResultSafely(), Is.EqualTo(2)); } [Test] diff --git a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs index 55a50fb825..3f063264e0 100644 --- a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs +++ b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs @@ -188,7 +188,7 @@ namespace osu.Game.Tests.Skins.IO var imported = skinManager.Import(new ImportTask(exportStream, "exported.osk")); - imported.WaitSafelyForResult().PerformRead(s => + imported.GetResultSafely().PerformRead(s => { Assert.IsFalse(s.Protected); Assert.AreNotEqual(originalSkinId, s.ID); @@ -223,7 +223,7 @@ namespace osu.Game.Tests.Skins.IO var imported = skinManager.Import(new ImportTask(exportStream, "exported.osk")); - imported.WaitSafelyForResult().PerformRead(s => + imported.GetResultSafely().PerformRead(s => { Assert.IsFalse(s.Protected); Assert.AreNotEqual(originalSkinId, s.ID); diff --git a/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs b/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs index a4c2ec3b34..c20ab84a68 100644 --- a/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs +++ b/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs @@ -25,7 +25,7 @@ namespace osu.Game.Tests.Skins [BackgroundDependencyLoader] private void load() { - var imported = beatmaps.Import(new ZipArchiveReader(TestResources.OpenResource("Archives/ogg-beatmap.osz"))).WaitSafelyForResult(); + var imported = beatmaps.Import(new ZipArchiveReader(TestResources.OpenResource("Archives/ogg-beatmap.osz"))).GetResultSafely(); beatmap = beatmaps.GetWorkingBeatmap(imported.Value.Beatmaps[0]); beatmap.LoadTrack(); } diff --git a/osu.Game.Tests/Skins/TestSceneSkinResources.cs b/osu.Game.Tests/Skins/TestSceneSkinResources.cs index a10e2436d6..0271198049 100644 --- a/osu.Game.Tests/Skins/TestSceneSkinResources.cs +++ b/osu.Game.Tests/Skins/TestSceneSkinResources.cs @@ -24,7 +24,7 @@ namespace osu.Game.Tests.Skins [BackgroundDependencyLoader] private void load() { - var imported = skins.Import(new ZipArchiveReader(TestResources.OpenResource("Archives/ogg-skin.osk"))).WaitSafelyForResult(); + var imported = skins.Import(new ZipArchiveReader(TestResources.OpenResource("Archives/ogg-skin.osk"))).GetResultSafely(); skin = imported.PerformRead(skinInfo => skins.GetSkin(skinInfo)); } diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs b/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs index 5dd5bad0d2..516305079b 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs @@ -33,7 +33,7 @@ namespace osu.Game.Tests.Visual.Editing public override void SetUpSteps() { - AddStep("import test beatmap", () => importedBeatmapSet = ImportBeatmapTest.LoadOszIntoOsu(game, virtualTrack: true).WaitSafelyForResult()); + AddStep("import test beatmap", () => importedBeatmapSet = ImportBeatmapTest.LoadOszIntoOsu(game, virtualTrack: true).GetResultSafely()); base.SetUpSteps(); } diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs index fcbae44c76..6d48ef3ba7 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs @@ -39,7 +39,7 @@ namespace osu.Game.Tests.Visual.Editing public override void SetUpSteps() { - AddStep("import test beatmap", () => importedBeatmapSet = ImportBeatmapTest.LoadOszIntoOsu(game).WaitSafelyForResult()); + AddStep("import test beatmap", () => importedBeatmapSet = ImportBeatmapTest.LoadOszIntoOsu(game).GetResultSafely()); base.SetUpSteps(); } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs index 7f5ac5cf48..3168c4b94e 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs @@ -136,7 +136,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("state is not downloaded", () => downloadButton.State.Value == DownloadState.NotDownloaded); - AddStep("import score", () => imported = scoreManager.Import(getScoreInfo(true)).WaitSafelyForResult()); + AddStep("import score", () => imported = scoreManager.Import(getScoreInfo(true)).GetResultSafely()); AddUntilStep("state is available", () => downloadButton.State.Value == DownloadState.LocallyAvailable); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index 90065e3b51..242eca0bbc 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -61,7 +61,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("import beatmap", () => { - importedBeatmap = ImportBeatmapTest.LoadOszIntoOsu(game, virtualTrack: true).WaitSafelyForResult(); + importedBeatmap = ImportBeatmapTest.LoadOszIntoOsu(game, virtualTrack: true).GetResultSafely(); importedBeatmapId = importedBeatmap.Beatmaps.First(b => b.RulesetID == 0).OnlineID ?? -1; }); } diff --git a/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs b/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs index c82bde3444..ee9363fa12 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs @@ -38,7 +38,7 @@ namespace osu.Game.Tests.Visual.Menus AddStep("import beatmap with track", () => { - var setWithTrack = Game.BeatmapManager.Import(new ImportTask(TestResources.GetTestBeatmapForImport())).WaitSafelyForResult(); + var setWithTrack = Game.BeatmapManager.Import(new ImportTask(TestResources.GetTestBeatmapForImport())).GetResultSafely(); Beatmap.Value = Game.BeatmapManager.GetWorkingBeatmap(setWithTrack.Value.Beatmaps.First()); }); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index 3f24da2c9e..61058bc87a 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -47,7 +47,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load() { - importedSet = ImportBeatmapTest.LoadOszIntoOsu(game, virtualTrack: true).WaitSafelyForResult(); + importedSet = ImportBeatmapTest.LoadOszIntoOsu(game, virtualTrack: true).GetResultSafely(); importedBeatmap = importedSet.Beatmaps.First(b => b.RulesetID == 0); importedBeatmapId = importedBeatmap.OnlineID ?? -1; } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs index 6b40a4fc9a..07a8ef66e1 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs @@ -44,7 +44,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { base.SetUpSteps(); - AddStep("set local user", () => ((DummyAPIAccess)API).LocalUser.Value = LookupCache.GetUserAsync(1).WaitSafelyForResult()); + AddStep("set local user", () => ((DummyAPIAccess)API).LocalUser.Value = LookupCache.GetUserAsync(1).GetResultSafely()); AddStep("create leaderboard", () => { @@ -91,7 +91,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public void TestUserQuit() { foreach (int user in users) - AddStep($"mark user {user} quit", () => Client.RemoveUser(LookupCache.GetUserAsync(user).WaitSafelyForResult().AsNonNull())); + AddStep($"mark user {user} quit", () => Client.RemoveUser(LookupCache.GetUserAsync(user).GetResultSafely().AsNonNull())); } [Test] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs index f5c079573d..1237a21e94 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs @@ -48,7 +48,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { base.SetUpSteps(); - AddStep("set local user", () => ((DummyAPIAccess)API).LocalUser.Value = LookupCache.GetUserAsync(1).WaitSafelyForResult()); + AddStep("set local user", () => ((DummyAPIAccess)API).LocalUser.Value = LookupCache.GetUserAsync(1).GetResultSafely()); AddStep("create leaderboard", () => { diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs b/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs index 9d495f7d05..6420e7b849 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs @@ -127,7 +127,7 @@ namespace osu.Game.Tests.Visual.Navigation Ruleset = ruleset ?? new OsuRuleset().RulesetInfo }, } - }).WaitSafelyForResult().Value; + }).GetResultSafely().Value; }); AddAssert($"import {i} succeeded", () => imported != null); diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs index 16c416fedc..5dc1808c12 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs @@ -58,7 +58,7 @@ namespace osu.Game.Tests.Visual.Navigation Ruleset = new OsuRuleset().RulesetInfo }, } - }).WaitSafelyForResult().Value; + }).GetResultSafely().Value; }); } @@ -132,7 +132,7 @@ namespace osu.Game.Tests.Visual.Navigation OnlineID = i, BeatmapInfo = beatmap.Beatmaps.First(), Ruleset = ruleset ?? new OsuRuleset().RulesetInfo - }).WaitSafelyForResult().Value; + }).GetResultSafely().Value; }); AddAssert($"import {i} succeeded", () => imported != null); diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs index 0c5985ad67..e59884f4f4 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs @@ -202,7 +202,7 @@ namespace osu.Game.Tests.Visual.Playlists }); } - private void importBeatmap() => AddStep("import beatmap", () => importedBeatmap = manager.Import(CreateBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo.BeatmapSet).WaitSafelyForResult()); + private void importBeatmap() => AddStep("import beatmap", () => importedBeatmap = manager.Import(CreateBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo.BeatmapSet).GetResultSafely()); private class TestPlaylistsRoomSubScreen : PlaylistsRoomSubScreen { diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs index 071e3225f0..08b5802713 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs @@ -182,7 +182,7 @@ namespace osu.Game.Tests.Visual.SongSelect beatmap.DifficultyName = $"SR{i + 1}"; } - return Game.BeatmapManager.Import(beatmapSet).WaitSafelyForResult().Value; + return Game.BeatmapManager.Import(beatmapSet).GetResultSafely().Value; } private bool ensureAllBeatmapSetsImported(IEnumerable beatmapSets) => beatmapSets.All(set => set != null); diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index ea667d02f3..37f110e727 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -760,7 +760,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("import huge difficulty count map", () => { var usableRulesets = rulesets.AvailableRulesets.Where(r => r.OnlineID != 2).ToArray(); - imported = manager.Import(TestResources.CreateTestBeatmapSetInfo(50, usableRulesets)).WaitSafelyForResult().Value; + imported = manager.Import(TestResources.CreateTestBeatmapSetInfo(50, usableRulesets)).GetResultSafely().Value; }); AddStep("select the first beatmap of import", () => Beatmap.Value = manager.GetWorkingBeatmap(imported.Beatmaps.First())); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index bca396b46b..a436fc0bfa 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -86,7 +86,7 @@ namespace osu.Game.Tests.Visual.UserInterface dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesetStore, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default)); dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, ContextFactory, Scheduler)); - beatmapInfo = beatmapManager.Import(new ImportTask(TestResources.GetQuickTestBeatmapForImport())).WaitSafelyForResult().Value.Beatmaps[0]; + beatmapInfo = beatmapManager.Import(new ImportTask(TestResources.GetQuickTestBeatmapForImport())).GetResultSafely().Value.Beatmaps[0]; for (int i = 0; i < 50; i++) { @@ -102,7 +102,7 @@ namespace osu.Game.Tests.Visual.UserInterface User = new APIUser { Username = "TestUser" }, }; - importedScores.Add(scoreManager.Import(score).WaitSafelyForResult().Value); + importedScores.Add(scoreManager.Import(score).GetResultSafely().Value); } return dependencies; diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs index f78cbdffc1..6fe1ccc037 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs @@ -32,7 +32,7 @@ namespace osu.Game.Tests.Visual.UserInterface { this.api = api; - testBeatmap = ImportBeatmapTest.LoadOszIntoOsu(osu).WaitSafelyForResult(); + testBeatmap = ImportBeatmapTest.LoadOszIntoOsu(osu).GetResultSafely(); } [Test] diff --git a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs index 45dadc4e3f..f760c25170 100644 --- a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs +++ b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs @@ -270,7 +270,7 @@ namespace osu.Game.Beatmaps if (cancellationToken.IsCancellationRequested) return; - var starDifficulty = task.GetCompletedResult(); + var starDifficulty = task.GetResultSafely(); if (starDifficulty != null) bindable.Value = starDifficulty.Value; diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index c461135266..ed7fe0bc91 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -92,7 +92,7 @@ namespace osu.Game.Beatmaps } }; - var imported = beatmapModelManager.Import(set).WaitSafelyForResult().Value; + var imported = beatmapModelManager.Import(set).GetResultSafely().Value; return GetWorkingBeatmap(imported.Beatmaps.First()); } diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 85e30b6e3d..8289b32d31 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -186,7 +186,7 @@ namespace osu.Game.Beatmaps { try { - return loadBeatmapAsync().WaitSafelyForResult(); + return loadBeatmapAsync().GetResultSafely(); } catch (AggregateException ae) { diff --git a/osu.Game/Database/OnlineLookupCache.cs b/osu.Game/Database/OnlineLookupCache.cs index f5847428ee..2f98aef58a 100644 --- a/osu.Game/Database/OnlineLookupCache.cs +++ b/osu.Game/Database/OnlineLookupCache.cs @@ -59,7 +59,7 @@ namespace osu.Game.Database if (!task.IsCompletedSuccessfully) return null; - return task.GetCompletedResult(); + return task.GetResultSafely(); }, token)); } diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 52d5fa1642..82e042ae4e 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -536,7 +536,7 @@ namespace osu.Game.Online.Chat // Try to get user in order to open PM chat users.GetUserAsync((int)lastClosedChannel.Id).ContinueWith(task => { - var user = task.GetCompletedResult(); + var user = task.GetResultSafely(); if (user != null) Schedule(() => CurrentChannel.Value = JoinChannel(new Channel(user))); diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 0f25be2d3c..a40f29abf2 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -85,7 +85,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores if (loadCancellationSource.IsCancellationRequested) return; - var scores = task.GetCompletedResult(); + var scores = task.GetResultSafely(); var topScore = scores.First(); diff --git a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs index 73b199f159..fde20575fc 100644 --- a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs +++ b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs @@ -64,7 +64,7 @@ namespace osu.Game.Overlays.Dashboard { users.GetUserAsync(id).ContinueWith(task => { - var user = task.GetCompletedResult(); + var user = task.GetResultSafely(); if (user == null) return; diff --git a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs index 8dbae420ac..158d8811b5 100644 --- a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs @@ -48,7 +48,7 @@ namespace osu.Game.Overlays.Settings.Sections.General checkForUpdatesButton.Enabled.Value = false; Task.Run(updateManager.CheckForUpdateAsync).ContinueWith(task => Schedule(() => { - if (!task.GetCompletedResult()) + if (!task.GetResultSafely()) { notifications?.Post(new SimpleNotification { diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index b4edb23689..39cd28cad2 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -113,7 +113,7 @@ namespace osu.Game.Scoring public void GetTotalScore([NotNull] ScoreInfo score, [NotNull] Action callback, ScoringMode mode = ScoringMode.Standardised, CancellationToken cancellationToken = default) { GetTotalScoreAsync(score, mode, cancellationToken) - .ContinueWith(task => scheduler.Add(() => callback(task.GetCompletedResult())), TaskContinuationOptions.OnlyOnRanToCompletion); + .ContinueWith(task => scheduler.Add(() => callback(task.GetResultSafely())), TaskContinuationOptions.OnlyOnRanToCompletion); } /// diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index e7731631ff..948e3a7d88 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -111,7 +111,7 @@ namespace osu.Game.Screens.Menu { // if we detect that the theme track or beatmap is unavailable this is either first startup or things are in a bad state. // this could happen if a user has nuked their files store. for now, reimport to repair this. - var import = beatmaps.Import(new ZipArchiveReader(game.Resources.GetStream($"Tracks/{BeatmapFile}"), BeatmapFile)).WaitSafelyForResult(); + var import = beatmaps.Import(new ZipArchiveReader(game.Resources.GetStream($"Tracks/{BeatmapFile}"), BeatmapFile)).GetResultSafely(); import.PerformWrite(b => { diff --git a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs index c384d97229..83c73e5a70 100644 --- a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs @@ -79,7 +79,7 @@ namespace osu.Game.Screens.Play.HUD userLookupCache.GetUsersAsync(playingUsers.Select(u => u.UserID).ToArray()).ContinueWith(task => Schedule(() => { - var users = task.GetCompletedResult(); + var users = task.GetResultSafely(); foreach (var user in users) { diff --git a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs index 897678640f..21a7698248 100644 --- a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs +++ b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs @@ -76,7 +76,7 @@ namespace osu.Game.Screens.Play.HUD difficultyCache.GetTimedDifficultyAttributesAsync(gameplayWorkingBeatmap, gameplayState.Ruleset, clonedMods, loadCancellationSource.Token) .ContinueWith(task => Schedule(() => { - timedAttributes = task.GetCompletedResult(); + timedAttributes = task.GetResultSafely(); IsValid = true; diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 262424258a..2a6f5e2398 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -771,7 +771,7 @@ namespace osu.Game.Screens.Play // This player instance may already be in the process of exiting. return; - this.Push(CreateResults(prepareScoreForDisplayTask.WaitSafelyForResult())); + this.Push(CreateResults(prepareScoreForDisplayTask.GetResultSafely())); }, Time.Current + delay, 50); Scheduler.Add(resultsDisplayDelegate); diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs index 87940c61aa..7e39708e65 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -80,7 +80,7 @@ namespace osu.Game.Screens.Ranking.Expanded statisticDisplays.AddRange(topStatistics); statisticDisplays.AddRange(bottomStatistics); - var starDifficulty = beatmapDifficultyCache.GetDifficultyAsync(beatmap, score.Ruleset, score.Mods).WaitSafelyForResult(); + var starDifficulty = beatmapDifficultyCache.GetDifficultyAsync(beatmap, score.Ruleset, score.Mods).GetResultSafely(); AddInternal(new FillFlowContainer { diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs index 1d00043244..d6e4cfbe51 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs @@ -38,7 +38,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics else { performanceCache.CalculatePerformanceAsync(score, cancellationTokenSource.Token) - .ContinueWith(t => Schedule(() => setPerformanceValue(t.GetCompletedResult())), cancellationTokenSource.Token); + .ContinueWith(t => Schedule(() => setPerformanceValue(t.GetResultSafely())), cancellationTokenSource.Token); } } diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index dacc8c11e1..354ee325d0 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -153,8 +153,8 @@ namespace osu.Game.Screens.Select.Details Task.WhenAll(normalStarDifficultyTask, moddedStarDifficultyTask).ContinueWith(_ => Schedule(() => { - var normalDifficulty = normalStarDifficultyTask.GetCompletedResult(); - var moddeDifficulty = moddedStarDifficultyTask.GetCompletedResult(); + var normalDifficulty = normalStarDifficultyTask.GetResultSafely(); + var moddeDifficulty = moddedStarDifficultyTask.GetResultSafely(); if (normalDifficulty == null || moddeDifficulty == null) return; diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index fbfca130ba..0102986070 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -143,7 +143,7 @@ namespace osu.Game.Screens.Select.Leaderboards } scoreManager.OrderByTotalScoreAsync(scores.ToArray(), cancellationToken) - .ContinueWith(task => scoresCallback?.Invoke(task.GetCompletedResult()), TaskContinuationOptions.OnlyOnRanToCompletion); + .ContinueWith(task => scoresCallback?.Invoke(task.GetResultSafely()), TaskContinuationOptions.OnlyOnRanToCompletion); return null; } @@ -184,7 +184,7 @@ namespace osu.Game.Screens.Select.Leaderboards if (cancellationToken.IsCancellationRequested) return; - scoresCallback?.Invoke(task.GetCompletedResult()); + scoresCallback?.Invoke(task.GetResultSafely()); TopScore = r.UserScore?.CreateScoreInfo(rulesets, fetchBeatmapInfo); }), TaskContinuationOptions.OnlyOnRanToCompletion); }; diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs index 44a31d89ed..c4e75cc413 100644 --- a/osu.Game/Screens/Spectate/SpectatorScreen.cs +++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs @@ -60,7 +60,7 @@ namespace osu.Game.Screens.Spectate userLookupCache.GetUsersAsync(users.ToArray()).ContinueWith(task => Schedule(() => { - var foundUsers = task.GetCompletedResult(); + var foundUsers = task.GetResultSafely(); foreach (var u in foundUsers) { diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index a55cc332c5..cde21b78c1 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -154,7 +154,7 @@ namespace osu.Game.Skinning Name = s.Name + @" (modified)", Creator = s.Creator, InstantiationInfo = s.InstantiationInfo, - }).WaitSafelyForResult(); + }).GetResultSafely(); if (result != null) { diff --git a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs index 589857b458..897d4363f1 100644 --- a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs +++ b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs @@ -148,7 +148,7 @@ namespace osu.Game.Tests.Beatmaps if (!conversionTask.Wait(10000)) Assert.Fail("Conversion timed out"); - return conversionTask.GetCompletedResult(); + return conversionTask.GetResultSafely(); } protected virtual void OnConversionGenerated(HitObject original, IEnumerable result, IBeatmapConverter beatmapConverter) From 690b425380cfb0c7e6bcb74066ccbf164fbc09da Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 Jan 2022 22:56:56 +0900 Subject: [PATCH 112/996] Move enum local to usage --- osu.Game/Overlays/ExpandingButtonContainer.cs | 7 ++++++- osu.Game/Overlays/Settings/SettingsSidebar.cs | 6 ------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/ExpandingButtonContainer.cs b/osu.Game/Overlays/ExpandingButtonContainer.cs index a49f0493f6..5964f7f263 100644 --- a/osu.Game/Overlays/ExpandingButtonContainer.cs +++ b/osu.Game/Overlays/ExpandingButtonContainer.cs @@ -11,7 +11,6 @@ using osu.Framework.Testing; using osu.Framework.Threading; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; -using osu.Game.Overlays.Settings; using osuTK; namespace osu.Game.Overlays @@ -131,4 +130,10 @@ namespace osu.Game.Overlays lastHoveredButton = hoveredButton; } } + + public enum ExpandedState + { + Contracted, + Expanded, + } } diff --git a/osu.Game/Overlays/Settings/SettingsSidebar.cs b/osu.Game/Overlays/Settings/SettingsSidebar.cs index 5aa2e61162..e6ce90c33e 100644 --- a/osu.Game/Overlays/Settings/SettingsSidebar.cs +++ b/osu.Game/Overlays/Settings/SettingsSidebar.cs @@ -28,10 +28,4 @@ namespace osu.Game.Overlays.Settings }); } } - - public enum ExpandedState - { - Contracted, - Expanded, - } } From 5aca2dd4ce17a4d39375e6b6577490b7f67abe46 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 Jan 2022 23:08:50 +0900 Subject: [PATCH 113/996] Hide header text when it won't fit in the toolbox group --- osu.Game/Overlays/SettingsToolboxGroup.cs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/SettingsToolboxGroup.cs b/osu.Game/Overlays/SettingsToolboxGroup.cs index 02b5bdc1e1..fcc7ff2504 100644 --- a/osu.Game/Overlays/SettingsToolboxGroup.cs +++ b/osu.Game/Overlays/SettingsToolboxGroup.cs @@ -2,11 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; +using osu.Framework.Layout; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; @@ -53,6 +55,8 @@ namespace osu.Game.Overlays private Color4 expandedColour; + private readonly OsuSpriteText headerText; + /// /// Create a new instance. /// @@ -90,12 +94,10 @@ namespace osu.Game.Overlays Height = header_height, Children = new Drawable[] { - new OsuSpriteText + headerText = new OsuSpriteText { Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, - RelativeSizeAxes = Axes.X, - Truncate = true, Text = title.ToUpperInvariant(), Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 17), Padding = new MarginPadding { Left = 10, Right = 30 }, @@ -132,6 +134,16 @@ namespace osu.Game.Overlays private const float fade_duration = 800; private const float inactive_alpha = 0.5f; + protected override bool OnInvalidate(Invalidation invalidation, InvalidationSource source) + { + // These toolbox grouped may be contracted to only show icons. + // For now, let's hide the header to avoid text truncation weirdness in such cases. + if (invalidation.HasFlagFast(Invalidation.DrawSize)) + headerText.FadeTo(headerText.DrawWidth < DrawWidth ? 1 : 0, 150, Easing.OutQuint); + + return base.OnInvalidate(invalidation, source); + } + protected override void LoadComplete() { base.LoadComplete(); From f703c5f03879f68a8996e7e30959f4cf0fa4f97f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 Jan 2022 23:38:54 +0900 Subject: [PATCH 114/996] Add comment and reduce how often `ChildrenOfType` is invoked in `ExpandingButtonContainer` --- osu.Game/Overlays/ExpandingButtonContainer.cs | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/osu.Game/Overlays/ExpandingButtonContainer.cs b/osu.Game/Overlays/ExpandingButtonContainer.cs index 5964f7f263..4eb8c47a1f 100644 --- a/osu.Game/Overlays/ExpandingButtonContainer.cs +++ b/osu.Game/Overlays/ExpandingButtonContainer.cs @@ -65,7 +65,7 @@ namespace osu.Game.Overlays protected override void OnHoverLost(HoverLostEvent e) { expandEvent?.Cancel(); - lastHoveredButton = null; + hoveredButton = null; State = ExpandedState.Contracted; base.OnHoverLost(e); @@ -112,22 +112,24 @@ namespace osu.Game.Overlays } } - private Drawable lastHoveredButton; - - private Drawable hoveredButton => FillFlow.ChildrenOfType().FirstOrDefault(c => c.IsHovered); + private Drawable hoveredButton; private void queueExpandIfHovering() { - // only expand when we hover a different button. - if (lastHoveredButton == hoveredButton) return; + // if the same button is hovered, let the scheduled expand play out.. + if (hoveredButton?.IsHovered == true) + return; - if (State != ExpandedState.Expanded) - { - expandEvent?.Cancel(); + // ..otherwise check whether a new button is hovered, and if so, queue a new hover operation. + + // usually we wouldn't use ChildrenOfType in implementations, but this is the simplest way + // to handle cases like the editor where the buttons may be nested within a child hierarchy. + hoveredButton = FillFlow.ChildrenOfType().FirstOrDefault(c => c.IsHovered); + + expandEvent?.Cancel(); + + if (hoveredButton?.IsHovered == true && State != ExpandedState.Expanded) expandEvent = Scheduler.AddDelayed(() => State = ExpandedState.Expanded, 750); - } - - lastHoveredButton = hoveredButton; } } From b9d2a10530695421bf5eee2ddad792718624493d Mon Sep 17 00:00:00 2001 From: MaxOhn Date: Thu, 6 Jan 2022 14:47:58 +0100 Subject: [PATCH 115/996] adjustable assist strength + dont update spinner & running slider --- osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs | 82 ++++--------------- 1 file changed, 16 insertions(+), 66 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs index f983ece71b..c7cabd7ab4 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs @@ -11,6 +11,8 @@ using osu.Game.Rulesets.Osu.UI; using osuTK; using System.Linq; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Configuration; +using osu.Framework.Bindables; namespace osu.Game.Rulesets.Osu.Mods { @@ -24,16 +26,16 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay) }; - private const float spin_radius = 30; - - private Vector2? prevCursorPos; - private OsuInputManager inputManager; + [SettingSource("Assist strength", "Change the distance notes should travel towards you.", 0)] + public BindableFloat AssistStrength { get; } = new BindableFloat(0.3f) + { + Precision = 0.05f, + MinValue = 0.0f, + MaxValue = 1.0f, + }; public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { - // Grab the input manager for future use - inputManager = (OsuInputManager)drawableRuleset.KeyBindingInputManager; - // Hide judgment displays and follow points drawableRuleset.Playfield.DisplayJudgements.Value = false; (drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide(); @@ -41,75 +43,23 @@ namespace osu.Game.Rulesets.Osu.Mods public void Update(Playfield playfield) { - var cursorPos = playfield.Cursor.ActiveCursor.DrawPosition; + Vector2 cursorPos = playfield.Cursor.ActiveCursor.DrawPosition; double currentTime = playfield.Clock.CurrentTime; - // Move all currently alive object to new destination foreach (var drawable in playfield.HitObjectContainer.AliveObjects.OfType()) { var h = drawable.HitObject; - switch (drawable) + if (currentTime < h.StartTime && (drawable is DrawableHitCircle || drawable is DrawableSlider)) { - case DrawableHitCircle circle: + double timeMoving = currentTime - (h.StartTime - h.TimePreempt); + float percentDoneMoving = (float)(timeMoving / h.TimePreempt); + float percentDistLeft = Math.Clamp(AssistStrength.Value - percentDoneMoving + 0.1f, 0, 1); - // 10ms earlier on the note to reduce chance of missing when clicking early / cursor moves fast - circle.MoveTo(cursorPos, Math.Max(0, h.StartTime - currentTime - 10)); - // FIXME: some circles cause flash at original(?) position when clicked too early - - break; - - case DrawableSlider slider: - - // Move slider to cursor - if (currentTime < h.StartTime) - { - slider.MoveTo(cursorPos, Math.Max(0, h.StartTime - currentTime - 10)); - } - // Move slider so that sliderball stays on the cursor - else - { - slider.HeadCircle.Hide(); // hide flash, triangles, ... so they don't move with slider - slider.MoveTo(cursorPos - slider.Ball.DrawPosition); - // FIXME: some sliders re-appearing at their original position for a single frame when they're done - } - - break; - - case DrawableSpinner spinner: - - // Move spinner _next_ to cursor - if (currentTime < h.StartTime) - { - spinner.MoveTo(cursorPos + new Vector2(0, -spin_radius), Math.Max(0, h.StartTime - currentTime - 10)); - } - else - { - // Move spinner visually - Vector2 delta = spin_radius * (spinner.Position - prevCursorPos ?? cursorPos).Normalized(); - const float angle = 3 * MathF.PI / 180; // radians per update, arbitrary value - - // Rotation matrix - var targetPos = new Vector2( - delta.X * MathF.Cos(angle) - delta.Y * MathF.Sin(angle) + cursorPos.X, - delta.X * MathF.Sin(angle) + delta.Y * MathF.Cos(angle) + cursorPos.Y - ); - - spinner.MoveTo(targetPos); - - // Move spinner logically - if (inputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false) - { - // Arbitrary value, might lead to some inconsistencies depending on clock rate, replay, ... - spinner.RotationTracker.AddRotation(2 * MathF.PI); - } - } - - break; + Vector2 targetPos = drawable.Position + percentDistLeft * (cursorPos - drawable.Position); + drawable.MoveTo(targetPos, h.StartTime - currentTime); } } - - prevCursorPos = cursorPos; } } } From 197ada1a8cf43c7fcb9db2fffa072e310be22b9e Mon Sep 17 00:00:00 2001 From: MaxOhn Date: Thu, 6 Jan 2022 16:04:38 +0100 Subject: [PATCH 116/996] naive 10hz update --- osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs index c7cabd7ab4..89a385dbdc 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs @@ -34,6 +34,8 @@ namespace osu.Game.Rulesets.Osu.Mods MaxValue = 1.0f, }; + private DateTime? lastUpdate; + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { // Hide judgment displays and follow points @@ -43,6 +45,9 @@ namespace osu.Game.Rulesets.Osu.Mods public void Update(Playfield playfield) { + if (DateTime.Now - (lastUpdate ?? DateTime.MinValue) < TimeSpan.FromMilliseconds(100)) + return; + Vector2 cursorPos = playfield.Cursor.ActiveCursor.DrawPosition; double currentTime = playfield.Clock.CurrentTime; @@ -60,6 +65,8 @@ namespace osu.Game.Rulesets.Osu.Mods drawable.MoveTo(targetPos, h.StartTime - currentTime); } } + + lastUpdate = DateTime.Now; } } } From b3230868cc88ca7ada83ea6c4c8f0542c333c569 Mon Sep 17 00:00:00 2001 From: MaxOhn Date: Thu, 6 Jan 2022 16:31:30 +0100 Subject: [PATCH 117/996] use playfield clock --- osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs index 89a385dbdc..a383b533fd 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Mods MaxValue = 1.0f, }; - private DateTime? lastUpdate; + private double? lastUpdate; public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { @@ -45,11 +45,12 @@ namespace osu.Game.Rulesets.Osu.Mods public void Update(Playfield playfield) { - if (DateTime.Now - (lastUpdate ?? DateTime.MinValue) < TimeSpan.FromMilliseconds(100)) + double currentTime = playfield.Clock.CurrentTime; + + if (currentTime - (lastUpdate ?? double.MinValue) < 100) return; Vector2 cursorPos = playfield.Cursor.ActiveCursor.DrawPosition; - double currentTime = playfield.Clock.CurrentTime; foreach (var drawable in playfield.HitObjectContainer.AliveObjects.OfType()) { @@ -66,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.Mods } } - lastUpdate = DateTime.Now; + lastUpdate = currentTime; } } } From e02863f7806e4ddeb49564aef48ac68bb537569b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 7 Jan 2022 01:24:30 +0900 Subject: [PATCH 118/996] Avoid accessing `DrawWidth` from invalidation --- osu.Game/Overlays/SettingsToolboxGroup.cs | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/SettingsToolboxGroup.cs b/osu.Game/Overlays/SettingsToolboxGroup.cs index fcc7ff2504..ff8966d55f 100644 --- a/osu.Game/Overlays/SettingsToolboxGroup.cs +++ b/osu.Game/Overlays/SettingsToolboxGroup.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Caching; using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -25,6 +26,11 @@ namespace osu.Game.Overlays private const int header_height = 30; private const int corner_radius = 5; + private const float fade_duration = 800; + private const float inactive_alpha = 0.5f; + + private readonly Cached headerTextVisibilityCache = new Cached(); + private readonly FillFlowContainer content; private readonly IconButton button; @@ -131,19 +137,24 @@ namespace osu.Game.Overlays }; } - private const float fade_duration = 800; - private const float inactive_alpha = 0.5f; - protected override bool OnInvalidate(Invalidation invalidation, InvalidationSource source) { - // These toolbox grouped may be contracted to only show icons. - // For now, let's hide the header to avoid text truncation weirdness in such cases. if (invalidation.HasFlagFast(Invalidation.DrawSize)) - headerText.FadeTo(headerText.DrawWidth < DrawWidth ? 1 : 0, 150, Easing.OutQuint); + headerTextVisibilityCache.Invalidate(); return base.OnInvalidate(invalidation, source); } + protected override void Update() + { + base.Update(); + + if (!headerTextVisibilityCache.IsValid) + // These toolbox grouped may be contracted to only show icons. + // For now, let's hide the header to avoid text truncation weirdness in such cases. + headerText.FadeTo(headerText.DrawWidth < DrawWidth ? 1 : 0, 150, Easing.OutQuint); + } + protected override void LoadComplete() { base.LoadComplete(); From 8f744c99ee0647b5786ef971f08bc513d5d0ed6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 6 Jan 2022 20:25:03 +0100 Subject: [PATCH 119/996] Fix settings toolbox toggle button starting in incorrect state While displaying replays, the colour of the toolbox toggle button would not match the actual state of the rest of the toolbox, i.e. both buttons would be white, even though the "playback settings" section was expanded and as such should have a yellow toggle button. In the case of the replay player, the failure scenario was as follows: 1. `SettingsToolboxGroup` calls `updateExpanded()` in its BDL to update the initial state of the toolbox, including the toggle button colour, by adding a colour fade transform. 2. An ancestor of both the toolbox groups - `PlayerSettingsOverlay`, which is a `VisibilityContainer` - calls `FinishTransforms(true)` in its `LoadCompleteAsync()`, therefore instantly applying the colour from point (1) to the toggle button instantly. 3. However, `IconButton` inherits from `OsuAnimatedButton`. And `OsuAnimatedButton` changes its colour in `LoadComplete()`, therefore undoing the instant application from point (2). This conjunction of circumstances is instrumental to reproducing the bug, because if the `FinishTransforms(true)` call wasn't there, point (3) wouldn't matter - the transform would get applied at some indeterminate point in the future, ignoring the write from `OsuAnimatedButton`. As for the fix, move the `updateExpanded()` call in `SettingsToolboxGroup` to `LoadComplete()` to avoid the above unfortunate order. Applying initial visual state in `LoadComplete()` is the idiomatic style of doing things these days anyhow. --- osu.Game/Overlays/SettingsToolboxGroup.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/SettingsToolboxGroup.cs b/osu.Game/Overlays/SettingsToolboxGroup.cs index ff8966d55f..ca0980a9c9 100644 --- a/osu.Game/Overlays/SettingsToolboxGroup.cs +++ b/osu.Game/Overlays/SettingsToolboxGroup.cs @@ -158,7 +158,9 @@ namespace osu.Game.Overlays protected override void LoadComplete() { base.LoadComplete(); + this.Delay(600).FadeTo(inactive_alpha, fade_duration, Easing.OutQuint); + updateExpanded(); } protected override bool OnHover(HoverEvent e) @@ -177,8 +179,6 @@ namespace osu.Game.Overlays private void load(OsuColour colours) { expandedColour = colours.Yellow; - - updateExpanded(); } private void updateExpanded() => button.FadeColour(expanded ? expandedColour : Color4.White, 200, Easing.InOutQuint); From b0d61a18b05424bdc5b39b248a36c5d139ad241c Mon Sep 17 00:00:00 2001 From: pikokr Date: Fri, 7 Jan 2022 15:57:30 +0900 Subject: [PATCH 120/996] Load keyBindingContainer once on LoadComplete() & make touch area height to const --- osu.Game.Rulesets.Mania/UI/Column.cs | 43 ++++++---------------------- 1 file changed, 9 insertions(+), 34 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 1e07c84d9f..cd5f3d2170 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -28,6 +28,7 @@ namespace osu.Game.Rulesets.Mania.UI { public const float COLUMN_WIDTH = 80; public const float SPECIAL_COLUMN_WIDTH = 70; + public const float TOUCH_AREA_HEIGHT = 100; /// /// The index of this column as part of the whole playfield. @@ -44,60 +45,34 @@ namespace osu.Game.Rulesets.Mania.UI private readonly GameplaySampleTriggerSource sampleTriggerSource; - public class ColumnTouchInputArea : Container + public class ColumnTouchInputArea : Drawable { - private Column column => (Column)Parent; - - private Container hintContainer; - public ColumnTouchInputArea() { RelativeSizeAxes = Axes.X; Anchor = Anchor.BottomCentre; Origin = Anchor.BottomCentre; - Height = 100; - InternalChild = hintContainer = new Container - { - RelativeSizeAxes = Axes.Both, - BorderColour = Color4.Red, - BorderThickness = 5, - Masking = true, - }; + Height = TOUCH_AREA_HEIGHT; } + private Column column => (Column)Parent; + protected override void LoadComplete() { - hintContainer.Delay(1000).FadeOutFromOne(500, Easing.OutSine); + keyBindingContainer = (ManiaInputManager.RulesetKeyBindingContainer)((ManiaInputManager)GetContainingInputManager()).KeyBindingContainer; } private ManiaInputManager.RulesetKeyBindingContainer keyBindingContainer { get; set; } - private ManiaInputManager.RulesetKeyBindingContainer getKeyBindingContainer() - { - return keyBindingContainer ??= (ManiaInputManager.RulesetKeyBindingContainer)((ManiaInputManager)GetContainingInputManager()).KeyBindingContainer; - } - protected override bool OnTouchDown(TouchDownEvent e) { - getKeyBindingContainer().TriggerPressed(column.Action.Value); - return base.OnTouchDown(e); - } - - protected override bool OnMouseDown(MouseDownEvent e) - { - getKeyBindingContainer().TriggerPressed(column.Action.Value); - - return base.OnMouseDown(e); - } - - protected override void OnMouseUp(MouseUpEvent e) - { - getKeyBindingContainer().TriggerReleased(column.Action.Value); + keyBindingContainer.TriggerPressed(column.Action.Value); + return false; } protected override void OnTouchUp(TouchUpEvent e) { - getKeyBindingContainer().TriggerReleased(column.Action.Value); + keyBindingContainer.TriggerReleased(column.Action.Value); } } From 6a1e1d186f7294c869e76501b088f46cac4a3973 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 7 Jan 2022 17:29:09 +0900 Subject: [PATCH 121/996] 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 67a9cd41dd..3ec12089ad 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 c7913cb71d..1b2b3318bd 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 63dc889e3c..833e0fbdb9 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -60,7 +60,7 @@ - + @@ -83,7 +83,7 @@ - + From 2ef791069c67911d548c0da48886f32bd95b7634 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 7 Jan 2022 17:33:38 +0900 Subject: [PATCH 122/996] Fix typon on `AdvancedStats` --- osu.Game/Screens/Select/Details/AdvancedStats.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index 354ee325d0..adaaa6425c 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -154,12 +154,12 @@ namespace osu.Game.Screens.Select.Details Task.WhenAll(normalStarDifficultyTask, moddedStarDifficultyTask).ContinueWith(_ => Schedule(() => { var normalDifficulty = normalStarDifficultyTask.GetResultSafely(); - var moddeDifficulty = moddedStarDifficultyTask.GetResultSafely(); + var moddedDifficulty = moddedStarDifficultyTask.GetResultSafely(); - if (normalDifficulty == null || moddeDifficulty == null) + if (normalDifficulty == null || moddedDifficulty == null) return; - starDifficulty.Value = ((float)normalDifficulty.Value.Stars, (float)moddeDifficulty.Value.Stars); + starDifficulty.Value = ((float)normalDifficulty.Value.Stars, (float)moddedDifficulty.Value.Stars); }), starDifficultyCancellationSource.Token, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Current); } From 0698ef633038df18077b24da424fd30ed44f795b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 7 Jan 2022 17:36:29 +0900 Subject: [PATCH 123/996] Fix one missed rename --- osu.Game/Screens/Ranking/ScorePanelList.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index 5ae6b9d417..f8e9d08350 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -154,7 +154,7 @@ namespace osu.Game.Screens.Ranking scoreManager.GetTotalScoreAsync(score) .ContinueWith(task => Schedule(() => { - flow.SetLayoutPosition(trackingContainer, task.GetCompletedResult()); + flow.SetLayoutPosition(trackingContainer, task.GetResultSafely()); trackingContainer.Show(); From 12c3e56881d9dbc38ecec16c877397a8a7ea6a27 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 7 Jan 2022 19:01:05 +0900 Subject: [PATCH 124/996] Fix `IPCLocationTest` not waiting for load of component As seen at https://github.com/ppy/osu/runs/4731480384?check_suite_focus=true. --- osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs b/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs index 952eb72bf4..03252e3be6 100644 --- a/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs @@ -36,7 +36,7 @@ namespace osu.Game.Tournament.Tests.NonVisual TournamentStorage storage = (TournamentStorage)osu.Dependencies.Get(); FileBasedIPC ipc = null; - WaitForOrAssert(() => (ipc = osu.Dependencies.Get() as FileBasedIPC) != null, @"ipc could not be populated in a reasonable amount of time"); + WaitForOrAssert(() => (ipc = osu.Dependencies.Get() as FileBasedIPC)?.IsLoaded == true, @"ipc could not be populated in a reasonable amount of time"); Assert.True(ipc.SetIPCLocation(testStableInstallDirectory)); Assert.True(storage.AllTournaments.Exists("stable.json")); From 814b318a10fc584ae24bd3a4478724a3347502c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 7 Jan 2022 14:00:22 +0100 Subject: [PATCH 125/996] Add test coverage of slider end snapping behaviour --- .../Editor/TestSceneSliderSnapping.cs | 183 ++++++++++++++++++ 1 file changed, 183 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSnapping.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSnapping.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSnapping.cs new file mode 100644 index 0000000000..4b4aedcff4 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSnapping.cs @@ -0,0 +1,183 @@ +// 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.Input.Events; +using osu.Framework.Testing; +using osu.Framework.Utils; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Input.Bindings; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Edit; +using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Screens.Edit; +using osu.Game.Screens.Edit.Compose.Components; +using osu.Game.Tests.Beatmaps; +using osu.Game.Tests.Visual; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Rulesets.Osu.Tests.Editor +{ + public class TestSceneSliderSnapping : EditorTestScene + { + private const double beat_length = 1000; + + protected override Ruleset CreateEditorRuleset() => new OsuRuleset(); + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) + { + var controlPointInfo = new ControlPointInfo(); + controlPointInfo.Add(0, new TimingControlPoint { BeatLength = beat_length }); + return new TestBeatmap(ruleset, false) + { + ControlPointInfo = controlPointInfo + }; + } + + private Slider slider; + + public override void SetUpSteps() + { + base.SetUpSteps(); + + AddStep("add unsnapped slider", () => EditorBeatmap.Add(slider = new Slider + { + StartTime = 0, + Position = OsuPlayfield.BASE_SIZE / 5, + Path = new SliderPath + { + ControlPoints = + { + new PathControlPoint(Vector2.Zero), + new PathControlPoint(OsuPlayfield.BASE_SIZE * 3 / 5) + } + } + })); + AddStep("set beat divisor to 1/1", () => + { + var beatDivisor = (BindableBeatDivisor)Editor.Dependencies.Get(typeof(BindableBeatDivisor)); + beatDivisor.Value = 1; + }); + } + + [Test] + public void TestMovingUnsnappedSliderNodesSnaps() + { + PathControlPointPiece sliderEnd = null; + + assertSliderSnapped(false); + + AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider)); + AddStep("select slider end", () => + { + sliderEnd = this.ChildrenOfType().Single(piece => piece.ControlPoint.Position != Vector2.Zero); + InputManager.MoveMouseTo(sliderEnd.ScreenSpaceDrawQuad.Centre); + }); + AddStep("move slider end", () => + { + InputManager.PressButton(MouseButton.Left); + InputManager.MoveMouseTo(sliderEnd.ScreenSpaceDrawQuad.Centre - new Vector2(0, 20)); + InputManager.ReleaseButton(MouseButton.Left); + }); + assertSliderSnapped(true); + } + + [Test] + public void TestResizingUnsnappedSliderSnaps() + { + SelectionBoxScaleHandle handle = null; + + assertSliderSnapped(false); + + AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider)); + AddStep("move mouse to scale handle", () => + { + handle = this.ChildrenOfType().First(); + InputManager.MoveMouseTo(handle.ScreenSpaceDrawQuad.Centre); + }); + AddStep("scale slider", () => + { + InputManager.PressButton(MouseButton.Left); + InputManager.MoveMouseTo(handle.ScreenSpaceDrawQuad.Centre + new Vector2(20, 20)); + InputManager.ReleaseButton(MouseButton.Left); + }); + assertSliderSnapped(true); + } + + [Test] + public void TestRotatingUnsnappedSliderDoesNotSnap() + { + SelectionBoxRotationHandle handle = null; + + assertSliderSnapped(false); + + AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider)); + AddStep("move mouse to rotate handle", () => + { + handle = this.ChildrenOfType().First(); + InputManager.MoveMouseTo(handle.ScreenSpaceDrawQuad.Centre); + }); + AddStep("scale slider", () => + { + InputManager.PressButton(MouseButton.Left); + InputManager.MoveMouseTo(handle.ScreenSpaceDrawQuad.Centre + new Vector2(0, 20)); + InputManager.ReleaseButton(MouseButton.Left); + }); + assertSliderSnapped(false); + } + + [Test] + public void TestFlippingSliderDoesNotSnap() + { + OsuSelectionHandler selectionHandler = null; + + assertSliderSnapped(false); + + AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider)); + AddStep("flip slider horizontally", () => + { + selectionHandler = this.ChildrenOfType().Single(); + selectionHandler.OnPressed(new KeyBindingPressEvent(InputManager.CurrentState, GlobalAction.EditorFlipHorizontally)); + }); + + assertSliderSnapped(false); + + AddStep("flip slider vertically", () => + { + selectionHandler = this.ChildrenOfType().Single(); + selectionHandler.OnPressed(new KeyBindingPressEvent(InputManager.CurrentState, GlobalAction.EditorFlipVertically)); + }); + + assertSliderSnapped(false); + } + + [Test] + public void TestReversingSliderDoesNotSnap() + { + assertSliderSnapped(false); + + AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider)); + AddStep("reverse slider", () => + { + InputManager.PressKey(Key.ControlLeft); + InputManager.Key(Key.G); + InputManager.ReleaseKey(Key.ControlLeft); + }); + + assertSliderSnapped(false); + } + + private void assertSliderSnapped(bool snapped) + => AddAssert($"slider is {(snapped ? "" : "not ")}snapped", () => + { + double durationInBeatLengths = slider.Duration / beat_length; + double fractionalPart = durationInBeatLengths - (int)durationInBeatLengths; + return Precision.AlmostEquals(fractionalPart, 0) == snapped; + }); + } +} From d2f44813dd9aed366931123b3fca31a25673decb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 7 Jan 2022 14:46:36 +0100 Subject: [PATCH 126/996] Add test coverage for slider snapping when adding/removing control points --- .../Editor/TestSceneSliderSnapping.cs | 44 ++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSnapping.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSnapping.cs index 4b4aedcff4..b43b2b1461 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSnapping.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSnapping.cs @@ -54,6 +54,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor ControlPoints = { new PathControlPoint(Vector2.Zero), + new PathControlPoint(OsuPlayfield.BASE_SIZE * 2 / 5), new PathControlPoint(OsuPlayfield.BASE_SIZE * 3 / 5) } } @@ -75,7 +76,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider)); AddStep("select slider end", () => { - sliderEnd = this.ChildrenOfType().Single(piece => piece.ControlPoint.Position != Vector2.Zero); + sliderEnd = this.ChildrenOfType().Single(piece => piece.ControlPoint == slider.Path.ControlPoints.Last()); InputManager.MoveMouseTo(sliderEnd.ScreenSpaceDrawQuad.Centre); }); AddStep("move slider end", () => @@ -87,6 +88,47 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertSliderSnapped(true); } + [Test] + public void TestAddingControlPointToUnsnappedSliderNodesSnaps() + { + assertSliderSnapped(false); + + AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider)); + AddStep("move mouse to new point location", () => + { + var firstPiece = this.ChildrenOfType().Single(piece => piece.ControlPoint == slider.Path.ControlPoints[0]); + var secondPiece = this.ChildrenOfType().Single(piece => piece.ControlPoint == slider.Path.ControlPoints[1]); + InputManager.MoveMouseTo((firstPiece.ScreenSpaceDrawQuad.Centre + secondPiece.ScreenSpaceDrawQuad.Centre) / 2); + }); + AddStep("move slider end", () => + { + InputManager.PressKey(Key.ControlLeft); + InputManager.Click(MouseButton.Left); + InputManager.ReleaseKey(Key.ControlLeft); + }); + assertSliderSnapped(true); + } + + [Test] + public void TestRemovingControlPointFromUnsnappedSliderNodesSnaps() + { + assertSliderSnapped(false); + + AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider)); + AddStep("move mouse to second control point", () => + { + var secondPiece = this.ChildrenOfType().Single(piece => piece.ControlPoint == slider.Path.ControlPoints[1]); + InputManager.MoveMouseTo(secondPiece); + }); + AddStep("quick delete", () => + { + InputManager.PressKey(Key.ShiftLeft); + InputManager.PressButton(MouseButton.Right); + InputManager.ReleaseKey(Key.ShiftLeft); + }); + assertSliderSnapped(true); + } + [Test] public void TestResizingUnsnappedSliderSnaps() { From c09f6ee0522fa038d58810d010d02d8570e9f6bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 7 Jan 2022 15:11:38 +0100 Subject: [PATCH 127/996] Use slider snapping more liberally to match user expectations Previously the slider path length would be snapped using the current beat snap setting on *every* change of the slider path. As it turns out this is unexpected behaviour in some situations (e.g. when reversing a path, which is expected to preserve the previous duration, even though the slider may be technically "unsnapped" at that point in time due to a different beat snap setting being selected afterwards). --- .../Components/PathControlPointVisualiser.cs | 5 +++++ .../Sliders/SliderSelectionBlueprint.cs | 15 +++++++-------- .../Edit/OsuSelectionHandler.cs | 16 +++++++++++++++- .../Rulesets/Objects/SliderPathExtensions.cs | 10 ++++++++++ 4 files changed, 37 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 065d4737a5..ae4141073e 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -283,6 +283,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components } } + // Snap the path to the current beat divisor before checking length validity. + slider.SnapTo(snapProvider); + if (!slider.Path.HasValidLength) { for (int i = 0; i < slider.Path.ControlPoints.Count; i++) @@ -290,6 +293,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components slider.Position = oldPosition; slider.StartTime = oldStartTime; + // Snap the path length again to undo the invalid length. + slider.SnapTo(snapProvider); return; } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 2aebe05c2f..6cf2a493a9 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -80,7 +80,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders controlPoints.BindTo(HitObject.Path.ControlPoints); pathVersion.BindTo(HitObject.Path.Version); - pathVersion.BindValueChanged(_ => updatePath()); + pathVersion.BindValueChanged(_ => editorBeatmap?.Update(HitObject)); BodyPiece.UpdateFrom(HitObject); } @@ -208,6 +208,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders // Move the control points from the insertion index onwards to make room for the insertion controlPoints.Insert(insertionIndex, pathControlPoint); + HitObject.SnapTo(composer); + return pathControlPoint; } @@ -227,7 +229,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders controlPoints.Remove(c); } - // If there are 0 or 1 remaining control points, the slider is in a degenerate (single point) form and should be deleted + // Snap the slider to the current beat divisor before checking length validity. + HitObject.SnapTo(composer); + + // If there are 0 or 1 remaining control points, or the slider has an invalid length, it is in a degenerate form and should be deleted if (controlPoints.Count <= 1 || !HitObject.Path.HasValidLength) { placementHandler?.Delete(HitObject); @@ -242,12 +247,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders HitObject.Position += first; } - private void updatePath() - { - HitObject.Path.ExpectedDistance.Value = composer?.GetSnappedDistanceFromDistance(HitObject, (float)HitObject.Path.CalculatedDistance) ?? (float)HitObject.Path.CalculatedDistance; - editorBeatmap?.Update(HitObject); - } - private void convertToStream() { if (editorBeatmap == null || changeHandler == null || beatDivisor == null) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 071ecf6329..efbac5439c 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -1,12 +1,16 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +#nullable enable + using System.Collections.Generic; using System.Linq; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; using osu.Framework.Utils; using osu.Game.Extensions; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; @@ -18,6 +22,9 @@ namespace osu.Game.Rulesets.Osu.Edit { public class OsuSelectionHandler : EditorSelectionHandler { + [Resolved(CanBeNull = true)] + private IPositionSnapProvider? positionSnapProvider { get; set; } + /// /// During a transform, the initial origin is stored so it can be used throughout the operation. /// @@ -27,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Edit /// During a transform, the initial path types of a single selected slider are stored so they /// can be maintained throughout the operation. /// - private List referencePathTypes; + private List? referencePathTypes; protected override void OnSelectionChanged() { @@ -197,6 +204,10 @@ namespace osu.Game.Rulesets.Osu.Edit for (int i = 0; i < slider.Path.ControlPoints.Count; ++i) slider.Path.ControlPoints[i].Type = referencePathTypes[i]; + // Snap the slider's length to the current beat divisor + // to calculate the final resulting duration / bounding box before the final checks. + slider.SnapTo(positionSnapProvider); + //if sliderhead or sliderend end up outside playfield, revert scaling. Quad scaledQuad = getSurroundingQuad(new OsuHitObject[] { slider }); (bool xInBounds, bool yInBounds) = isQuadInBounds(scaledQuad); @@ -206,6 +217,9 @@ namespace osu.Game.Rulesets.Osu.Edit foreach (var point in slider.Path.ControlPoints) point.Position = oldControlPoints.Dequeue(); + + // Snap the slider's length again to undo the potentially-invalid length applied by the previous snap. + slider.SnapTo(positionSnapProvider); } private void scaleHitObjects(OsuHitObject[] hitObjects, Anchor reference, Vector2 scale) diff --git a/osu.Game/Rulesets/Objects/SliderPathExtensions.cs b/osu.Game/Rulesets/Objects/SliderPathExtensions.cs index 1308fff7ae..ba614900c0 100644 --- a/osu.Game/Rulesets/Objects/SliderPathExtensions.cs +++ b/osu.Game/Rulesets/Objects/SliderPathExtensions.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Linq; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects.Types; using osuTK; @@ -11,6 +12,15 @@ namespace osu.Game.Rulesets.Objects { public static class SliderPathExtensions { + /// + /// Snaps the provided 's duration using the . + /// + public static void SnapTo(this THitObject hitObject, IPositionSnapProvider? snapProvider) + where THitObject : HitObject, IHasPath + { + hitObject.Path.ExpectedDistance.Value = snapProvider?.GetSnappedDistanceFromDistance(hitObject, (float)hitObject.Path.CalculatedDistance) ?? hitObject.Path.CalculatedDistance; + } + /// /// Reverse the direction of this path. /// From 881fa2b86b17aa87119ce58d20fbc27011217c52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 6 Jan 2022 14:49:56 +0100 Subject: [PATCH 128/996] Add basic test scene for scoreboard time --- .../Visual/Online/TestSceneScoreboardTime.cs | 57 +++++++++++++++++++ .../BeatmapSet/Scores/ScoreboardTime.cs | 21 +++++++ 2 files changed, 78 insertions(+) create mode 100644 osu.Game.Tests/Visual/Online/TestSceneScoreboardTime.cs create mode 100644 osu.Game/Overlays/BeatmapSet/Scores/ScoreboardTime.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneScoreboardTime.cs b/osu.Game.Tests/Visual/Online/TestSceneScoreboardTime.cs new file mode 100644 index 0000000000..7e33b5240c --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneScoreboardTime.cs @@ -0,0 +1,57 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Timing; +using osu.Game.Overlays.BeatmapSet.Scores; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneScoreboardTime : OsuTestScene + { + private StopwatchClock stopwatch; + + [Test] + public void TestVariousUnits() + { + AddStep("create various scoreboard times", () => Child = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Clock = new FramedClock(stopwatch = new StopwatchClock()), // prevent time from naturally elapsing. + Direction = FillDirection.Vertical, + ChildrenEnumerable = testCases.Select(dateTime => new ScoreboardTime(dateTime, 24).With(time => time.Anchor = time.Origin = Anchor.TopCentre)) + }); + + AddStep("start stopwatch", () => stopwatch.Start()); + } + + private static IEnumerable testCases => new[] + { + DateTimeOffset.Now, + DateTimeOffset.Now.AddSeconds(-1), + DateTimeOffset.Now.AddSeconds(-25), + DateTimeOffset.Now.AddSeconds(-59), + DateTimeOffset.Now.AddMinutes(-1), + DateTimeOffset.Now.AddMinutes(-25), + DateTimeOffset.Now.AddMinutes(-59), + DateTimeOffset.Now.AddHours(-1), + DateTimeOffset.Now.AddHours(-13), + DateTimeOffset.Now.AddHours(-23), + DateTimeOffset.Now.AddDays(-1), + DateTimeOffset.Now.AddDays(-6), + DateTimeOffset.Now.AddDays(-16), + DateTimeOffset.Now.AddMonths(-1), + DateTimeOffset.Now.AddMonths(-11), + DateTimeOffset.Now.AddYears(-1), + DateTimeOffset.Now.AddYears(-5) + }; + } +} diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreboardTime.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreboardTime.cs new file mode 100644 index 0000000000..db421ec6b7 --- /dev/null +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreboardTime.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; +using osu.Game.Graphics; + +namespace osu.Game.Overlays.BeatmapSet.Scores +{ + public class ScoreboardTime : DrawableDate + { + public ScoreboardTime(DateTimeOffset date, float textSize = OsuFont.DEFAULT_FONT_SIZE, bool italic = true) + : base(date, textSize, italic) + { + } + + protected override string Format() + { + return base.Format(); + } + } +} From 87f7c7e6915757752a6b978cbf5ca859bc03368f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 6 Jan 2022 16:00:45 +0100 Subject: [PATCH 129/996] Implement scoreboard-specific time formatting --- .../BeatmapSet/Scores/ScoreboardTime.cs | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreboardTime.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreboardTime.cs index db421ec6b7..ff1d3490b4 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreboardTime.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreboardTime.cs @@ -2,7 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using System; +using Humanizer; using osu.Game.Graphics; +using osu.Game.Resources.Localisation.Web; namespace osu.Game.Overlays.BeatmapSet.Scores { @@ -15,7 +17,40 @@ namespace osu.Game.Overlays.BeatmapSet.Scores protected override string Format() { - return base.Format(); + var now = DateTime.Now; + var difference = now - Date; + + // web uses momentjs's custom locales to format the date for the purposes of the scoreboard. + // this is intended to be a best-effort, more legible approximation of that. + // compare: + // * https://github.com/ppy/osu-web/blob/a8f5a68fb435cb19a4faa4c7c4bce08c4f096933/resources/assets/lib/scoreboard-time.tsx + // * https://momentjs.com/docs/#/customization/ (reference for the customisation format) + + // TODO: support localisation (probably via `CommonStrings.CountHours()` etc.) + // requires pluralisable string support framework-side + + if (difference.TotalHours < 1) + return CommonStrings.TimeNow.ToString(); + if (difference.TotalDays < 1) + return "hr".ToQuantity((int)difference.TotalHours); + + // this is where this gets more complicated because of how the calendar works. + // since there's no `TotalMonths` / `TotalYears`, we have to iteratively add months/years + // and test against cutoff dates to determine how many months/years to show. + + if (Date > now.AddMonths(-1)) + return difference.TotalDays < 2 ? "1dy" : $"{(int)difference.TotalDays}dys"; + + for (int months = 1; months <= 11; ++months) + { + if (Date > now.AddMonths(-(months + 1))) + return months == 1 ? "1mo" : $"{months}mos"; + } + + int years = 1; + while (Date <= now.AddYears(-(years + 1))) + years += 1; + return years == 1 ? "1yr" : $"{years}yrs"; } } } From 9e84e31eacd3008ae518fee266de58007eb46357 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 6 Jan 2022 16:14:26 +0100 Subject: [PATCH 130/996] Add score time to beatmap set overlay scoreboard table --- osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index 018faf2011..2c78fa264e 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -128,6 +128,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores if (showPerformancePoints) columns.Add(new TableColumn(BeatmapsetsStrings.ShowScoreboardHeaderspp, Anchor.CentreLeft, new Dimension(GridSizeMode.Absolute, 30))); + columns.Add(new TableColumn(BeatmapsetsStrings.ShowScoreboardHeadersTime, Anchor.CentreLeft, new Dimension(GridSizeMode.AutoSize))); columns.Add(new TableColumn(BeatmapsetsStrings.ShowScoreboardHeadersMods, Anchor.CentreLeft, new Dimension(GridSizeMode.AutoSize))); return columns.ToArray(); @@ -202,6 +203,11 @@ namespace osu.Game.Overlays.BeatmapSet.Scores }); } + content.Add(new ScoreboardTime(score.Date, text_size) + { + Margin = new MarginPadding { Right = 10 } + }); + content.Add(new FillFlowContainer { Direction = FillDirection.Horizontal, From f6f24220c2a0b9799d4eac60400ac3659ac400c5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 8 Jan 2022 15:28:11 +0900 Subject: [PATCH 131/996] Fix `LegacyScoreDecoderTest` incorrectly comparing unset beatmap IDs This has been wrong from the outside, but hidden by the fact that the default values are equal. I've changed to MD5Hash which actually asserts that the correct beatmap has likely arrived. Found this in my realm changes, where it fails due to the beatmap ID being a differing Guid in each case. --- osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs index 81d89359e0..b886a61c0f 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs @@ -95,7 +95,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.That(decodedAfterEncode, Is.Not.Null); Assert.That(decodedAfterEncode.ScoreInfo.User.Username, Is.EqualTo(scoreInfo.User.Username)); - Assert.That(decodedAfterEncode.ScoreInfo.BeatmapInfoID, Is.EqualTo(scoreInfo.BeatmapInfoID)); + Assert.That(decodedAfterEncode.ScoreInfo.BeatmapInfo.MD5Hash, Is.EqualTo(scoreInfo.BeatmapInfo.MD5Hash)); Assert.That(decodedAfterEncode.ScoreInfo.Ruleset, Is.EqualTo(scoreInfo.Ruleset)); Assert.That(decodedAfterEncode.ScoreInfo.TotalScore, Is.EqualTo(scoreInfo.TotalScore)); Assert.That(decodedAfterEncode.ScoreInfo.MaxCombo, Is.EqualTo(scoreInfo.MaxCombo)); From f026973b19316c828696f0e34a4cd8230215b741 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 8 Jan 2022 14:16:05 +0100 Subject: [PATCH 132/996] Add failing test for ruleset incorrectly applying from latest picked item --- .../TestSceneAllPlayersQueueMode.cs | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs index a5744f9986..a06bf44d88 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs @@ -1,13 +1,21 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +#nullable enable + using System; using System.Linq; using NUnit.Framework; +using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Online.Multiplayer; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Catch; +using osu.Game.Rulesets.Osu; using osu.Game.Screens.OnlinePlay.Multiplayer; +using osu.Game.Screens.OnlinePlay.Multiplayer.Match; +using osu.Game.Screens.Play; using osuTK.Input; namespace osu.Game.Tests.Visual.Multiplayer @@ -83,7 +91,24 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("selected beatmap is initial beatmap", () => Beatmap.Value.BeatmapInfo.OnlineID == InitialBeatmap.OnlineID); } - private void addItem(Func beatmap) + [Test] + public void TestCorrectRulesetSelectedAfterNewItemAdded() + { + addItem(() => OtherBeatmap, new CatchRuleset().RulesetInfo); + AddUntilStep("selected beatmap is initial beatmap", () => Beatmap.Value.BeatmapInfo.OnlineID == InitialBeatmap.OnlineID); + + AddUntilStep("wait for idle", () => Client.LocalUser?.State == MultiplayerUserState.Idle); + ClickButtonWhenEnabled(); + + AddUntilStep("wait for ready", () => Client.LocalUser?.State == MultiplayerUserState.Ready); + ClickButtonWhenEnabled(); + + AddUntilStep("wait for player", () => CurrentScreen is Player player && player.IsLoaded); + AddAssert("ruleset is correct", () => ((Player)CurrentScreen).Ruleset.Value.Equals(new OsuRuleset().RulesetInfo)); + AddStep("exit player", () => CurrentScreen.Exit()); + } + + private void addItem(Func beatmap, RulesetInfo? ruleset = null) { AddStep("click add button", () => { @@ -92,6 +117,12 @@ namespace osu.Game.Tests.Visual.Multiplayer }); AddUntilStep("wait for song select", () => CurrentSubScreen is Screens.Select.SongSelect select && select.BeatmapSetsLoaded); + + if (ruleset != null) + { + AddStep($"set {ruleset.Name} ruleset", () => ((Screens.Select.SongSelect)CurrentSubScreen).Ruleset.Value = ruleset); + } + AddStep("select other beatmap", () => ((Screens.Select.SongSelect)CurrentSubScreen).FinaliseSelection(beatmap())); AddUntilStep("wait for return to match", () => CurrentSubScreen is MultiplayerMatchSubScreen); } From 50077f05bdb07468a93a0ba844b1ea00ec1823d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 8 Jan 2022 14:39:22 +0100 Subject: [PATCH 133/996] Add test coverage for correct revert of ruleset when play starts at song select --- .../Multiplayer/TestSceneMultiplayer.cs | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 4eebda94e9..1221a9f080 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -22,6 +22,7 @@ using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; using osu.Game.Overlays.Mods; using osu.Game.Rulesets; +using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Screens.OnlinePlay.Components; @@ -438,6 +439,45 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("Beatmap matches current item", () => Beatmap.Value.BeatmapInfo.OnlineID == client.Room?.Playlist.First().BeatmapID); } + [Test] + public void TestPlayStartsWithCorrectRulesetWhileAtSongSelect() + { + createRoom(() => new Room + { + Name = { Value = "Test Room" }, + Playlist = + { + new PlaylistItem + { + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Ruleset = { Value = new OsuRuleset().RulesetInfo }, + } + } + }); + + pressReadyButton(); + + AddStep("Enter song select", () => + { + var currentSubScreen = ((Screens.OnlinePlay.Multiplayer.Multiplayer)multiplayerComponents.CurrentScreen).CurrentSubScreen; + ((MultiplayerMatchSubScreen)currentSubScreen).OpenSongSelection(client.Room?.Settings.PlaylistItemId); + }); + + AddUntilStep("wait for song select", () => this.ChildrenOfType().FirstOrDefault()?.BeatmapSetsLoaded == true); + + AddAssert("Ruleset matches current item", () => Ruleset.Value.OnlineID == client.Room?.Playlist.First().RulesetID); + + AddStep("Switch ruleset", () => ((MultiplayerMatchSongSelect)multiplayerComponents.MultiplayerScreen.CurrentSubScreen).Ruleset.Value = new CatchRuleset().RulesetInfo); + + AddUntilStep("Ruleset doesn't match current item", () => Ruleset.Value.OnlineID != client.Room?.Playlist.First().RulesetID); + + AddStep("start match externally", () => client.StartMatch()); + + AddUntilStep("play started", () => multiplayerComponents.CurrentScreen is Player); + + AddAssert("Ruleset matches current item", () => Ruleset.Value.OnlineID == client.Room?.Playlist.First().RulesetID); + } + [Test] public void TestLocalPlayDoesNotStartWhileSpectatingWithNoBeatmap() { From 3dd5705a810d8f1f68e3bf8c9accb5cda865d6f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 8 Jan 2022 14:46:41 +0100 Subject: [PATCH 134/996] Add test coverage for correct revert of mods after new item is queued --- .../TestSceneAllPlayersQueueMode.cs | 37 ++++++++++++++++--- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs index a06bf44d88..ad60ac824d 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs @@ -4,15 +4,19 @@ #nullable enable using System; +using System.Collections.Generic; using System.Linq; using NUnit.Framework; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Online.Multiplayer; using osu.Game.Rulesets; using osu.Game.Rulesets.Catch; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Mods; using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.OnlinePlay.Multiplayer.Match; using osu.Game.Screens.Play; @@ -108,22 +112,43 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("exit player", () => CurrentScreen.Exit()); } - private void addItem(Func beatmap, RulesetInfo? ruleset = null) + [Test] + public void TestCorrectModsSelectedAfterNewItemAdded() { + addItem(() => OtherBeatmap, mods: new Mod[] { new OsuModDoubleTime() }); + AddUntilStep("selected beatmap is initial beatmap", () => Beatmap.Value.BeatmapInfo.OnlineID == InitialBeatmap.OnlineID); + + AddUntilStep("wait for idle", () => Client.LocalUser?.State == MultiplayerUserState.Idle); + ClickButtonWhenEnabled(); + + AddUntilStep("wait for ready", () => Client.LocalUser?.State == MultiplayerUserState.Ready); + ClickButtonWhenEnabled(); + + AddUntilStep("wait for player", () => CurrentScreen is Player player && player.IsLoaded); + AddAssert("mods are correct", () => !((Player)CurrentScreen).Mods.Value.Any()); + AddStep("exit player", () => CurrentScreen.Exit()); + } + + private void addItem(Func beatmap, RulesetInfo? ruleset = null, IReadOnlyList? mods = null) + { + Screens.Select.SongSelect? songSelect = null; + AddStep("click add button", () => { InputManager.MoveMouseTo(this.ChildrenOfType().Single()); InputManager.Click(MouseButton.Left); }); - AddUntilStep("wait for song select", () => CurrentSubScreen is Screens.Select.SongSelect select && select.BeatmapSetsLoaded); + AddUntilStep("wait for song select", () => (songSelect = CurrentSubScreen as Screens.Select.SongSelect) != null); + AddUntilStep("wait for loaded", () => songSelect.AsNonNull().BeatmapSetsLoaded); if (ruleset != null) - { - AddStep($"set {ruleset.Name} ruleset", () => ((Screens.Select.SongSelect)CurrentSubScreen).Ruleset.Value = ruleset); - } + AddStep($"set {ruleset.Name} ruleset", () => songSelect.AsNonNull().Ruleset.Value = ruleset); - AddStep("select other beatmap", () => ((Screens.Select.SongSelect)CurrentSubScreen).FinaliseSelection(beatmap())); + if (mods != null) + AddStep($"set mods to {string.Join(",", mods.Select(m => m.Acronym))}", () => songSelect.AsNonNull().Mods.Value = mods); + + AddStep("select other beatmap", () => songSelect.AsNonNull().FinaliseSelection(beatmap())); AddUntilStep("wait for return to match", () => CurrentSubScreen is MultiplayerMatchSubScreen); } } From 446962446ea3e8970a2da3712d1ceca493396d6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 8 Jan 2022 14:50:49 +0100 Subject: [PATCH 135/996] Add test coverage for correct revert of mods when play starts at song select --- .../Multiplayer/TestSceneMultiplayer.cs | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 1221a9f080..43f9b6636a 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -7,6 +7,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input; @@ -23,6 +24,7 @@ using osu.Game.Online.Rooms; using osu.Game.Overlays.Mods; using osu.Game.Rulesets; using osu.Game.Rulesets.Catch; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Screens.OnlinePlay.Components; @@ -478,6 +480,45 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("Ruleset matches current item", () => Ruleset.Value.OnlineID == client.Room?.Playlist.First().RulesetID); } + [Test] + public void TestPlayStartsWithCorrectModsWhileAtSongSelect() + { + createRoom(() => new Room + { + Name = { Value = "Test Room" }, + Playlist = + { + new PlaylistItem + { + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Ruleset = { Value = new OsuRuleset().RulesetInfo }, + } + } + }); + + pressReadyButton(); + + AddStep("Enter song select", () => + { + var currentSubScreen = ((Screens.OnlinePlay.Multiplayer.Multiplayer)multiplayerComponents.CurrentScreen).CurrentSubScreen; + ((MultiplayerMatchSubScreen)currentSubScreen).OpenSongSelection(client.Room?.Settings.PlaylistItemId); + }); + + AddUntilStep("wait for song select", () => this.ChildrenOfType().FirstOrDefault()?.BeatmapSetsLoaded == true); + + AddAssert("Mods match current item", () => SelectedMods.Value.Select(m => m.Acronym).SequenceEqual(client.Room.AsNonNull().Playlist.First().RequiredMods.Select(m => m.Acronym))); + + AddStep("Switch required mods", () => ((MultiplayerMatchSongSelect)multiplayerComponents.MultiplayerScreen.CurrentSubScreen).Mods.Value = new Mod[] { new OsuModDoubleTime() }); + + AddAssert("Mods don't match current item", () => !SelectedMods.Value.Select(m => m.Acronym).SequenceEqual(client.Room.AsNonNull().Playlist.First().RequiredMods.Select(m => m.Acronym))); + + AddStep("start match externally", () => client.StartMatch()); + + AddUntilStep("play started", () => multiplayerComponents.CurrentScreen is Player); + + AddAssert("Mods match current item", () => SelectedMods.Value.Select(m => m.Acronym).SequenceEqual(client.Room.AsNonNull().Playlist.First().RequiredMods.Select(m => m.Acronym))); + } + [Test] public void TestLocalPlayDoesNotStartWhileSpectatingWithNoBeatmap() { From c5ac996e3f73ee45ee07a21e1de04fe550c5f966 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 8 Jan 2022 14:55:40 +0100 Subject: [PATCH 136/996] Restore ruleset using current playlist item on resuming room sub screen Ensures that the ruleset selected at the multiplayer song selection screen does not overwrite the current playlist item's ruleset. --- osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index a0e7e8de87..c31239616c 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -300,6 +300,7 @@ namespace osu.Game.Screens.OnlinePlay.Match updateWorkingBeatmap(); beginHandlingTrack(); Scheduler.AddOnce(UpdateMods); + Scheduler.AddOnce(updateRuleset); } public override bool OnExiting(IScreen next) @@ -353,8 +354,7 @@ namespace osu.Game.Screens.OnlinePlay.Match .ToList(); UpdateMods(); - - Ruleset.Value = rulesets.GetRuleset(selected.RulesetID); + updateRuleset(); if (!selected.AllowedMods.Any()) { @@ -387,6 +387,14 @@ namespace osu.Game.Screens.OnlinePlay.Match Mods.Value = UserMods.Value.Concat(SelectedItem.Value.RequiredMods).ToList(); } + private void updateRuleset() + { + if (SelectedItem.Value == null || !this.IsCurrentScreen()) + return; + + Ruleset.Value = rulesets.GetRuleset(SelectedItem.Value.RulesetID); + } + private void beginHandlingTrack() { Beatmap.BindValueChanged(applyLoopingToTrack, true); From 9370e8446047af81f1fa1ae3cec83456462d923d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 8 Jan 2022 16:12:52 +0100 Subject: [PATCH 137/996] Fix effect point multiplier text box displaying too much decimal digits --- osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs b/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs index 8738d36bf4..67f1dacec4 100644 --- a/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs +++ b/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Globalization; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -9,6 +10,7 @@ using osu.Framework.Graphics.UserInterface; using osu.Framework.Localisation; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Overlays.Settings; +using osu.Game.Utils; namespace osu.Game.Screens.Edit.Timing { @@ -66,7 +68,8 @@ namespace osu.Game.Screens.Edit.Timing Current.BindValueChanged(val => { - textBox.Text = val.NewValue.ToString(); + decimal decimalValue = slider.Current.Value.ToDecimal(NumberFormatInfo.InvariantInfo); + textBox.Text = decimalValue.ToString($@"N{FormatUtils.FindPrecision(decimalValue)}"); }, true); } From 24d377fddb18f60bb72c3da0aca6ae449ffccb7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 8 Jan 2022 17:19:52 +0100 Subject: [PATCH 138/996] Move implementation of drag handle operations to concrete classes --- .../Edit/Compose/Components/SelectionBox.cs | 5 +++-- .../Components/SelectionBoxDragHandle.cs | 8 ------- .../Components/SelectionBoxRotationHandle.cs | 22 +++++++++++++++++++ .../Components/SelectionBoxScaleHandle.cs | 11 ++++++++++ 4 files changed, 36 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs index 3a31f6ea8c..cda986c7cd 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs @@ -15,6 +15,7 @@ using osuTK.Input; namespace osu.Game.Screens.Edit.Compose.Components { + [Cached] public class SelectionBox : CompositeDrawable { public const float BORDER_RADIUS = 3; @@ -306,7 +307,7 @@ namespace osu.Game.Screens.Edit.Compose.Components var handle = new SelectionBoxScaleHandle { Anchor = anchor, - HandleDrag = e => OnScale?.Invoke(e.Delta, anchor) + HandleScale = (delta, a) => OnScale?.Invoke(delta, a) }; handle.OperationStarted += operationStarted; @@ -319,7 +320,7 @@ namespace osu.Game.Screens.Edit.Compose.Components var handle = new SelectionBoxRotationHandle { Anchor = anchor, - HandleDrag = e => OnRotation?.Invoke(convertDragEventToAngleOfRotation(e)) + HandleRotate = angle => OnRotation?.Invoke(angle) }; handle.OperationStarted += operationStarted; diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandle.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandle.cs index cac907ca5e..c37fefeed4 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandle.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandle.cs @@ -8,20 +8,12 @@ namespace osu.Game.Screens.Edit.Compose.Components { public abstract class SelectionBoxDragHandle : SelectionBoxControl { - public Action HandleDrag { get; set; } - protected override bool OnDragStart(DragStartEvent e) { TriggerOperationStarted(); return true; } - protected override void OnDrag(DragEvent e) - { - HandleDrag?.Invoke(e); - base.OnDrag(e); - } - protected override void OnDragEnd(DragEndEvent e) { TriggerOperationEnded(); diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxRotationHandle.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxRotationHandle.cs index 65a54292ab..0db36d8902 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxRotationHandle.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxRotationHandle.cs @@ -1,10 +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; using osu.Framework.Allocation; using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Events; using osuTK; using osuTK.Graphics; @@ -12,8 +14,13 @@ namespace osu.Game.Screens.Edit.Compose.Components { public class SelectionBoxRotationHandle : SelectionBoxDragHandle { + public Action HandleRotate { get; set; } + private SpriteIcon icon; + [Resolved] + private SelectionBox selectionBox { get; set; } + [BackgroundDependencyLoader] private void load() { @@ -38,5 +45,20 @@ namespace osu.Game.Screens.Edit.Compose.Components base.UpdateHoverState(); icon.FadeColour(!IsHeld && IsHovered ? Color4.White : Color4.Black, TRANSFORM_DURATION, Easing.OutQuint); } + + protected override void OnDrag(DragEvent e) + { + base.OnDrag(e); + HandleRotate?.Invoke(convertDragEventToAngleOfRotation(e)); + } + + private float convertDragEventToAngleOfRotation(DragEvent e) + { + // Adjust coordinate system to the center of SelectionBox + float startAngle = MathF.Atan2(e.LastMousePosition.Y - selectionBox.DrawHeight / 2, e.LastMousePosition.X - selectionBox.DrawWidth / 2); + float endAngle = MathF.Atan2(e.MousePosition.Y - selectionBox.DrawHeight / 2, e.MousePosition.X - selectionBox.DrawWidth / 2); + + return (endAngle - startAngle) * 180 / MathF.PI; + } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs index a87c661f45..1f82f28380 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs @@ -1,17 +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 osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Input.Events; using osuTK; namespace osu.Game.Screens.Edit.Compose.Components { public class SelectionBoxScaleHandle : SelectionBoxDragHandle { + public Action HandleScale { get; set; } + [BackgroundDependencyLoader] private void load() { Size = new Vector2(10); } + + protected override void OnDrag(DragEvent e) + { + HandleScale?.Invoke(e.Delta, Anchor); + base.OnDrag(e); + } } } From d76c674abc569c383bc8adbc7c4d9986a2029de4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 8 Jan 2022 17:42:58 +0100 Subject: [PATCH 139/996] Add tooltip with relative rotation in degrees to rotation handles --- .../Components/SelectionBoxRotationHandle.cs | 46 ++++++++++++++++++- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxRotationHandle.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxRotationHandle.cs index 0db36d8902..22479bd9b3 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxRotationHandle.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxRotationHandle.cs @@ -3,21 +3,29 @@ using System; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Extensions.EnumExtensions; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osuTK; using osuTK.Graphics; namespace osu.Game.Screens.Edit.Compose.Components { - public class SelectionBoxRotationHandle : SelectionBoxDragHandle + public class SelectionBoxRotationHandle : SelectionBoxDragHandle, IHasTooltip { public Action HandleRotate { get; set; } + public LocalisableString TooltipText { get; private set; } + private SpriteIcon icon; + private readonly Bindable cumulativeRotation = new Bindable(); + [Resolved] private SelectionBox selectionBox { get; set; } @@ -40,16 +48,45 @@ namespace osu.Game.Screens.Edit.Compose.Components }); } + protected override void LoadComplete() + { + base.LoadComplete(); + cumulativeRotation.BindValueChanged(_ => updateTooltipText(), true); + } + protected override void UpdateHoverState() { base.UpdateHoverState(); icon.FadeColour(!IsHeld && IsHovered ? Color4.White : Color4.Black, TRANSFORM_DURATION, Easing.OutQuint); } + protected override bool OnDragStart(DragStartEvent e) + { + bool handle = base.OnDragStart(e); + if (handle) + cumulativeRotation.Value = 0; + return handle; + } + protected override void OnDrag(DragEvent e) { base.OnDrag(e); - HandleRotate?.Invoke(convertDragEventToAngleOfRotation(e)); + + float instantaneousAngle = convertDragEventToAngleOfRotation(e); + cumulativeRotation.Value += instantaneousAngle; + + if (cumulativeRotation.Value < -180) + cumulativeRotation.Value += 360; + else if (cumulativeRotation.Value > 180) + cumulativeRotation.Value -= 360; + + HandleRotate?.Invoke(instantaneousAngle); + } + + protected override void OnDragEnd(DragEndEvent e) + { + base.OnDragEnd(e); + cumulativeRotation.Value = null; } private float convertDragEventToAngleOfRotation(DragEvent e) @@ -60,5 +97,10 @@ namespace osu.Game.Screens.Edit.Compose.Components return (endAngle - startAngle) * 180 / MathF.PI; } + + private void updateTooltipText() + { + TooltipText = cumulativeRotation.Value?.ToLocalisableString("0.0°") ?? default(LocalisableString); + } } } From 82d6639a3b3e8c576e036f95b50367b7c97e8d2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 9 Jan 2022 14:31:37 +0100 Subject: [PATCH 140/996] Decouple rankings table test from online API --- .../Visual/Online/TestSceneRankingsTables.cs | 158 +++++++++++------- 1 file changed, 97 insertions(+), 61 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsTables.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsTables.cs index ee109189c7..35e219f839 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneRankingsTables.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsTables.cs @@ -1,38 +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 System.Collections.Generic; using osu.Framework.Graphics.Containers; using osu.Game.Overlays.Rankings.Tables; using osu.Framework.Graphics; -using osu.Game.Online.API.Requests; -using osu.Game.Rulesets; using System.Threading; -using osu.Game.Online.API; -using osu.Game.Rulesets.Osu; -using osu.Game.Rulesets.Mania; -using osu.Game.Rulesets.Taiko; -using osu.Game.Rulesets.Catch; using osu.Framework.Allocation; using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; -using osu.Game.Overlays.Rankings; +using osu.Game.Users; namespace osu.Game.Tests.Visual.Online { public class TestSceneRankingsTables : OsuTestScene { - protected override bool UseOnlineAPI => true; - - [Resolved] - private IAPIProvider api { get; set; } - [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green); private readonly BasicScrollContainer scrollFlow; private readonly LoadingLayer loading; private CancellationTokenSource cancellationToken; - private APIRequest request; public TestSceneRankingsTables() { @@ -53,73 +42,120 @@ namespace osu.Game.Tests.Visual.Online { base.LoadComplete(); - AddStep("Osu performance", () => createPerformanceTable(new OsuRuleset().RulesetInfo, null)); - AddStep("Mania scores", () => createScoreTable(new ManiaRuleset().RulesetInfo)); - AddStep("Taiko country scores", () => createCountryTable(new TaikoRuleset().RulesetInfo)); - AddStep("Catch US performance page 10", () => createPerformanceTable(new CatchRuleset().RulesetInfo, "US", 10)); - AddStep("Osu spotlight table (chart 271)", () => createSpotlightTable(new OsuRuleset().RulesetInfo, 271)); + AddStep("User performance", createPerformanceTable); + AddStep("User scores", createScoreTable); + AddStep("Country scores", createCountryTable); } - private void createCountryTable(RulesetInfo ruleset, int page = 1) + private void createCountryTable() { onLoadStarted(); - request = new GetCountryRankingsRequest(ruleset, page); - ((GetCountryRankingsRequest)request).Success += rankings => Schedule(() => + var countries = new List { - var table = new CountriesTable(page, rankings.Countries); - loadTable(table); - }); + new CountryStatistics + { + Country = new Country { FlagName = "US", FullName = "United States" }, + FlagName = "US", + ActiveUsers = 2_972_623, + PlayCount = 3_086_515_743, + RankedScore = 449_407_643_332_546, + Performance = 371_974_024 + }, + new CountryStatistics + { + Country = new Country { FlagName = "RU", FullName = "Russian Federation" }, + FlagName = "RU", + ActiveUsers = 1_609_989, + PlayCount = 1_637_052_841, + RankedScore = 221_660_827_473_004, + Performance = 163_426_476 + } + }; - api.Queue(request); + var table = new CountriesTable(1, countries); + loadTable(table); } - private void createPerformanceTable(RulesetInfo ruleset, string country, int page = 1) + private static List createUserStatistics() => new List + { + new UserStatistics + { + User = new APIUser + { + Username = "first active user", + Country = new Country { FlagName = "JP" }, + Active = true, + }, + Accuracy = 0.9972, + PlayCount = 233_215, + TotalScore = 983_231_234_656, + RankedScore = 593_231_345_897, + PP = 23_934, + GradesCount = new UserStatistics.Grades + { + SS = 35_132, + S = 23_345, + A = 12_234 + } + }, + new UserStatistics + { + User = new APIUser + { + Username = "inactive user", + Country = new Country { FlagName = "AU" }, + Active = false, + }, + Accuracy = 0.9831, + PlayCount = 195_342, + TotalScore = 683_231_234_656, + RankedScore = 393_231_345_897, + PP = 20_934, + GradesCount = new UserStatistics.Grades + { + SS = 32_132, + S = 20_345, + A = 9_234 + } + }, + new UserStatistics + { + User = new APIUser + { + Username = "second active user", + Country = new Country { FlagName = "PL" }, + Active = true, + }, + Accuracy = 0.9584, + PlayCount = 100_903, + TotalScore = 97_242_983_434, + RankedScore = 3_156_345_897, + PP = 9_568, + GradesCount = new UserStatistics.Grades + { + SS = 13_152, + S = 24_375, + A = 9_960 + } + }, + }; + + private void createPerformanceTable() { onLoadStarted(); - - request = new GetUserRankingsRequest(ruleset, country: country, page: page); - ((GetUserRankingsRequest)request).Success += rankings => Schedule(() => - { - var table = new PerformanceTable(page, rankings.Users); - loadTable(table); - }); - - api.Queue(request); + loadTable(new PerformanceTable(1, createUserStatistics())); } - private void createScoreTable(RulesetInfo ruleset, int page = 1) + private void createScoreTable() { onLoadStarted(); - - request = new GetUserRankingsRequest(ruleset, UserRankingsType.Score, page); - ((GetUserRankingsRequest)request).Success += rankings => Schedule(() => - { - var table = new ScoresTable(page, rankings.Users); - loadTable(table); - }); - - api.Queue(request); - } - - private void createSpotlightTable(RulesetInfo ruleset, int spotlight) - { - onLoadStarted(); - - request = new GetSpotlightRankingsRequest(ruleset, spotlight, RankingsSortCriteria.All); - ((GetSpotlightRankingsRequest)request).Success += rankings => Schedule(() => - { - var table = new ScoresTable(1, rankings.Users); - loadTable(table); - }); - - api.Queue(request); + loadTable(new ScoresTable(1, createUserStatistics())); } private void onLoadStarted() { loading.Show(); - request?.Cancel(); cancellationToken?.Cancel(); cancellationToken = new CancellationTokenSource(); } From 12c8243a9b55b6ed2c1509852c191fb7b13a8898 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 9 Jan 2022 14:32:33 +0100 Subject: [PATCH 141/996] Fade out inactive player rows on user ranking table --- .../Overlays/Rankings/Tables/RankingsTable.cs | 8 ++++--- .../Rankings/Tables/UserBasedTable.cs | 23 +++++++++++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs index 6e6230f958..fd69b6c80a 100644 --- a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs @@ -54,13 +54,15 @@ namespace osu.Game.Overlays.Rankings.Tables Spacing = new Vector2(0, row_spacing), }); - rankings.ForEach(_ => backgroundFlow.Add(new TableRowBackground { Height = row_height })); + rankings.ForEach(s => backgroundFlow.Add(CreateRowBackground(s))); Columns = mainHeaders.Concat(CreateAdditionalHeaders()).Cast().ToArray(); - Content = rankings.Select((s, i) => createContent((page - 1) * items_per_page + i, s)).ToArray().ToRectangular(); + Content = rankings.Select((s, i) => CreateRowContent((page - 1) * items_per_page + i, s)).ToArray().ToRectangular(); } - private Drawable[] createContent(int index, TModel item) => new Drawable[] { createIndexDrawable(index), createMainContent(item) }.Concat(CreateAdditionalContent(item)).ToArray(); + protected virtual Drawable CreateRowBackground(TModel item) => new TableRowBackground { Height = row_height }; + + protected virtual Drawable[] CreateRowContent(int index, TModel item) => new Drawable[] { createIndexDrawable(index), createMainContent(item) }.Concat(CreateAdditionalContent(item)).ToArray(); private static RankingsTableColumn[] mainHeaders => new[] { diff --git a/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs b/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs index cc2ef55a2b..c403d734d3 100644 --- a/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs @@ -24,6 +24,29 @@ namespace osu.Game.Overlays.Rankings.Tables protected virtual IEnumerable GradeColumns => new List { RankingsStrings.Statss, RankingsStrings.Stats, RankingsStrings.Stata }; + protected override Drawable CreateRowBackground(UserStatistics item) + { + var background = base.CreateRowBackground(item); + + if (!item.User.Active) + background.Alpha = 0.5f; + + return background; + } + + protected override Drawable[] CreateRowContent(int index, UserStatistics item) + { + var content = base.CreateRowContent(index, item); + + if (!item.User.Active) + { + foreach (var d in content) + d.Alpha = 0.5f; + } + + return content; + } + protected override RankingsTableColumn[] CreateAdditionalHeaders() => new[] { new RankingsTableColumn(RankingsStrings.StatAccuracy, Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), From 2e9ba40ae27fb3b177e8e86038a4328b8f2bb02c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 9 Jan 2022 14:46:15 +0100 Subject: [PATCH 142/996] Add references to web implementation wrt property used --- osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs b/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs index c403d734d3..5d150c9535 100644 --- a/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs @@ -28,6 +28,7 @@ namespace osu.Game.Overlays.Rankings.Tables { var background = base.CreateRowBackground(item); + // see: https://github.com/ppy/osu-web/blob/9de00a0b874c56893d98261d558d78d76259d81b/resources/views/multiplayer/rooms/_rankings_table.blade.php#L23 if (!item.User.Active) background.Alpha = 0.5f; @@ -38,6 +39,7 @@ namespace osu.Game.Overlays.Rankings.Tables { var content = base.CreateRowContent(index, item); + // see: https://github.com/ppy/osu-web/blob/9de00a0b874c56893d98261d558d78d76259d81b/resources/views/multiplayer/rooms/_rankings_table.blade.php#L23 if (!item.User.Active) { foreach (var d in content) From ca162ed09a68693cbef2c0bc2c1ae3b1ae3e7c44 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 Jan 2022 11:36:26 +0900 Subject: [PATCH 143/996] Update resources --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 67a9cd41dd..3b75014aad 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -51,7 +51,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index c7913cb71d..24e5858cb0 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -37,7 +37,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 63dc889e3c..b0dafa1daa 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + From 3d14511286f8badd817e4543c4f07123d4deb5d6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 Jan 2022 12:17:32 +0900 Subject: [PATCH 144/996] Remove MD5 comparison also --- osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs index b886a61c0f..9ac7838821 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs @@ -95,7 +95,6 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.That(decodedAfterEncode, Is.Not.Null); Assert.That(decodedAfterEncode.ScoreInfo.User.Username, Is.EqualTo(scoreInfo.User.Username)); - Assert.That(decodedAfterEncode.ScoreInfo.BeatmapInfo.MD5Hash, Is.EqualTo(scoreInfo.BeatmapInfo.MD5Hash)); Assert.That(decodedAfterEncode.ScoreInfo.Ruleset, Is.EqualTo(scoreInfo.Ruleset)); Assert.That(decodedAfterEncode.ScoreInfo.TotalScore, Is.EqualTo(scoreInfo.TotalScore)); Assert.That(decodedAfterEncode.ScoreInfo.MaxCombo, Is.EqualTo(scoreInfo.MaxCombo)); From 02d8a6359a98a3c8ed393eda5c26376079d8cd79 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 Jan 2022 12:28:09 +0900 Subject: [PATCH 145/996] Update `FilterMatchingTest` and filter code to use ruleset's `OnlineID` The tests were relying on the `RulesetID` being set to 0 in the example beatmap, even though the ruleset *instance* was set to ID 5. This explicitly adds that 0 value to show intent, and also removes the incorrect specification of 5 (which would cause the convert filter tests to fail). Also updates the filter code to use `OnlineID`, which is required in realm changes. --- osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs | 3 ++- osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs index 55378043e6..8ba3d1a6c7 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs @@ -16,7 +16,8 @@ namespace osu.Game.Tests.NonVisual.Filtering { private BeatmapInfo getExampleBeatmap() => new BeatmapInfo { - Ruleset = new RulesetInfo { OnlineID = 5 }, + Ruleset = new RulesetInfo { OnlineID = 0 }, + RulesetID = 0, StarRating = 4.0d, BaseDifficulty = new BeatmapDifficulty { diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index cc2db6ed31..d2c7c75da8 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -28,8 +28,8 @@ namespace osu.Game.Screens.Select.Carousel bool match = criteria.Ruleset == null || - BeatmapInfo.RulesetID == criteria.Ruleset.ID || - (BeatmapInfo.RulesetID == 0 && criteria.Ruleset.ID > 0 && criteria.AllowConvertedBeatmaps); + BeatmapInfo.RulesetID == criteria.Ruleset.OnlineID || + (BeatmapInfo.RulesetID == 0 && criteria.Ruleset.OnlineID > 0 && criteria.AllowConvertedBeatmaps); if (BeatmapInfo.BeatmapSet?.Equals(criteria.SelectedBeatmapSet) == true) { From d072f1d08d2e045e7546edc20df60959a4193032 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 Jan 2022 12:54:24 +0900 Subject: [PATCH 146/996] Add mention to detach methods of only running once --- osu.Game/Database/RealmObjectExtensions.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Database/RealmObjectExtensions.cs b/osu.Game/Database/RealmObjectExtensions.cs index e5177823ba..e09f046421 100644 --- a/osu.Game/Database/RealmObjectExtensions.cs +++ b/osu.Game/Database/RealmObjectExtensions.cs @@ -26,6 +26,9 @@ namespace osu.Game.Database /// /// Create a detached copy of the each item in the collection. /// + /// + /// Items which are already detached (ie. not managed by realm) will not be modified. + /// /// A list of managed s to detach. /// The type of object. /// A list containing non-managed copies of provided items. @@ -42,6 +45,9 @@ namespace osu.Game.Database /// /// Create a detached copy of the item. /// + /// + /// If the item if already detached (ie. not managed by realm) it will not be detached again and the original instance will be returned. This allows this method to be potentially called at multiple levels while only incurring the clone overhead once. + /// /// The managed to detach. /// The type of object. /// A non-managed copy of provided item. Will return the provided item if already detached. From b88a65166eb8cff24a1f4e9558de34721fd934fd Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 11 Jan 2022 10:22:16 +0900 Subject: [PATCH 147/996] Fix pp counter underflow with SpunOut mod --- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index d7d294df47..fdf646ef85 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty if (mods.Any(m => m is OsuModNoFail)) multiplier *= Math.Max(0.90, 1.0 - 0.02 * effectiveMissCount); - if (mods.Any(m => m is OsuModSpunOut)) + if (mods.Any(m => m is OsuModSpunOut) && totalHits > 0) multiplier *= 1.0 - Math.Pow((double)Attributes.SpinnerCount / totalHits, 0.85); if (mods.Any(h => h is OsuModRelax)) From a09563a7d9ea559ad179d5997c0d0ae3c3265235 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 11 Jan 2022 11:00:30 +0900 Subject: [PATCH 148/996] Fix calculation of most common beat length for mania scroll speed --- .../UI/Scrolling/DrawableScrollingRuleset.cs | 22 ++++--------------- 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs index 1f3a937311..926f2fd539 100644 --- a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs +++ b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs @@ -116,25 +116,11 @@ namespace osu.Game.Rulesets.UI.Scrolling if (RelativeScaleBeatLengths) { - IReadOnlyList timingPoints = Beatmap.ControlPointInfo.TimingPoints; - double maxDuration = 0; + baseBeatLength = Beatmap.GetMostCommonBeatLength(); - for (int i = 0; i < timingPoints.Count; i++) - { - if (timingPoints[i].Time > lastObjectTime) - break; - - double endTime = i < timingPoints.Count - 1 ? timingPoints[i + 1].Time : lastObjectTime; - double duration = endTime - timingPoints[i].Time; - - if (duration > maxDuration) - { - maxDuration = duration; - // The slider multiplier is post-multiplied to determine the final velocity, but for relative scale beat lengths - // the multiplier should not affect the effective timing point (the longest in the beatmap), so it is factored out here - baseBeatLength = timingPoints[i].BeatLength / Beatmap.Difficulty.SliderMultiplier; - } - } + // The slider multiplier is post-multiplied to determine the final velocity, but for relative scale beat lengths + // the multiplier should not affect the effective timing point (the longest in the beatmap), so it is factored out here + baseBeatLength /= Beatmap.Difficulty.SliderMultiplier; } // Merge sequences of timing and difficulty control points to create the aggregate "multiplier" control point From 81fed4c6bfed04d3a6ede5bbc685320a91ddcdd7 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 11 Jan 2022 12:55:12 +0900 Subject: [PATCH 149/996] Use time=0 as for the first control point --- osu.Game/Beatmaps/Beatmap.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index f69a10f000..133a22d8f7 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -93,8 +93,10 @@ namespace osu.Game.Beatmaps if (t.Time > lastTime) return (beatLength: t.BeatLength, 0); + double currentTime = i == 0 ? 0 : t.Time; double nextTime = i == ControlPointInfo.TimingPoints.Count - 1 ? lastTime : ControlPointInfo.TimingPoints[i + 1].Time; - return (beatLength: t.BeatLength, duration: nextTime - t.Time); + + return (beatLength: t.BeatLength, duration: nextTime - currentTime); }) // Aggregate durations into a set of (beatLength, duration) tuples for each beat length .GroupBy(t => Math.Round(t.beatLength * 1000) / 1000) From 38a51b9ce014fd210a02e8d499ec58295d16ec2b Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 11 Jan 2022 13:53:51 +0900 Subject: [PATCH 150/996] Add comment --- osu.Game/Beatmaps/Beatmap.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index 133a22d8f7..435183fe92 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -93,6 +93,8 @@ namespace osu.Game.Beatmaps if (t.Time > lastTime) return (beatLength: t.BeatLength, 0); + // osu-stable forced the first control point to start at 0. + // This is reproduced here to maintain compatibility around osu!mania scroll speed and song select display. double currentTime = i == 0 ? 0 : t.Time; double nextTime = i == ControlPointInfo.TimingPoints.Count - 1 ? lastTime : ControlPointInfo.TimingPoints[i + 1].Time; From 797d3f5d65e9880c613375903532b70897fb6693 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 11 Jan 2022 15:09:51 +0900 Subject: [PATCH 151/996] 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 f19a33ba75..92adbebf5a 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 43bb0cdb88..1430320c87 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 49301fc329..3cef888447 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -60,7 +60,7 @@ - + @@ -83,7 +83,7 @@ - + From 269df08fc9453b0ce2eaa9c24bd4a224e82fc9b1 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 11 Jan 2022 17:42:02 +0900 Subject: [PATCH 152/996] Make ChatLink tests into proper unit test methods --- .../Visual/Online/TestSceneChatLink.cs | 118 ++++++++++-------- 1 file changed, 65 insertions(+), 53 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs b/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs index a03c00eb58..ce5c38d33f 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs @@ -50,58 +50,19 @@ namespace osu.Game.Tests.Visual.Online Dependencies.Cache(new ChatOverlay()); Dependencies.Cache(dialogOverlay); - - testLinksGeneral(); - testEcho(); } - private void clear() => AddStep("clear messages", textContainer.Clear); - - private void addMessageWithChecks(string text, int linkAmount = 0, bool isAction = false, bool isImportant = false, params LinkAction[] expectedActions) + [SetUp] + public void Setup() => Schedule(() => { - int index = textContainer.Count + 1; - var newLine = new ChatLine(new DummyMessage(text, isAction, isImportant, index)); - textContainer.Add(newLine); + textContainer.Clear(); + }); - AddAssert($"msg #{index} has {linkAmount} link(s)", () => newLine.Message.Links.Count == linkAmount); - AddAssert($"msg #{index} has the right action", hasExpectedActions); - //AddAssert($"msg #{index} is " + (isAction ? "italic" : "not italic"), () => newLine.ContentFlow.Any() && isAction == isItalic()); - AddAssert($"msg #{index} shows {linkAmount} link(s)", isShowingLinks); - - bool hasExpectedActions() - { - var expectedActionsList = expectedActions.ToList(); - - if (expectedActionsList.Count != newLine.Message.Links.Count) - return false; - - for (int i = 0; i < newLine.Message.Links.Count; i++) - { - var action = newLine.Message.Links[i].Action; - if (action != expectedActions[i]) return false; - } - - return true; - } - - //bool isItalic() => newLine.ContentFlow.Where(d => d is OsuSpriteText).Cast().All(sprite => sprite.Font.Italics); - - bool isShowingLinks() - { - bool hasBackground = !string.IsNullOrEmpty(newLine.Message.Sender.Colour); - - Color4 textColour = isAction && hasBackground ? Color4Extensions.FromHex(newLine.Message.Sender.Colour) : Color4.White; - - var linkCompilers = newLine.ContentFlow.Where(d => d is DrawableLinkCompiler).ToList(); - var linkSprites = linkCompilers.SelectMany(comp => ((DrawableLinkCompiler)comp).Parts); - - return linkSprites.All(d => d.Colour == linkColour) - && newLine.ContentFlow.Except(linkSprites.Concat(linkCompilers)).All(d => d.Colour == textColour); - } - } - - private void testLinksGeneral() + [Test] + public void TestLinksGeneral() { + int messageIndex = 0; + addMessageWithChecks("test!"); addMessageWithChecks("dev.ppy.sh!"); addMessageWithChecks("https://dev.ppy.sh!", 1, expectedActions: LinkAction.External); @@ -117,7 +78,8 @@ namespace osu.Game.Tests.Visual.Online expectedActions: new[] { LinkAction.External, LinkAction.OpenBeatmap, LinkAction.External }); addMessageWithChecks("[https://dev.ppy.sh/home New link format with escaped [and \\[ paired] braces]", 1, expectedActions: LinkAction.External); addMessageWithChecks("[Markdown link format with escaped [and \\[ paired] braces](https://dev.ppy.sh/home)", 1, expectedActions: LinkAction.External); - addMessageWithChecks("(Old link format with escaped (and \\( paired) parentheses)[https://dev.ppy.sh/home] and [[also a rogue wiki link]]", 2, expectedActions: new[] { LinkAction.External, LinkAction.External }); + addMessageWithChecks("(Old link format with escaped (and \\( paired) parentheses)[https://dev.ppy.sh/home] and [[also a rogue wiki link]]", 2, + expectedActions: new[] { LinkAction.External, LinkAction.External }); // note that there's 0 links here (they get removed if a channel is not found) addMessageWithChecks("#lobby or #osu would be blue (and work) in the ChatDisplay test (when a proper ChatOverlay is present)."); addMessageWithChecks("I am important!", 0, false, true); @@ -129,11 +91,60 @@ namespace osu.Game.Tests.Visual.Online addMessageWithChecks("Join my osu://chan/#english.", 1, expectedActions: LinkAction.OpenChannel); addMessageWithChecks("Join my #english or #japanese channels.", 2, expectedActions: new[] { LinkAction.OpenChannel, LinkAction.OpenChannel }); addMessageWithChecks("Join my #english or #nonexistent #hashtag channels.", 1, expectedActions: LinkAction.OpenChannel); + + void addMessageWithChecks(string text, int linkAmount = 0, bool isAction = false, bool isImportant = false, params LinkAction[] expectedActions) + { + ChatLine newLine = null; + int index = messageIndex++; + + AddStep("add message", () => + { + newLine = new ChatLine(new DummyMessage(text, isAction, isImportant, index)); + textContainer.Add(newLine); + }); + + AddAssert($"msg #{index} has {linkAmount} link(s)", () => newLine.Message.Links.Count == linkAmount); + AddAssert($"msg #{index} has the right action", hasExpectedActions); + //AddAssert($"msg #{index} is " + (isAction ? "italic" : "not italic"), () => newLine.ContentFlow.Any() && isAction == isItalic()); + AddAssert($"msg #{index} shows {linkAmount} link(s)", isShowingLinks); + + bool hasExpectedActions() + { + var expectedActionsList = expectedActions.ToList(); + + if (expectedActionsList.Count != newLine.Message.Links.Count) + return false; + + for (int i = 0; i < newLine.Message.Links.Count; i++) + { + var action = newLine.Message.Links[i].Action; + if (action != expectedActions[i]) return false; + } + + return true; + } + + //bool isItalic() => newLine.ContentFlow.Where(d => d is OsuSpriteText).Cast().All(sprite => sprite.Font.Italics); + + bool isShowingLinks() + { + bool hasBackground = !string.IsNullOrEmpty(newLine.Message.Sender.Colour); + + Color4 textColour = isAction && hasBackground ? Color4Extensions.FromHex(newLine.Message.Sender.Colour) : Color4.White; + + var linkCompilers = newLine.ContentFlow.Where(d => d is DrawableLinkCompiler).ToList(); + var linkSprites = linkCompilers.SelectMany(comp => ((DrawableLinkCompiler)comp).Parts); + + return linkSprites.All(d => d.Colour == linkColour) + && newLine.ContentFlow.Except(linkSprites.Concat(linkCompilers)).All(d => d.Colour == textColour); + } + } } - private void testEcho() + [Test] + public void TestEcho() { - int echoCounter = 0; + int messageIndex = 0; addEchoWithWait("sent!", "received!"); addEchoWithWait("https://dev.ppy.sh/home", null, 500); @@ -142,15 +153,16 @@ namespace osu.Game.Tests.Visual.Online void addEchoWithWait(string text, string completeText = null, double delay = 250) { - var newLine = new ChatLine(new DummyEchoMessage(text)); + int index = messageIndex++; - AddStep($"send msg #{++echoCounter} after {delay}ms", () => + AddStep($"send msg #{index} after {delay}ms", () => { + ChatLine newLine = new ChatLine(new DummyEchoMessage(text)); textContainer.Add(newLine); Scheduler.AddDelayed(() => newLine.Message = new DummyMessage(completeText ?? text), delay); }); - AddUntilStep($"wait for msg #{echoCounter}", () => textContainer.All(line => line.Message is DummyMessage)); + AddUntilStep($"wait for msg #{index}", () => textContainer.All(line => line.Message is DummyMessage)); } } From 3cb5f43f775e4aa2e15c94b8d635aac7d7fca823 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 11 Jan 2022 17:45:31 +0900 Subject: [PATCH 153/996] Fix incorrect action returned for wiki links in DEBUG mode --- osu.Game.Tests/Visual/Online/TestSceneChatLink.cs | 4 ++-- osu.Game/Online/Chat/MessageFormatter.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs b/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs index ce5c38d33f..12b5f64559 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs @@ -67,7 +67,7 @@ namespace osu.Game.Tests.Visual.Online addMessageWithChecks("dev.ppy.sh!"); addMessageWithChecks("https://dev.ppy.sh!", 1, expectedActions: LinkAction.External); addMessageWithChecks("00:12:345 (1,2) - Test?", 1, expectedActions: LinkAction.OpenEditorTimestamp); - addMessageWithChecks("Wiki link for tasty [[Performance Points]]", 1, expectedActions: LinkAction.External); + addMessageWithChecks("Wiki link for tasty [[Performance Points]]", 1, expectedActions: LinkAction.OpenWiki); addMessageWithChecks("(osu forums)[https://dev.ppy.sh/forum] (old link format)", 1, expectedActions: LinkAction.External); addMessageWithChecks("[https://dev.ppy.sh/home New site] (new link format)", 1, expectedActions: LinkAction.External); addMessageWithChecks("[osu forums](https://dev.ppy.sh/forum) (new link format 2)", 1, expectedActions: LinkAction.External); @@ -79,7 +79,7 @@ namespace osu.Game.Tests.Visual.Online addMessageWithChecks("[https://dev.ppy.sh/home New link format with escaped [and \\[ paired] braces]", 1, expectedActions: LinkAction.External); addMessageWithChecks("[Markdown link format with escaped [and \\[ paired] braces](https://dev.ppy.sh/home)", 1, expectedActions: LinkAction.External); addMessageWithChecks("(Old link format with escaped (and \\( paired) parentheses)[https://dev.ppy.sh/home] and [[also a rogue wiki link]]", 2, - expectedActions: new[] { LinkAction.External, LinkAction.External }); + expectedActions: new[] { LinkAction.External, LinkAction.OpenWiki }); // note that there's 0 links here (they get removed if a channel is not found) addMessageWithChecks("#lobby or #osu would be blue (and work) in the ChatDisplay test (when a proper ChatOverlay is present)."); addMessageWithChecks("I am important!", 0, false, true); diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index 92911f0f51..0543c08308 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -262,7 +262,7 @@ namespace osu.Game.Online.Chat handleMatches(old_link_regex, "{1}", "{2}", result, startIndex, escapeChars: new[] { '(', ')' }); // handle wiki links - handleMatches(wiki_regex, "{1}", "https://osu.ppy.sh/wiki/{1}", result, startIndex); + handleMatches(wiki_regex, "{1}", $"https://{websiteRootUrl}/wiki/{{1}}", result, startIndex); // handle bare links handleAdvanced(advanced_link_regex, result, startIndex); From ef66ec462297f3680de43df9d7c2de3974ad3268 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 11 Jan 2022 17:53:15 +0900 Subject: [PATCH 154/996] Also fix MessageFormatter tests --- osu.Game.Tests/Chat/MessageFormatterTests.cs | 39 ++++++++++++++------ osu.Game/Online/Chat/MessageFormatter.cs | 5 ++- 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/Chat/MessageFormatterTests.cs b/osu.Game.Tests/Chat/MessageFormatterTests.cs index af87fc17ad..8def8005f1 100644 --- a/osu.Game.Tests/Chat/MessageFormatterTests.cs +++ b/osu.Game.Tests/Chat/MessageFormatterTests.cs @@ -9,6 +9,21 @@ namespace osu.Game.Tests.Chat [TestFixture] public class MessageFormatterTests { + private string originalWebsiteRootUrl; + + [OneTimeSetUp] + public void OneTimeSetUp() + { + originalWebsiteRootUrl = MessageFormatter.WebsiteRootUrl; + MessageFormatter.WebsiteRootUrl = "dev.ppy.sh"; + } + + [OneTimeTearDown] + public void OneTimeTearDown() + { + MessageFormatter.WebsiteRootUrl = originalWebsiteRootUrl; + } + [Test] public void TestBareLink() { @@ -32,8 +47,6 @@ namespace osu.Game.Tests.Chat [TestCase(LinkAction.External, "https://dev.ppy.sh/beatmapsets/discussions/123", "https://dev.ppy.sh/beatmapsets/discussions/123")] public void TestBeatmapLinks(LinkAction expectedAction, string expectedArg, string link) { - MessageFormatter.WebsiteRootUrl = "dev.ppy.sh"; - Message result = MessageFormatter.FormatMessage(new Message { Content = link }); Assert.AreEqual(result.Content, result.DisplayContent); @@ -47,7 +60,10 @@ namespace osu.Game.Tests.Chat [Test] public void TestMultipleComplexLinks() { - Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a http://test.io/link#fragment. (see https://twitter.com). Also, This string should not be altered. http://example.com/" }); + Message result = MessageFormatter.FormatMessage(new Message + { + Content = "This is a http://test.io/link#fragment. (see https://twitter.com). Also, This string should not be altered. http://example.com/" + }); Assert.AreEqual(result.Content, result.DisplayContent); Assert.AreEqual(3, result.Links.Count); @@ -104,7 +120,7 @@ namespace osu.Game.Tests.Chat Assert.AreEqual("This is a Wiki Link.", result.DisplayContent); Assert.AreEqual(1, result.Links.Count); - Assert.AreEqual("https://osu.ppy.sh/wiki/Wiki Link", result.Links[0].Url); + Assert.AreEqual("https://dev.ppy.sh/wiki/Wiki Link", result.Links[0].Url); Assert.AreEqual(10, result.Links[0].Index); Assert.AreEqual(9, result.Links[0].Length); } @@ -117,15 +133,15 @@ namespace osu.Game.Tests.Chat Assert.AreEqual("This is a Wiki Link Wiki:LinkWiki.Link.", result.DisplayContent); Assert.AreEqual(3, result.Links.Count); - Assert.AreEqual("https://osu.ppy.sh/wiki/Wiki Link", result.Links[0].Url); + Assert.AreEqual("https://dev.ppy.sh/wiki/Wiki Link", result.Links[0].Url); Assert.AreEqual(10, result.Links[0].Index); Assert.AreEqual(9, result.Links[0].Length); - Assert.AreEqual("https://osu.ppy.sh/wiki/Wiki:Link", result.Links[1].Url); + Assert.AreEqual("https://dev.ppy.sh/wiki/Wiki:Link", result.Links[1].Url); Assert.AreEqual(20, result.Links[1].Index); Assert.AreEqual(9, result.Links[1].Length); - Assert.AreEqual("https://osu.ppy.sh/wiki/Wiki.Link", result.Links[2].Url); + Assert.AreEqual("https://dev.ppy.sh/wiki/Wiki.Link", result.Links[2].Url); Assert.AreEqual(29, result.Links[2].Index); Assert.AreEqual(9, result.Links[2].Length); } @@ -445,12 +461,15 @@ namespace osu.Game.Tests.Chat [Test] public void TestLinkComplex() { - Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a [http://www.simple-test.com simple test] with some [traps] and [[wiki links]]. Don't forget to visit https://osu.ppy.sh (now!)[http://google.com]\uD83D\uDE12" }); + Message result = MessageFormatter.FormatMessage(new Message + { + Content = "This is a [http://www.simple-test.com simple test] with some [traps] and [[wiki links]]. Don't forget to visit https://osu.ppy.sh (now!)[http://google.com]\uD83D\uDE12" + }); Assert.AreEqual("This is a simple test with some [traps] and wiki links. Don't forget to visit https://osu.ppy.sh now!\0\0\0", result.DisplayContent); Assert.AreEqual(5, result.Links.Count); - Link f = result.Links.Find(l => l.Url == "https://osu.ppy.sh/wiki/wiki links"); + Link f = result.Links.Find(l => l.Url == "https://dev.ppy.sh/wiki/wiki links"); Assert.That(f, Is.Not.Null); Assert.AreEqual(44, f.Index); Assert.AreEqual(10, f.Length); @@ -514,8 +533,6 @@ namespace osu.Game.Tests.Chat [TestCase("https://dev.ppy.sh/home/changelog/lazer/2021.1012", "lazer/2021.1012")] public void TestChangelogLinks(string link, string expectedArg) { - MessageFormatter.WebsiteRootUrl = "dev.ppy.sh"; - LinkDetails result = MessageFormatter.GetLinkDetails(link); Assert.AreEqual(LinkAction.OpenChangelog, result.Action); diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index 0543c08308..d7974004b1 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -57,6 +57,7 @@ namespace osu.Game.Online.Chat /// public static string WebsiteRootUrl { + get => websiteRootUrl; set => websiteRootUrl = value .Trim('/') // trim potential trailing slash/ .Split('/').Last(); // only keep domain name, ignoring protocol. @@ -134,7 +135,7 @@ namespace osu.Game.Online.Chat case "http": case "https": // length > 3 since all these links need another argument to work - if (args.Length > 3 && args[1].EndsWith(websiteRootUrl, StringComparison.OrdinalIgnoreCase)) + if (args.Length > 3 && args[1].EndsWith(WebsiteRootUrl, StringComparison.OrdinalIgnoreCase)) { string mainArg = args[3]; @@ -262,7 +263,7 @@ namespace osu.Game.Online.Chat handleMatches(old_link_regex, "{1}", "{2}", result, startIndex, escapeChars: new[] { '(', ')' }); // handle wiki links - handleMatches(wiki_regex, "{1}", $"https://{websiteRootUrl}/wiki/{{1}}", result, startIndex); + handleMatches(wiki_regex, "{1}", $"https://{WebsiteRootUrl}/wiki/{{1}}", result, startIndex); // handle bare links handleAdvanced(advanced_link_regex, result, startIndex); From 906e700b60900e41809d8e0334e12201b667074f Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 11 Jan 2022 21:22:16 +0900 Subject: [PATCH 155/996] Improve quality of beatmap background blurs --- osu.Game/Graphics/Backgrounds/Background.cs | 48 ++++++++++++++++++--- 1 file changed, 43 insertions(+), 5 deletions(-) diff --git a/osu.Game/Graphics/Backgrounds/Background.cs b/osu.Game/Graphics/Backgrounds/Background.cs index 353054a1f1..b09ec1d9b9 100644 --- a/osu.Game/Graphics/Backgrounds/Background.cs +++ b/osu.Game/Graphics/Backgrounds/Background.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -17,8 +18,6 @@ namespace osu.Game.Graphics.Backgrounds /// public class Background : CompositeDrawable, IEquatable { - private const float blur_scale = 0.5f; - public readonly Sprite Sprite; private readonly string textureName; @@ -46,7 +45,7 @@ namespace osu.Game.Graphics.Backgrounds Sprite.Texture = textures.Get(textureName); } - public Vector2 BlurSigma => bufferedContainer?.BlurSigma / blur_scale ?? Vector2.Zero; + public Vector2 BlurSigma => Vector2.Divide(bufferedContainer?.BlurSigma ?? Vector2.Zero, blurScale); /// /// Smoothly adjusts over time. @@ -67,9 +66,48 @@ namespace osu.Game.Graphics.Backgrounds } if (bufferedContainer != null) - bufferedContainer.FrameBufferScale = newBlurSigma == Vector2.Zero ? Vector2.One : new Vector2(blur_scale); + transformBlurSigma(newBlurSigma, duration, easing); + } - bufferedContainer?.BlurTo(newBlurSigma * blur_scale, duration, easing); + private void transformBlurSigma(Vector2 newBlurSigma, double duration, Easing easing) + => this.TransformTo(nameof(blurSigma), newBlurSigma, duration, easing); + + private Vector2 blurSigmaBacking = Vector2.Zero; + private Vector2 blurScale = Vector2.One; + + private Vector2 blurSigma + { + get => blurSigmaBacking; + set + { + Debug.Assert(bufferedContainer != null); + + blurSigmaBacking = value; + blurScale = new Vector2(calculateBlurDownscale(value.X), calculateBlurDownscale(value.Y)); + + bufferedContainer.FrameBufferScale = blurScale; + bufferedContainer.BlurSigma = value * blurScale; // If the image is scaled down, the blur radius also needs to be reduced to cover the same pixel block. + } + } + + /// + /// Determines a factor to downscale the background based on a given blur sigma, in order to reduce the computational complexity of blurs. + /// + /// The blur sigma. + /// The scale-down factor. + private float calculateBlurDownscale(float sigma) + { + // If we're blurring within one pixel, scaling down will always result in an undesirable loss of quality. + // The algorithm below would also cause this value to go above 1, which is likewise undesirable. + if (sigma <= 1) + return 1; + + // A good value is one where the loss in quality as a result of downscaling the image is not easily perceivable. + // The constants here have been experimentally chosen to yield nice transitions by approximating a log curve through the points {{ 1, 1 }, { 4, 0.75 }, { 16, 0.5 }, { 32, 0.25 }}. + float scale = -0.18f * MathF.Log(0.004f * sigma); + + // To reduce shimmering, the scaling transitions are limited to happen only in increments of 0.2. + return MathF.Round(scale / 0.2f, MidpointRounding.AwayFromZero) * 0.2f; } public virtual bool Equals(Background other) From 60e42bf45eac844f656602b3074a8fdcc2f995b7 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 11 Jan 2022 23:01:17 +0900 Subject: [PATCH 156/996] Add lenience to fix test failures --- .../Visual/Background/TestSceneUserDimBackgrounds.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs index 54c5c252c0..b1f642b909 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs @@ -14,6 +14,7 @@ using osu.Framework.Input.Events; using osu.Framework.Input.States; using osu.Framework.Platform; using osu.Framework.Screens; +using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics; @@ -323,7 +324,7 @@ namespace osu.Game.Tests.Visual.Background public bool IsBackgroundUndimmed() => background.CurrentColour == Color4.White; - public bool IsUserBlurApplied() => background.CurrentBlur == new Vector2((float)BlurLevel.Value * BackgroundScreenBeatmap.USER_BLUR_FACTOR); + public bool IsUserBlurApplied() => Precision.AlmostEquals(background.CurrentBlur, new Vector2((float)BlurLevel.Value * BackgroundScreenBeatmap.USER_BLUR_FACTOR), 0.1f); public bool IsUserBlurDisabled() => background.CurrentBlur == new Vector2(0); @@ -331,9 +332,9 @@ namespace osu.Game.Tests.Visual.Background public bool IsBackgroundVisible() => background.CurrentAlpha == 1; - public bool IsBackgroundBlur() => background.CurrentBlur == new Vector2(BACKGROUND_BLUR); + public bool IsBackgroundBlur() => Precision.AlmostEquals(background.CurrentBlur, new Vector2(BACKGROUND_BLUR), 0.1f); - public bool CheckBackgroundBlur(Vector2 expected) => background.CurrentBlur == expected; + public bool CheckBackgroundBlur(Vector2 expected) => Precision.AlmostEquals(background.CurrentBlur, expected, 0.1f); /// /// Make sure every time a screen gets pushed, the background doesn't get replaced From a8c3fdd3835e53cf131f4e7667bb06d3d7930c50 Mon Sep 17 00:00:00 2001 From: r00ster Date: Tue, 11 Jan 2022 16:11:07 +0100 Subject: [PATCH 157/996] Update outdated OpenTabletDriver FAQ links --- osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs index c94b418331..802d442ced 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs @@ -107,8 +107,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input t.NewLine(); t.AddText("If your tablet is not detected, please read "); t.AddLink("this FAQ", LinkAction.External, RuntimeInfo.OS == RuntimeInfo.Platform.Windows - ? @"https://github.com/OpenTabletDriver/OpenTabletDriver/wiki/Windows-FAQ" - : @"https://github.com/OpenTabletDriver/OpenTabletDriver/wiki/Linux-FAQ"); + ? @"https://opentabletdriver.net/Wiki/FAQ/Windows" + : @"https://opentabletdriver.net/Wiki/FAQ/Linux"); t.AddText(" for troubleshooting steps."); } }), From e91d3dd8b4b5dfaba5adf41f649dcb0fd4802949 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 11 Jan 2022 21:21:04 +0100 Subject: [PATCH 158/996] Add failing test for incorrect sample control point time after paste --- .../Visual/Editing/TestSceneEditorClipboard.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs index 3aff74a0a8..e41f8372b4 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs @@ -4,6 +4,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Testing; +using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Rulesets.Edit; @@ -85,11 +86,17 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("is one object", () => EditorBeatmap.HitObjects.Count == 1); + Slider slider = null; + AddStep("retrieve slider", () => slider = (Slider)EditorBeatmap.HitObjects.Single()); AddAssert("path matches", () => { - var path = ((Slider)EditorBeatmap.HitObjects.Single()).Path; + var path = slider.Path; return path.ControlPoints.Count == 2 && path.ControlPoints.SequenceEqual(addedObject.Path.ControlPoints); }); + + // see `HitObject.control_point_leniency`. + AddAssert("sample control point has correct time", () => Precision.AlmostEquals(slider.SampleControlPoint.Time, slider.GetEndTime(), 1)); + AddAssert("difficulty control point has correct time", () => slider.DifficultyControlPoint.Time == slider.StartTime); } [Test] From 7a25fe79b71c8c6ca7f08d9934ca4107a1b0fb36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 11 Jan 2022 21:27:22 +0100 Subject: [PATCH 159/996] Fix sample control point time being calculated before defaults applied In editor contexts, the `StartTimeBindable` subscription in `HitObject` was firing before defaults were applied, which in the case of sliders manifested in an infinite end time. `ApplyDefaults()` also did not always set the time of the control point to the correct value, which matters when the beatmap is encoded. Ensure that the control points receive the correct time values during default application, and only register the `StartTimeBindable` change callback after defaults have been successfully applied. --- osu.Game/Rulesets/Objects/HitObject.cs | 44 ++++++++++++-------------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index a80b3d0fa5..e63ccbc0d3 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -87,23 +87,6 @@ namespace osu.Game.Rulesets.Objects [JsonIgnore] public SlimReadOnlyListWrapper NestedHitObjects => nestedHitObjects.AsSlimReadOnly(); - public HitObject() - { - StartTimeBindable.ValueChanged += time => - { - double offset = time.NewValue - time.OldValue; - - foreach (var nested in nestedHitObjects) - nested.StartTime += offset; - - if (DifficultyControlPoint != DifficultyControlPoint.DEFAULT) - DifficultyControlPoint.Time = time.NewValue; - - if (SampleControlPoint != SampleControlPoint.DEFAULT) - SampleControlPoint.Time = this.GetEndTime() + control_point_leniency; - }; - } - /// /// Applies default values to this HitObject. /// @@ -115,24 +98,22 @@ namespace osu.Game.Rulesets.Objects var legacyInfo = controlPointInfo as LegacyControlPointInfo; if (legacyInfo != null) - { DifficultyControlPoint = (DifficultyControlPoint)legacyInfo.DifficultyPointAt(StartTime).DeepClone(); - DifficultyControlPoint.Time = StartTime; - } else if (DifficultyControlPoint == DifficultyControlPoint.DEFAULT) DifficultyControlPoint = new DifficultyControlPoint(); + DifficultyControlPoint.Time = StartTime; + ApplyDefaultsToSelf(controlPointInfo, difficulty); // This is done here after ApplyDefaultsToSelf as we may require custom defaults to be applied to have an accurate end time. if (legacyInfo != null) - { SampleControlPoint = (SampleControlPoint)legacyInfo.SamplePointAt(this.GetEndTime() + control_point_leniency).DeepClone(); - SampleControlPoint.Time = this.GetEndTime() + control_point_leniency; - } else if (SampleControlPoint == SampleControlPoint.DEFAULT) SampleControlPoint = new SampleControlPoint(); + SampleControlPoint.Time = this.GetEndTime() + control_point_leniency; + nestedHitObjects.Clear(); CreateNestedHitObjects(cancellationToken); @@ -155,6 +136,23 @@ namespace osu.Game.Rulesets.Objects foreach (var h in nestedHitObjects) h.ApplyDefaults(controlPointInfo, difficulty, cancellationToken); + // importantly, this callback is only registered after default application + // to ensure that the read of `this.GetEndTime()` within doesn't return an invalid value + // if `StartTimeBindable` is changed prior to default application. + StartTimeBindable.ValueChanged += time => + { + double offset = time.NewValue - time.OldValue; + + foreach (var nested in nestedHitObjects) + nested.StartTime += offset; + + if (DifficultyControlPoint != DifficultyControlPoint.DEFAULT) + DifficultyControlPoint.Time = time.NewValue; + + if (SampleControlPoint != SampleControlPoint.DEFAULT) + SampleControlPoint.Time = this.GetEndTime() + control_point_leniency; + }; + DefaultsApplied?.Invoke(this); } From 76d5225bb9ee9bfcc599bf9ec6f769c3658a4b3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 11 Jan 2022 22:00:04 +0100 Subject: [PATCH 160/996] Rewrite storyboard clock management in slightly different way --- .../Backgrounds/BeatmapBackgroundWithStoryboard.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs b/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs index c5c8a25f4a..56ef87c1f4 100644 --- a/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs +++ b/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs @@ -15,7 +15,7 @@ namespace osu.Game.Graphics.Backgrounds { public class BeatmapBackgroundWithStoryboard : BeatmapBackground { - private InterpolatingFramedClock storyboardClock = null!; + private readonly InterpolatingFramedClock storyboardClock; [Resolved(CanBeNull = true)] private MusicController? musicController { get; set; } @@ -23,6 +23,7 @@ namespace osu.Game.Graphics.Backgrounds public BeatmapBackgroundWithStoryboard(WorkingBeatmap beatmap, string fallbackTextureName = "Backgrounds/bg1") : base(beatmap, fallbackTextureName) { + storyboardClock = new InterpolatingFramedClock(); } [BackgroundDependencyLoader] @@ -38,7 +39,7 @@ namespace osu.Game.Graphics.Backgrounds { RelativeSizeAxes = Axes.Both, Volume = { Value = 0 }, - Child = new DrawableStoryboard(Beatmap.Storyboard) { Clock = storyboardClock = new InterpolatingFramedClock(Beatmap.Track) } + Child = new DrawableStoryboard(Beatmap.Storyboard) { Clock = storyboardClock } }, AddInternal); } @@ -47,9 +48,13 @@ namespace osu.Game.Graphics.Backgrounds base.LoadComplete(); if (musicController != null) musicController.TrackChanged += onTrackChanged; + + updateStoryboardClockSource(Beatmap); } - private void onTrackChanged(WorkingBeatmap newBeatmap, TrackChangeDirection _) + private void onTrackChanged(WorkingBeatmap newBeatmap, TrackChangeDirection _) => updateStoryboardClockSource(newBeatmap); + + private void updateStoryboardClockSource(WorkingBeatmap newBeatmap) { if (newBeatmap != Beatmap) return; From 80ccff90689601ce449ac574ab777666ae953678 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 11 Jan 2022 22:12:23 +0100 Subject: [PATCH 161/996] Remove no longer necessary guards against default control points The subscription in which the guards were present was moved from constructor to `ApplyDefaults()`, and at that point neither the sample control point or the difficulty point can be the default point, because there are explicit paths that overwrite those with blank points in the same methods, prior to the subscription's registration. The only worry would be that someone would set the default point on the object themselves, but at that point that is most likely programmer error anyhow. --- osu.Game/Rulesets/Objects/HitObject.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index e63ccbc0d3..7d08261035 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -146,11 +146,8 @@ namespace osu.Game.Rulesets.Objects foreach (var nested in nestedHitObjects) nested.StartTime += offset; - if (DifficultyControlPoint != DifficultyControlPoint.DEFAULT) - DifficultyControlPoint.Time = time.NewValue; - - if (SampleControlPoint != SampleControlPoint.DEFAULT) - SampleControlPoint.Time = this.GetEndTime() + control_point_leniency; + DifficultyControlPoint.Time = time.NewValue; + SampleControlPoint.Time = this.GetEndTime() + control_point_leniency; }; DefaultsApplied?.Invoke(this); From a0842838e7aa880a8ce34621b2a0e77178457667 Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Wed, 12 Jan 2022 00:15:17 +0100 Subject: [PATCH 162/996] Add `AllowIme => false` where applicable Also adds `AllowWordNavigation => false` to password text box. --- osu.Game/Graphics/UserInterface/OsuNumberBox.cs | 2 ++ osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs | 4 ++++ osu.Game/Overlays/Settings/SettingsNumberBox.cs | 2 ++ osu.Game/Screens/Edit/Setup/LabelledRomanisedTextBox.cs | 2 ++ 4 files changed, 10 insertions(+) diff --git a/osu.Game/Graphics/UserInterface/OsuNumberBox.cs b/osu.Game/Graphics/UserInterface/OsuNumberBox.cs index 3d565a4464..8a3b77d3c2 100644 --- a/osu.Game/Graphics/UserInterface/OsuNumberBox.cs +++ b/osu.Game/Graphics/UserInterface/OsuNumberBox.cs @@ -7,6 +7,8 @@ namespace osu.Game.Graphics.UserInterface { public class OsuNumberBox : OsuTextBox { + protected override bool AllowIme => false; + protected override bool CanAddCharacter(char character) => character.IsAsciiDigit(); } } diff --git a/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs b/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs index 8e82f4a7c1..b276159558 100644 --- a/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs +++ b/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs @@ -29,6 +29,10 @@ namespace osu.Game.Graphics.UserInterface protected override bool AllowClipboardExport => false; + protected override bool AllowWordNavigation => false; + + protected override bool AllowIme => false; + private readonly CapsWarning warning; [Resolved] diff --git a/osu.Game/Overlays/Settings/SettingsNumberBox.cs b/osu.Game/Overlays/Settings/SettingsNumberBox.cs index cc4446033a..d931c53e73 100644 --- a/osu.Game/Overlays/Settings/SettingsNumberBox.cs +++ b/osu.Game/Overlays/Settings/SettingsNumberBox.cs @@ -68,6 +68,8 @@ namespace osu.Game.Overlays.Settings private class OutlinedNumberBox : OutlinedTextBox { + protected override bool AllowIme => false; + protected override bool CanAddCharacter(char character) => character.IsAsciiDigit(); public new void NotifyInputError() => base.NotifyInputError(); diff --git a/osu.Game/Screens/Edit/Setup/LabelledRomanisedTextBox.cs b/osu.Game/Screens/Edit/Setup/LabelledRomanisedTextBox.cs index ee9d86029e..c39b4d6f41 100644 --- a/osu.Game/Screens/Edit/Setup/LabelledRomanisedTextBox.cs +++ b/osu.Game/Screens/Edit/Setup/LabelledRomanisedTextBox.cs @@ -13,6 +13,8 @@ namespace osu.Game.Screens.Edit.Setup private class RomanisedTextBox : OsuTextBox { + protected override bool AllowIme => false; + protected override bool CanAddCharacter(char character) => MetadataUtils.IsRomanised(character); } From 11e07c1137303f0e9c132b5ee684c356d8bac3f7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 12 Jan 2022 15:20:48 +0900 Subject: [PATCH 163/996] Add button to compact realm on demand In general we're doing things correctly so the realm file shouldn't expand (unless mass deletions are made from it), but this is a nice way to manually confirm the behaviour. Sometimes if using realm studio with osu! running, for instance, the realm file size can blow out of proportion. This will recover from such cases. Note that calling `RealmFactory.Compact` itself is not enough, as it will fail unless all instances of the realm have been closed. --- osu.Game/Localisation/DebugSettingsStrings.cs | 5 +++++ .../Sections/DebugSettings/MemorySettings.cs | 14 +++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/osu.Game/Localisation/DebugSettingsStrings.cs b/osu.Game/Localisation/DebugSettingsStrings.cs index dd21739096..74b2c8d892 100644 --- a/osu.Game/Localisation/DebugSettingsStrings.cs +++ b/osu.Game/Localisation/DebugSettingsStrings.cs @@ -44,6 +44,11 @@ namespace osu.Game.Localisation /// public static LocalisableString ClearAllCaches => new TranslatableString(getKey(@"clear_all_caches"), @"Clear all caches"); + /// + /// "Compact realm" + /// + public static LocalisableString CompactRealm => new TranslatableString(getKey(@"compact_realm"), @"Compact realm"); + private static string getKey(string key) => $"{prefix}:{key}"; } } diff --git a/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs b/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs index 6f48768dcd..eb6e48dfbf 100644 --- a/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs +++ b/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs @@ -6,6 +6,7 @@ using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Localisation; using osu.Framework.Platform; +using osu.Game.Database; using osu.Game.Localisation; namespace osu.Game.Overlays.Settings.Sections.DebugSettings @@ -15,7 +16,7 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings protected override LocalisableString Header => DebugSettingsStrings.MemoryHeader; [BackgroundDependencyLoader] - private void load(FrameworkDebugConfigManager config, GameHost host) + private void load(FrameworkDebugConfigManager config, GameHost host, RealmContextFactory realmFactory) { Children = new Drawable[] { @@ -24,6 +25,17 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings Text = DebugSettingsStrings.ClearAllCaches, Action = host.Collect }, + new SettingsButton + { + Text = DebugSettingsStrings.CompactRealm, + Action = () => + { + // Blocking operations implicitly causes a Compact(). + using (realmFactory.BlockAllOperations()) + { + } + } + }, }; } } From c383f26729e3632a9d1e0567234435249652facc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Nov 2021 15:59:12 +0900 Subject: [PATCH 164/996] Remove EF specific tests that have since been replaced --- .../Beatmaps/IO/ImportBeatmapTest.cs | 975 +----------------- 1 file changed, 3 insertions(+), 972 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index c02141bf9f..da414dcf8d 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -2,983 +2,24 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.IO; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using NUnit.Framework; -using osu.Framework.Platform; -using osu.Game.IPC; using osu.Framework.Allocation; using osu.Framework.Extensions; -using osu.Framework.Extensions.ObjectExtensions; -using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.Database; -using osu.Game.Extensions; -using osu.Game.IO; -using osu.Game.Online.API.Requests.Responses; -using osu.Game.Overlays.Notifications; -using osu.Game.Rulesets.Osu; -using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Scoring; using osu.Game.Tests.Resources; -using osu.Game.Tests.Scores.IO; -using SharpCompress.Archives; -using SharpCompress.Archives.Zip; -using SharpCompress.Common; -using SharpCompress.Writers.Zip; -using FileInfo = System.IO.FileInfo; namespace osu.Game.Tests.Beatmaps.IO { [TestFixture] public class ImportBeatmapTest : ImportTest { - [Test] - public async Task TestImportWhenClosed() - { - // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) - { - try - { - await LoadOszIntoOsu(LoadOsuIntoHost(host)); - } - finally - { - host.Exit(); - } - } - } - - [Test] - public async Task TestImportThenDelete() - { - // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) - { - try - { - var osu = LoadOsuIntoHost(host); - - var imported = await LoadOszIntoOsu(osu); - - deleteBeatmapSet(imported, osu); - } - finally - { - host.Exit(); - } - } - } - - [Test] - public async Task TestImportThenDeleteFromStream() - { - // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) - { - try - { - var osu = LoadOsuIntoHost(host); - - string tempPath = TestResources.GetTestBeatmapForImport(); - - var manager = osu.Dependencies.Get(); - - ILive importedSet; - - using (var stream = File.OpenRead(tempPath)) - { - importedSet = await manager.Import(new ImportTask(stream, Path.GetFileName(tempPath))); - await ensureLoaded(osu); - } - - Assert.IsTrue(File.Exists(tempPath), "Stream source file somehow went missing"); - File.Delete(tempPath); - - var imported = manager.GetAllUsableBeatmapSets().Find(beatmapSet => beatmapSet.ID == importedSet.Value.ID); - - deleteBeatmapSet(imported, osu); - } - finally - { - host.Exit(); - } - } - } - - [Test] - public async Task TestImportThenImport() - { - // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) - { - try - { - var osu = LoadOsuIntoHost(host); - - var imported = await LoadOszIntoOsu(osu); - var importedSecondTime = await LoadOszIntoOsu(osu); - - // check the newly "imported" beatmap is actually just the restored previous import. since it matches hash. - Assert.IsTrue(imported.ID == importedSecondTime.ID); - Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID); - - checkBeatmapSetCount(osu, 1); - checkSingleReferencedFileCount(osu, 18); - } - finally - { - host.Exit(); - } - } - } - - [Test] - public async Task TestImportThenImportWithReZip() - { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) - { - try - { - var osu = LoadOsuIntoHost(host); - - string temp = TestResources.GetTestBeatmapForImport(); - - string extractedFolder = $"{temp}_extracted"; - Directory.CreateDirectory(extractedFolder); - - try - { - var imported = await LoadOszIntoOsu(osu); - - string hashBefore = hashFile(temp); - - using (var zip = ZipArchive.Open(temp)) - zip.WriteToDirectory(extractedFolder); - - using (var zip = ZipArchive.Create()) - { - zip.AddAllFromDirectory(extractedFolder); - zip.SaveTo(temp, new ZipWriterOptions(CompressionType.Deflate)); - } - - // zip files differ because different compression or encoder. - Assert.AreNotEqual(hashBefore, hashFile(temp)); - - var importedSecondTime = await osu.Dependencies.Get().Import(new ImportTask(temp)); - - await ensureLoaded(osu); - - // but contents doesn't, so existing should still be used. - Assert.IsTrue(imported.ID == importedSecondTime.Value.ID); - Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Value.Beatmaps.First().ID); - } - finally - { - Directory.Delete(extractedFolder, true); - } - } - finally - { - host.Exit(); - } - } - } - - [Test] - public async Task TestImportThenImportWithChangedHashedFile() - { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) - { - try - { - var osu = LoadOsuIntoHost(host); - - string temp = TestResources.GetTestBeatmapForImport(); - - string extractedFolder = $"{temp}_extracted"; - Directory.CreateDirectory(extractedFolder); - - try - { - var imported = await LoadOszIntoOsu(osu); - - await createScoreForBeatmap(osu, imported.Beatmaps.First()); - - using (var zip = ZipArchive.Open(temp)) - zip.WriteToDirectory(extractedFolder); - - // arbitrary write to hashed file - // this triggers the special BeatmapManager.PreImport deletion/replacement flow. - using (var sw = new FileInfo(Directory.GetFiles(extractedFolder, "*.osu").First()).AppendText()) - await sw.WriteLineAsync("// changed"); - - using (var zip = ZipArchive.Create()) - { - zip.AddAllFromDirectory(extractedFolder); - zip.SaveTo(temp, new ZipWriterOptions(CompressionType.Deflate)); - } - - var importedSecondTime = await osu.Dependencies.Get().Import(new ImportTask(temp)); - - await ensureLoaded(osu); - - // check the newly "imported" beatmap is not the original. - Assert.IsTrue(imported.ID != importedSecondTime.Value.ID); - Assert.IsTrue(imported.Beatmaps.First().ID != importedSecondTime.Value.Beatmaps.First().ID); - } - finally - { - Directory.Delete(extractedFolder, true); - } - } - finally - { - host.Exit(); - } - } - } - - [Test] - [Ignore("intentionally broken by import optimisations")] - public async Task TestImportThenImportWithChangedFile() - { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) - { - try - { - var osu = LoadOsuIntoHost(host); - - string temp = TestResources.GetTestBeatmapForImport(); - - string extractedFolder = $"{temp}_extracted"; - Directory.CreateDirectory(extractedFolder); - - try - { - var imported = await LoadOszIntoOsu(osu); - - using (var zip = ZipArchive.Open(temp)) - zip.WriteToDirectory(extractedFolder); - - // arbitrary write to non-hashed file - using (var sw = new FileInfo(Directory.GetFiles(extractedFolder, "*.mp3").First()).AppendText()) - await sw.WriteLineAsync("text"); - - using (var zip = ZipArchive.Create()) - { - zip.AddAllFromDirectory(extractedFolder); - zip.SaveTo(temp, new ZipWriterOptions(CompressionType.Deflate)); - } - - var importedSecondTime = await osu.Dependencies.Get().Import(new ImportTask(temp)); - - await ensureLoaded(osu); - - // check the newly "imported" beatmap is not the original. - Assert.IsTrue(imported.ID != importedSecondTime.Value.ID); - Assert.IsTrue(imported.Beatmaps.First().ID != importedSecondTime.Value.Beatmaps.First().ID); - } - finally - { - Directory.Delete(extractedFolder, true); - } - } - finally - { - host.Exit(); - } - } - } - - [Test] - public async Task TestImportThenImportWithDifferentFilename() - { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) - { - try - { - var osu = LoadOsuIntoHost(host); - - string temp = TestResources.GetTestBeatmapForImport(); - - string extractedFolder = $"{temp}_extracted"; - Directory.CreateDirectory(extractedFolder); - - try - { - var imported = await LoadOszIntoOsu(osu); - - using (var zip = ZipArchive.Open(temp)) - zip.WriteToDirectory(extractedFolder); - - // change filename - var firstFile = new FileInfo(Directory.GetFiles(extractedFolder).First()); - firstFile.MoveTo(Path.Combine(firstFile.DirectoryName.AsNonNull(), $"{firstFile.Name}-changed{firstFile.Extension}")); - - using (var zip = ZipArchive.Create()) - { - zip.AddAllFromDirectory(extractedFolder); - zip.SaveTo(temp, new ZipWriterOptions(CompressionType.Deflate)); - } - - var importedSecondTime = await osu.Dependencies.Get().Import(new ImportTask(temp)); - - await ensureLoaded(osu); - - // check the newly "imported" beatmap is not the original. - Assert.IsTrue(imported.ID != importedSecondTime.Value.ID); - Assert.IsTrue(imported.Beatmaps.First().ID != importedSecondTime.Value.Beatmaps.First().ID); - } - finally - { - Directory.Delete(extractedFolder, true); - } - } - finally - { - host.Exit(); - } - } - } - - [Test] - [Ignore("intentionally broken by import optimisations")] - public async Task TestImportCorruptThenImport() - { - // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) - { - try - { - var osu = LoadOsuIntoHost(host); - - var imported = await LoadOszIntoOsu(osu); - - var firstFile = imported.Files.First(); - - var files = osu.Dependencies.Get(); - - long originalLength; - using (var stream = files.Storage.GetStream(firstFile.FileInfo.GetStoragePath())) - originalLength = stream.Length; - - using (var stream = files.Storage.GetStream(firstFile.FileInfo.GetStoragePath(), FileAccess.Write, FileMode.Create)) - stream.WriteByte(0); - - var importedSecondTime = await LoadOszIntoOsu(osu); - - using (var stream = files.Storage.GetStream(firstFile.FileInfo.GetStoragePath())) - Assert.AreEqual(stream.Length, originalLength, "Corruption was not fixed on second import"); - - // check the newly "imported" beatmap is actually just the restored previous import. since it matches hash. - Assert.IsTrue(imported.ID == importedSecondTime.ID); - Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID); - - checkBeatmapSetCount(osu, 1); - checkSingleReferencedFileCount(osu, 18); - } - finally - { - host.Exit(); - } - } - } - - [Test] - public async Task TestModelCreationFailureDoesntReturn() - { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) - { - try - { - var osu = LoadOsuIntoHost(host); - var importer = osu.Dependencies.Get(); - - var progressNotification = new ImportProgressNotification(); - - var zipStream = new MemoryStream(); - - using (var zip = ZipArchive.Create()) - zip.SaveTo(zipStream, new ZipWriterOptions(CompressionType.Deflate)); - - var imported = await importer.Import( - progressNotification, - new ImportTask(zipStream, string.Empty) - ); - - checkBeatmapSetCount(osu, 0); - checkBeatmapCount(osu, 0); - - Assert.IsEmpty(imported); - Assert.AreEqual(ProgressNotificationState.Cancelled, progressNotification.State); - } - finally - { - host.Exit(); - } - } - } - - [Test] - public async Task TestRollbackOnFailure() - { - // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) - { - try - { - int itemAddRemoveFireCount = 0; - int loggedExceptionCount = 0; - - Logger.NewEntry += l => - { - if (l.Target == LoggingTarget.Database && l.Exception != null) - Interlocked.Increment(ref loggedExceptionCount); - }; - - var osu = LoadOsuIntoHost(host); - var manager = osu.Dependencies.Get(); - - // ReSharper disable once AccessToModifiedClosure - manager.ItemUpdated += _ => Interlocked.Increment(ref itemAddRemoveFireCount); - manager.ItemRemoved += _ => Interlocked.Increment(ref itemAddRemoveFireCount); - - var imported = await LoadOszIntoOsu(osu); - - Assert.AreEqual(0, itemAddRemoveFireCount -= 1); - - imported.Hash += "-changed"; - manager.Update(imported); - - Assert.AreEqual(0, itemAddRemoveFireCount -= 1); - - checkBeatmapSetCount(osu, 1); - checkBeatmapCount(osu, 12); - checkSingleReferencedFileCount(osu, 18); - - string brokenTempFilename = TestResources.GetTestBeatmapForImport(); - - MemoryStream brokenOsu = new MemoryStream(); - MemoryStream brokenOsz = new MemoryStream(await File.ReadAllBytesAsync(brokenTempFilename)); - - File.Delete(brokenTempFilename); - - using (var outStream = File.Open(brokenTempFilename, FileMode.CreateNew)) - using (var zip = ZipArchive.Open(brokenOsz)) - { - zip.AddEntry("broken.osu", brokenOsu, false); - zip.SaveTo(outStream, CompressionType.Deflate); - } - - // this will trigger purging of the existing beatmap (online set id match) but should rollback due to broken osu. - try - { - await manager.Import(new ImportTask(brokenTempFilename)); - } - catch - { - } - - // no events should be fired in the case of a rollback. - Assert.AreEqual(0, itemAddRemoveFireCount); - - checkBeatmapSetCount(osu, 1); - checkBeatmapCount(osu, 12); - - checkSingleReferencedFileCount(osu, 18); - - Assert.AreEqual(1, loggedExceptionCount); - - File.Delete(brokenTempFilename); - } - finally - { - host.Exit(); - } - } - } - - [Test] - public async Task TestImportThenDeleteThenImport() - { - // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) - { - try - { - var osu = LoadOsuIntoHost(host); - - var imported = await LoadOszIntoOsu(osu); - - deleteBeatmapSet(imported, osu); - - var importedSecondTime = await LoadOszIntoOsu(osu); - - // check the newly "imported" beatmap is actually just the restored previous import. since it matches hash. - Assert.IsTrue(imported.ID == importedSecondTime.ID); - Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID); - } - finally - { - host.Exit(); - } - } - } - - [Test] - public async Task TestImportThenDeleteThenImportWithOnlineIDsMissing() - { - // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) - { - try - { - var osu = LoadOsuIntoHost(host); - - var imported = await LoadOszIntoOsu(osu); - - foreach (var b in imported.Beatmaps) - b.OnlineID = null; - - osu.Dependencies.Get().Update(imported); - - deleteBeatmapSet(imported, osu); - - var importedSecondTime = await LoadOszIntoOsu(osu); - - // check the newly "imported" beatmap has been reimported due to mismatch (even though hashes matched) - Assert.IsTrue(imported.ID != importedSecondTime.ID); - Assert.IsTrue(imported.Beatmaps.First().ID != importedSecondTime.Beatmaps.First().ID); - } - finally - { - host.Exit(); - } - } - } - - [Test] - public async Task TestImportWithDuplicateBeatmapIDs() - { - // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) - { - try - { - var osu = LoadOsuIntoHost(host); - - var metadata = new BeatmapMetadata - { - Artist = "SomeArtist", - AuthorString = "SomeAuthor" - }; - - var difficulty = new BeatmapDifficulty(); - - var toImport = new BeatmapSetInfo - { - OnlineID = 1, - Metadata = metadata, - Beatmaps = - { - new BeatmapInfo - { - OnlineID = 2, - Metadata = metadata, - BaseDifficulty = difficulty - }, - new BeatmapInfo - { - OnlineID = 2, - Metadata = metadata, - Status = BeatmapOnlineStatus.Loved, - BaseDifficulty = difficulty - } - } - }; - - var manager = osu.Dependencies.Get(); - - var imported = await manager.Import(toImport); - - Assert.NotNull(imported); - Assert.AreEqual(null, imported.Value.Beatmaps[0].OnlineID); - Assert.AreEqual(null, imported.Value.Beatmaps[1].OnlineID); - } - finally - { - host.Exit(); - } - } - } - - [Test] - [NonParallelizable] - public void TestImportOverIPC() - { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(true)) - using (HeadlessGameHost client = new CleanRunHeadlessGameHost(true)) - { - try - { - Assert.IsTrue(host.IsPrimaryInstance); - Assert.IsFalse(client.IsPrimaryInstance); - - var osu = LoadOsuIntoHost(host); - - string temp = TestResources.GetTestBeatmapForImport(); - - var importer = new ArchiveImportIPCChannel(client); - if (!importer.ImportAsync(temp).Wait(10000)) - Assert.Fail(@"IPC took too long to send"); - - ensureLoaded(osu).WaitSafely(); - - waitForOrAssert(() => !File.Exists(temp), "Temporary still exists after IPC import", 5000); - } - finally - { - host.Exit(); - } - } - } - - [Test] - public async Task TestImportWhenFileOpen() - { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) - { - try - { - var osu = LoadOsuIntoHost(host); - string temp = TestResources.GetTestBeatmapForImport(); - using (File.OpenRead(temp)) - await osu.Dependencies.Get().Import(temp); - await ensureLoaded(osu); - File.Delete(temp); - Assert.IsFalse(File.Exists(temp), "We likely held a read lock on the file when we shouldn't"); - } - finally - { - host.Exit(); - } - } - } - - [Test] - public async Task TestImportWithDuplicateHashes() - { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) - { - try - { - var osu = LoadOsuIntoHost(host); - - string temp = TestResources.GetTestBeatmapForImport(); - - string extractedFolder = $"{temp}_extracted"; - Directory.CreateDirectory(extractedFolder); - - try - { - using (var zip = ZipArchive.Open(temp)) - zip.WriteToDirectory(extractedFolder); - - using (var zip = ZipArchive.Create()) - { - zip.AddAllFromDirectory(extractedFolder); - zip.AddEntry("duplicate.osu", Directory.GetFiles(extractedFolder, "*.osu").First()); - zip.SaveTo(temp, new ZipWriterOptions(CompressionType.Deflate)); - } - - await osu.Dependencies.Get().Import(temp); - - await ensureLoaded(osu); - } - finally - { - Directory.Delete(extractedFolder, true); - } - } - finally - { - host.Exit(); - } - } - } - - [Test] - public async Task TestImportNestedStructure() - { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) - { - try - { - var osu = LoadOsuIntoHost(host); - - string temp = TestResources.GetTestBeatmapForImport(); - - string extractedFolder = $"{temp}_extracted"; - string subfolder = Path.Combine(extractedFolder, "subfolder"); - - Directory.CreateDirectory(subfolder); - - try - { - using (var zip = ZipArchive.Open(temp)) - zip.WriteToDirectory(subfolder); - - using (var zip = ZipArchive.Create()) - { - zip.AddAllFromDirectory(extractedFolder); - zip.SaveTo(temp, new ZipWriterOptions(CompressionType.Deflate)); - } - - var imported = await osu.Dependencies.Get().Import(new ImportTask(temp)); - - await ensureLoaded(osu); - - Assert.IsFalse(imported.Value.Files.Any(f => f.Filename.Contains("subfolder")), "Files contain common subfolder"); - } - finally - { - Directory.Delete(extractedFolder, true); - } - } - finally - { - host.Exit(); - } - } - } - - [Test] - public async Task TestImportWithIgnoredDirectoryInArchive() - { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) - { - try - { - var osu = LoadOsuIntoHost(host); - - string temp = TestResources.GetTestBeatmapForImport(); - - string extractedFolder = $"{temp}_extracted"; - string dataFolder = Path.Combine(extractedFolder, "actual_data"); - string resourceForkFolder = Path.Combine(extractedFolder, "__MACOSX"); - string resourceForkFilePath = Path.Combine(resourceForkFolder, ".extracted"); - - Directory.CreateDirectory(dataFolder); - Directory.CreateDirectory(resourceForkFolder); - - using (var resourceForkFile = File.CreateText(resourceForkFilePath)) - { - await resourceForkFile.WriteLineAsync("adding content so that it's not empty"); - } - - try - { - using (var zip = ZipArchive.Open(temp)) - zip.WriteToDirectory(dataFolder); - - using (var zip = ZipArchive.Create()) - { - zip.AddAllFromDirectory(extractedFolder); - zip.SaveTo(temp, new ZipWriterOptions(CompressionType.Deflate)); - } - - var imported = await osu.Dependencies.Get().Import(new ImportTask(temp)); - - await ensureLoaded(osu); - - Assert.IsFalse(imported.Value.Files.Any(f => f.Filename.Contains("__MACOSX")), "Files contain resource fork folder, which should be ignored"); - Assert.IsFalse(imported.Value.Files.Any(f => f.Filename.Contains("actual_data")), "Files contain common subfolder"); - } - finally - { - Directory.Delete(extractedFolder, true); - } - } - finally - { - host.Exit(); - } - } - } - - [Test] - public void TestUpdateBeatmapInfo() - { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) - { - try - { - var osu = LoadOsuIntoHost(host); - var manager = osu.Dependencies.Get(); - - string temp = TestResources.GetTestBeatmapForImport(); - - osu.Dependencies.Get().Import(temp).WaitSafely(); - - // Update via the beatmap, not the beatmap info, to ensure correct linking - BeatmapSetInfo setToUpdate = manager.GetAllUsableBeatmapSets()[0]; - Beatmap beatmapToUpdate = (Beatmap)manager.GetWorkingBeatmap(setToUpdate.Beatmaps.First(b => b.RulesetID == 0)).Beatmap; - beatmapToUpdate.BeatmapInfo.DifficultyName = "updated"; - - manager.Update(setToUpdate); - - BeatmapInfo updatedInfo = manager.QueryBeatmap(b => b.ID == beatmapToUpdate.BeatmapInfo.ID); - Assert.That(updatedInfo.DifficultyName, Is.EqualTo("updated")); - } - finally - { - host.Exit(); - } - } - } - - [Test] - public void TestUpdateBeatmapFile() - { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) - { - try - { - var osu = LoadOsuIntoHost(host); - var manager = osu.Dependencies.Get(); - - string temp = TestResources.GetTestBeatmapForImport(); - - osu.Dependencies.Get().Import(temp).WaitSafely(); - - BeatmapSetInfo setToUpdate = manager.GetAllUsableBeatmapSets()[0]; - - var beatmapInfo = setToUpdate.Beatmaps.First(b => b.RulesetID == 0); - Beatmap beatmapToUpdate = (Beatmap)manager.GetWorkingBeatmap(setToUpdate.Beatmaps.First(b => b.RulesetID == 0)).Beatmap; - BeatmapSetFileInfo fileToUpdate = setToUpdate.Files.First(f => beatmapToUpdate.BeatmapInfo.Path.Contains(f.Filename)); - - string oldMd5Hash = beatmapToUpdate.BeatmapInfo.MD5Hash; - - beatmapToUpdate.HitObjects.Clear(); - beatmapToUpdate.HitObjects.Add(new HitCircle { StartTime = 5000 }); - - manager.Save(beatmapInfo, beatmapToUpdate); - - // Check that the old file reference has been removed - Assert.That(manager.QueryBeatmapSet(s => s.ID == setToUpdate.ID).Files.All(f => f.ID != fileToUpdate.ID)); - - // Check that the new file is referenced correctly by attempting a retrieval - Beatmap updatedBeatmap = (Beatmap)manager.GetWorkingBeatmap(manager.QueryBeatmap(b => b.ID == beatmapToUpdate.BeatmapInfo.ID)).Beatmap; - Assert.That(updatedBeatmap.HitObjects.Count, Is.EqualTo(1)); - Assert.That(updatedBeatmap.HitObjects[0].StartTime, Is.EqualTo(5000)); - Assert.That(updatedBeatmap.BeatmapInfo.MD5Hash, Is.Not.EqualTo(oldMd5Hash)); - } - finally - { - host.Exit(); - } - } - } - - // TODO: needs to be pulled across to realm implementation when this file is nuked. - [Test] - public void TestSaveRemovesInvalidCharactersFromPath() - { - // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) - { - try - { - var osu = LoadOsuIntoHost(host); - - var manager = osu.Dependencies.Get(); - - var working = manager.CreateNew(new OsuRuleset().RulesetInfo, APIUser.SYSTEM_USER); - - var beatmap = working.Beatmap; - - beatmap.BeatmapInfo.DifficultyName = "difficulty"; - beatmap.BeatmapInfo.Metadata = new BeatmapMetadata - { - Artist = "Artist/With\\Slashes", - Title = "Title", - AuthorString = "mapper", - }; - - manager.Save(beatmap.BeatmapInfo, working.Beatmap); - - Assert.AreEqual("Artist_With_Slashes - Title (mapper) [difficulty].osu", beatmap.BeatmapInfo.Path); - } - finally - { - host.Exit(); - } - } - } - - [Test] - public void TestCreateNewEmptyBeatmap() - { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) - { - try - { - var osu = LoadOsuIntoHost(host); - var manager = osu.Dependencies.Get(); - - var working = manager.CreateNew(new OsuRuleset().RulesetInfo, APIUser.SYSTEM_USER); - - manager.Save(working.BeatmapInfo, working.Beatmap); - - var retrievedSet = manager.GetAllUsableBeatmapSets()[0]; - - // Check that the new file is referenced correctly by attempting a retrieval - Beatmap updatedBeatmap = (Beatmap)manager.GetWorkingBeatmap(retrievedSet.Beatmaps[0]).Beatmap; - Assert.That(updatedBeatmap.HitObjects.Count, Is.EqualTo(0)); - } - finally - { - host.Exit(); - } - } - } - - [Test] - public void TestCreateNewBeatmapWithObject() - { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) - { - try - { - var osu = LoadOsuIntoHost(host); - var manager = osu.Dependencies.Get(); - - var working = manager.CreateNew(new OsuRuleset().RulesetInfo, APIUser.SYSTEM_USER); - - ((Beatmap)working.Beatmap).HitObjects.Add(new HitCircle { StartTime = 5000 }); - - manager.Save(working.BeatmapInfo, working.Beatmap); - - var retrievedSet = manager.GetAllUsableBeatmapSets()[0]; - - // Check that the new file is referenced correctly by attempting a retrieval - Beatmap updatedBeatmap = (Beatmap)manager.GetWorkingBeatmap(retrievedSet.Beatmaps[0]).Beatmap; - Assert.That(updatedBeatmap.HitObjects.Count, Is.EqualTo(1)); - Assert.That(updatedBeatmap.HitObjects[0].StartTime, Is.EqualTo(5000)); - } - finally - { - host.Exit(); - } - } - } - - public static Task LoadQuickOszIntoOsu(OsuGameBase osu) => Task.Factory.StartNew(() => + public static async Task LoadQuickOszIntoOsu(OsuGameBase osu) { string temp = TestResources.GetQuickTestBeatmapForImport(); @@ -1020,16 +61,6 @@ namespace osu.Game.Tests.Beatmaps.IO Assert.IsTrue(manager.QueryBeatmapSets(_ => true).First().DeletePending); } - private static Task createScoreForBeatmap(OsuGameBase osu, BeatmapInfo beatmapInfo) - { - return ImportScoreTest.LoadScoreIntoOsu(osu, new ScoreInfo - { - OnlineID = 2, - BeatmapInfo = beatmapInfo, - BeatmapInfoID = beatmapInfo.ID - }, new ImportScoreTest.TestArchiveReader()); - } - private static void checkBeatmapSetCount(OsuGameBase osu, int expected, bool includeDeletePending = false) { var manager = osu.Dependencies.Get(); @@ -1064,7 +95,7 @@ namespace osu.Game.Tests.Beatmaps.IO // ensure we were stored to beatmap database backing... Assert.IsTrue(resultSets.Count() == 1, $@"Incorrect result count found ({resultSets.Count()} but should be 1)."); - IEnumerable queryBeatmaps() => store.QueryBeatmaps(s => s.BeatmapSet.OnlineID == 241526 && s.BaseDifficultyID > 0); + IEnumerable queryBeatmaps() => store.QueryBeatmaps(s => s.BeatmapSet.OnlineID == 241526); IEnumerable queryBeatmapSets() => store.QueryBeatmapSets(s => s.OnlineID == 241526); // if we don't re-check here, the set will be inserted but the beatmaps won't be present yet. From 0b6c4497bd0a9f74d4153a218afb603b88d9b46c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Nov 2021 18:59:14 +0900 Subject: [PATCH 165/996] Rename EF classes to allow for shit to hit the fan --- ...apDifficulty.cs => EFBeatmapDifficulty.cs} | 14 ++++---- .../{BeatmapInfo.cs => EFBeatmapInfo.cs} | 32 +++++++++---------- ...eatmapMetadata.cs => EFBeatmapMetadata.cs} | 8 ++--- ...{BeatmapSetInfo.cs => EFBeatmapSetInfo.cs} | 8 ++--- osu.Game/Database/OsuDbContext.cs | 28 ++++++++-------- .../{RulesetInfo.cs => EFRulesetInfo.cs} | 6 ++-- osu.sln.DotSettings | 2 +- 7 files changed, 49 insertions(+), 49 deletions(-) rename osu.Game/Beatmaps/{BeatmapDifficulty.cs => EFBeatmapDifficulty.cs} (81%) rename osu.Game/Beatmaps/{BeatmapInfo.cs => EFBeatmapInfo.cs} (76%) rename osu.Game/Beatmaps/{BeatmapMetadata.cs => EFBeatmapMetadata.cs} (86%) rename osu.Game/Beatmaps/{BeatmapSetInfo.cs => EFBeatmapSetInfo.cs} (90%) rename osu.Game/Rulesets/{RulesetInfo.cs => EFRulesetInfo.cs} (86%) diff --git a/osu.Game/Beatmaps/BeatmapDifficulty.cs b/osu.Game/Beatmaps/EFBeatmapDifficulty.cs similarity index 81% rename from osu.Game/Beatmaps/BeatmapDifficulty.cs rename to osu.Game/Beatmaps/EFBeatmapDifficulty.cs index 65d1fb8286..843c6ac6bf 100644 --- a/osu.Game/Beatmaps/BeatmapDifficulty.cs +++ b/osu.Game/Beatmaps/EFBeatmapDifficulty.cs @@ -6,7 +6,7 @@ using osu.Game.Database; namespace osu.Game.Beatmaps { - public class BeatmapDifficulty : IHasPrimaryKey, IBeatmapDifficultyInfo + public class EFBeatmapDifficulty : IHasPrimaryKey, IBeatmapDifficultyInfo { /// /// The default value used for all difficulty settings except and . @@ -23,11 +23,11 @@ namespace osu.Game.Beatmaps private float? approachRate; - public BeatmapDifficulty() + public EFBeatmapDifficulty() { } - public BeatmapDifficulty(IBeatmapDifficultyInfo source) + public EFBeatmapDifficulty(IBeatmapDifficultyInfo source) { CopyFrom(source); } @@ -42,11 +42,11 @@ namespace osu.Game.Beatmaps public double SliderTickRate { get; set; } = 1; /// - /// Returns a shallow-clone of this . + /// Returns a shallow-clone of this . /// - public BeatmapDifficulty Clone() + public EFBeatmapDifficulty Clone() { - var diff = (BeatmapDifficulty)Activator.CreateInstance(GetType()); + var diff = (EFBeatmapDifficulty)Activator.CreateInstance(GetType()); CopyTo(diff); return diff; } @@ -62,7 +62,7 @@ namespace osu.Game.Beatmaps SliderTickRate = other.SliderTickRate; } - public virtual void CopyTo(BeatmapDifficulty other) + public virtual void CopyTo(EFBeatmapDifficulty other) { other.ApproachRate = ApproachRate; other.DrainRate = DrainRate; diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/EFBeatmapInfo.cs similarity index 76% rename from osu.Game/Beatmaps/BeatmapInfo.cs rename to osu.Game/Beatmaps/EFBeatmapInfo.cs index 4175d7ff6b..2336eeb456 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/EFBeatmapInfo.cs @@ -16,7 +16,7 @@ namespace osu.Game.Beatmaps { [ExcludeFromDynamicCompile] [Serializable] - public class BeatmapInfo : IEquatable, IHasPrimaryKey, IBeatmapInfo + public class EFBeatmapInfo : IEquatable, IHasPrimaryKey, IBeatmapInfo { public int ID { get; set; } @@ -40,14 +40,14 @@ namespace osu.Game.Beatmaps public BeatmapOnlineStatus Status { get; set; } = BeatmapOnlineStatus.None; [Required] - public BeatmapSetInfo BeatmapSet { get; set; } + public EFBeatmapSetInfo BeatmapSet { get; set; } - public BeatmapMetadata Metadata { get; set; } + public EFBeatmapMetadata Metadata { get; set; } [JsonIgnore] public int BaseDifficultyID { get; set; } - public BeatmapDifficulty BaseDifficulty { get; set; } + public EFBeatmapDifficulty BaseDifficulty { get; set; } [NotMapped] public APIBeatmap OnlineInfo { get; set; } @@ -86,7 +86,7 @@ namespace osu.Game.Beatmaps public int RulesetID { get; set; } - public RulesetInfo Ruleset { get; set; } + public EFRulesetInfo Ruleset { get; set; } public bool LetterboxInBreaks { get; set; } public bool WidescreenStoryboard { get; set; } @@ -131,7 +131,7 @@ namespace osu.Game.Beatmaps public override string ToString() => this.GetDisplayTitle(); - public bool Equals(BeatmapInfo other) + public bool Equals(EFBeatmapInfo other) { if (ReferenceEquals(this, other)) return true; if (other == null) return false; @@ -142,20 +142,20 @@ namespace osu.Game.Beatmaps return false; } - public bool Equals(IBeatmapInfo other) => other is BeatmapInfo b && Equals(b); + public bool Equals(IBeatmapInfo other) => other is EFBeatmapInfo b && Equals(b); - public bool AudioEquals(BeatmapInfo other) => other != null && BeatmapSet != null && other.BeatmapSet != null && - BeatmapSet.Hash == other.BeatmapSet.Hash && - (Metadata ?? BeatmapSet.Metadata).AudioFile == (other.Metadata ?? other.BeatmapSet.Metadata).AudioFile; + public bool AudioEquals(EFBeatmapInfo other) => other != null && BeatmapSet != null && other.BeatmapSet != null && + BeatmapSet.Hash == other.BeatmapSet.Hash && + (Metadata ?? BeatmapSet.Metadata).AudioFile == (other.Metadata ?? other.BeatmapSet.Metadata).AudioFile; - public bool BackgroundEquals(BeatmapInfo other) => other != null && BeatmapSet != null && other.BeatmapSet != null && - BeatmapSet.Hash == other.BeatmapSet.Hash && - (Metadata ?? BeatmapSet.Metadata).BackgroundFile == (other.Metadata ?? other.BeatmapSet.Metadata).BackgroundFile; + public bool BackgroundEquals(EFBeatmapInfo other) => other != null && BeatmapSet != null && other.BeatmapSet != null && + BeatmapSet.Hash == other.BeatmapSet.Hash && + (Metadata ?? BeatmapSet.Metadata).BackgroundFile == (other.Metadata ?? other.BeatmapSet.Metadata).BackgroundFile; /// - /// Returns a shallow-clone of this . + /// Returns a shallow-clone of this . /// - public BeatmapInfo Clone() => (BeatmapInfo)MemberwiseClone(); + public EFBeatmapInfo Clone() => (EFBeatmapInfo)MemberwiseClone(); #region Implementation of IHasOnlineID @@ -166,7 +166,7 @@ namespace osu.Game.Beatmaps #region Implementation of IBeatmapInfo [JsonIgnore] - IBeatmapMetadataInfo IBeatmapInfo.Metadata => Metadata ?? BeatmapSet?.Metadata ?? new BeatmapMetadata(); + IBeatmapMetadataInfo IBeatmapInfo.Metadata => Metadata ?? BeatmapSet?.Metadata ?? new EFBeatmapMetadata(); [JsonIgnore] IBeatmapDifficultyInfo IBeatmapInfo.Difficulty => BaseDifficulty; diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/EFBeatmapMetadata.cs similarity index 86% rename from osu.Game/Beatmaps/BeatmapMetadata.cs rename to osu.Game/Beatmaps/EFBeatmapMetadata.cs index 5da0264893..6c192088dc 100644 --- a/osu.Game/Beatmaps/BeatmapMetadata.cs +++ b/osu.Game/Beatmaps/EFBeatmapMetadata.cs @@ -16,7 +16,7 @@ namespace osu.Game.Beatmaps { [ExcludeFromDynamicCompile] [Serializable] - public class BeatmapMetadata : IEquatable, IHasPrimaryKey, IBeatmapMetadataInfo + public class EFBeatmapMetadata : IEquatable, IHasPrimaryKey, IBeatmapMetadataInfo { public int ID { get; set; } @@ -33,10 +33,10 @@ namespace osu.Game.Beatmaps public string ArtistUnicode { get; set; } = string.Empty; [JsonIgnore] - public List Beatmaps { get; set; } = new List(); + public List Beatmaps { get; set; } = new List(); [JsonIgnore] - public List BeatmapSets { get; set; } = new List(); + public List BeatmapSets { get; set; } = new List(); /// /// The author of the beatmaps in this set. @@ -81,7 +81,7 @@ namespace osu.Game.Beatmaps public string BackgroundFile { get; set; } = string.Empty; - public bool Equals(BeatmapMetadata other) => ((IBeatmapMetadataInfo)this).Equals(other); + public bool Equals(EFBeatmapMetadata other) => ((IBeatmapMetadataInfo)this).Equals(other); public override string ToString() => this.GetDisplayTitle(); diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/EFBeatmapSetInfo.cs similarity index 90% rename from osu.Game/Beatmaps/BeatmapSetInfo.cs rename to osu.Game/Beatmaps/EFBeatmapSetInfo.cs index a3a8f8555f..c0310f5a76 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/EFBeatmapSetInfo.cs @@ -14,7 +14,7 @@ using osu.Game.Extensions; namespace osu.Game.Beatmaps { [ExcludeFromDynamicCompile] - public class BeatmapSetInfo : IHasPrimaryKey, IHasFiles, ISoftDelete, IEquatable, IBeatmapSetInfo + public class EFBeatmapSetInfo : IHasPrimaryKey, IHasFiles, ISoftDelete, IEquatable, IBeatmapSetInfo { public int ID { get; set; } @@ -31,7 +31,7 @@ namespace osu.Game.Beatmaps public DateTimeOffset DateAdded { get; set; } - public BeatmapMetadata Metadata { get; set; } + public EFBeatmapMetadata Metadata { get; set; } [NotNull] public List Beatmaps { get; } = new List(); @@ -74,7 +74,7 @@ namespace osu.Game.Beatmaps public bool Protected { get; set; } - public bool Equals(BeatmapSetInfo other) + public bool Equals(EFBeatmapSetInfo other) { if (ReferenceEquals(this, other)) return true; if (other == null) return false; @@ -85,7 +85,7 @@ namespace osu.Game.Beatmaps return false; } - public bool Equals(IBeatmapSetInfo other) => other is BeatmapSetInfo b && Equals(b); + public bool Equals(IBeatmapSetInfo other) => other is EFBeatmapSetInfo b && Equals(b); #region Implementation of IHasOnlineID diff --git a/osu.Game/Database/OsuDbContext.cs b/osu.Game/Database/OsuDbContext.cs index 7cd9ae2885..9f0bae4a66 100644 --- a/osu.Game/Database/OsuDbContext.cs +++ b/osu.Game/Database/OsuDbContext.cs @@ -19,12 +19,12 @@ namespace osu.Game.Database { public class OsuDbContext : DbContext { - public DbSet BeatmapInfo { get; set; } - public DbSet BeatmapDifficulty { get; set; } - public DbSet BeatmapMetadata { get; set; } - public DbSet BeatmapSetInfo { get; set; } + public DbSet EFBeatmapInfo { get; set; } + public DbSet BeatmapDifficulty { get; set; } + public DbSet BeatmapMetadata { get; set; } + public DbSet EFBeatmapSetInfo { get; set; } public DbSet FileInfo { get; set; } - public DbSet RulesetInfo { get; set; } + public DbSet RulesetInfo { get; set; } public DbSet SkinInfo { get; set; } public DbSet ScoreInfo { get; set; } @@ -125,13 +125,13 @@ namespace osu.Game.Database { base.OnModelCreating(modelBuilder); - modelBuilder.Entity().HasIndex(b => b.OnlineID).IsUnique(); - modelBuilder.Entity().HasIndex(b => b.MD5Hash); - modelBuilder.Entity().HasIndex(b => b.Hash); + modelBuilder.Entity().HasIndex(b => b.OnlineID).IsUnique(); + modelBuilder.Entity().HasIndex(b => b.MD5Hash); + modelBuilder.Entity().HasIndex(b => b.Hash); - modelBuilder.Entity().HasIndex(b => b.OnlineID).IsUnique(); - modelBuilder.Entity().HasIndex(b => b.DeletePending); - modelBuilder.Entity().HasIndex(b => b.Hash).IsUnique(); + modelBuilder.Entity().HasIndex(b => b.OnlineID).IsUnique(); + modelBuilder.Entity().HasIndex(b => b.DeletePending); + modelBuilder.Entity().HasIndex(b => b.Hash).IsUnique(); modelBuilder.Entity().HasIndex(b => b.Hash).IsUnique(); modelBuilder.Entity().HasIndex(b => b.DeletePending); @@ -142,10 +142,10 @@ namespace osu.Game.Database modelBuilder.Entity().HasIndex(b => b.Hash).IsUnique(); modelBuilder.Entity().HasIndex(b => b.ReferenceCount); - modelBuilder.Entity().HasIndex(b => b.Available); - modelBuilder.Entity().HasIndex(b => b.ShortName).IsUnique(); + modelBuilder.Entity().HasIndex(b => b.Available); + modelBuilder.Entity().HasIndex(b => b.ShortName).IsUnique(); - modelBuilder.Entity().HasOne(b => b.BaseDifficulty); + modelBuilder.Entity().HasOne(b => b.BaseDifficulty); modelBuilder.Entity().HasIndex(b => b.OnlineID).IsUnique(); } diff --git a/osu.Game/Rulesets/RulesetInfo.cs b/osu.Game/Rulesets/EFRulesetInfo.cs similarity index 86% rename from osu.Game/Rulesets/RulesetInfo.cs rename to osu.Game/Rulesets/EFRulesetInfo.cs index d018cc4194..36e2add557 100644 --- a/osu.Game/Rulesets/RulesetInfo.cs +++ b/osu.Game/Rulesets/EFRulesetInfo.cs @@ -10,7 +10,7 @@ using osu.Framework.Testing; namespace osu.Game.Rulesets { [ExcludeFromDynamicCompile] - public sealed class RulesetInfo : IEquatable, IRulesetInfo + public sealed class EFRulesetInfo : IEquatable, IRulesetInfo { public int? ID { get; set; } @@ -45,9 +45,9 @@ namespace osu.Game.Rulesets return ruleset; } - public bool Equals(RulesetInfo other) => other != null && ID == other.ID && Available == other.Available && Name == other.Name && InstantiationInfo == other.InstantiationInfo; + public bool Equals(EFRulesetInfo other) => other != null && ID == other.ID && Available == other.Available && Name == other.Name && InstantiationInfo == other.InstantiationInfo; - public override bool Equals(object obj) => obj is RulesetInfo rulesetInfo && Equals(rulesetInfo); + public override bool Equals(object obj) => obj is EFRulesetInfo rulesetInfo && Equals(rulesetInfo); public bool Equals(IRulesetInfo other) => other is RulesetInfo b && Equals(b); diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index 44d75f265c..0da109ae7c 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -789,7 +789,7 @@ See the LICENCE file in the repository root for full licence text. <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - True + True True True True From 618903c2178b0ce4ae322de5f417938dc35f97b5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Nov 2021 19:07:21 +0900 Subject: [PATCH 166/996] Rename realm to become imposter classes --- .../Database/BeatmapImporterTests.cs | 64 +++++++++---------- osu.Game.Tests/Database/GeneralUsageTests.cs | 4 +- osu.Game.Tests/Database/RealmLiveTests.cs | 46 ++++++------- osu.Game.Tests/Database/RealmTest.cs | 20 +++--- osu.Game.Tests/Database/RulesetStoreTests.cs | 6 +- .../BeatmapDifficulty.cs} | 13 ++-- .../BeatmapInfo.cs} | 26 ++++---- .../BeatmapMetadata.cs} | 6 +- .../BeatmapSetInfo.cs} | 14 ++-- osu.Game/Database/RealmContextFactory.cs | 13 ++-- .../RulesetInfo.cs} | 17 +++-- osu.Game/Stores/BeatmapImporter.cs | 40 ++++++------ osu.Game/Stores/RealmRulesetStore.cs | 11 ++-- 13 files changed, 140 insertions(+), 140 deletions(-) rename osu.Game/{Models/RealmBeatmapDifficulty.cs => Beatmaps/BeatmapDifficulty.cs} (76%) rename osu.Game/{Models/RealmBeatmap.cs => Beatmaps/BeatmapInfo.cs} (80%) rename osu.Game/{Models/RealmBeatmapMetadata.cs => Beatmaps/BeatmapMetadata.cs} (91%) rename osu.Game/{Models/RealmBeatmapSet.cs => Beatmaps/BeatmapSetInfo.cs} (84%) rename osu.Game/{Models/RealmRuleset.cs => Rulesets/RulesetInfo.cs} (74%) diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index e47e24021f..37c2007681 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -19,6 +19,7 @@ using osu.Game.Extensions; using osu.Game.IO.Archives; using osu.Game.Models; using osu.Game.Overlays.Notifications; +using osu.Game.Rulesets; using osu.Game.Stores; using osu.Game.Tests.Resources; using Realms; @@ -42,25 +43,25 @@ namespace osu.Game.Tests.Database using (var importer = new BeatmapImporter(realmFactory, storage)) using (new RealmRulesetStore(realmFactory, storage)) { - ILive? imported; + ILive? imported; using (var reader = new ZipArchiveReader(TestResources.GetTestBeatmapStream())) imported = await importer.Import(reader); - Assert.AreEqual(1, realmFactory.Context.All().Count()); + Assert.AreEqual(1, realmFactory.Context.All().Count()); Assert.NotNull(imported); Debug.Assert(imported != null); imported.PerformWrite(s => s.DeletePending = true); - Assert.AreEqual(1, realmFactory.Context.All().Count(s => s.DeletePending)); + Assert.AreEqual(1, realmFactory.Context.All().Count(s => s.DeletePending)); } }); Logger.Log("Running with no work to purge pending deletions"); - RunTestWithRealm((realmFactory, _) => { Assert.AreEqual(0, realmFactory.Context.All().Count()); }); + RunTestWithRealm((realmFactory, _) => { Assert.AreEqual(0, realmFactory.Context.All().Count()); }); } [Test] @@ -117,7 +118,7 @@ namespace osu.Game.Tests.Database string? tempPath = TestResources.GetTestBeatmapForImport(); - ILive? importedSet; + ILive? importedSet; using (var stream = File.OpenRead(tempPath)) { @@ -131,7 +132,7 @@ namespace osu.Game.Tests.Database Assert.IsTrue(File.Exists(tempPath), "Stream source file somehow went missing"); File.Delete(tempPath); - var imported = realmFactory.Context.All().First(beatmapSet => beatmapSet.ID == importedSet.ID); + var imported = realmFactory.Context.All().First(beatmapSet => beatmapSet.ID == importedSet.ID); deleteBeatmapSet(imported, realmFactory.Context); }); @@ -556,7 +557,7 @@ namespace osu.Game.Tests.Database using var importer = new BeatmapImporter(realmFactory, storage); using var store = new RealmRulesetStore(realmFactory, storage); - var metadata = new RealmBeatmapMetadata + var metadata = new BeatmapMetadata { Artist = "SomeArtist", Author = @@ -565,18 +566,18 @@ namespace osu.Game.Tests.Database } }; - var ruleset = realmFactory.Context.All().First(); + var ruleset = realmFactory.Context.All().First(); - var toImport = new RealmBeatmapSet + var toImport = new BeatmapSetInfo { OnlineID = 1, Beatmaps = { - new RealmBeatmap(ruleset, new RealmBeatmapDifficulty(), metadata) + new BeatmapInfo(ruleset, new BeatmapDifficulty(), metadata) { OnlineID = 2, }, - new RealmBeatmap(ruleset, new RealmBeatmapDifficulty(), metadata) + new BeatmapInfo(ruleset, new BeatmapDifficulty(), metadata) { OnlineID = 2, Status = BeatmapOnlineStatus.Loved, @@ -752,18 +753,18 @@ namespace osu.Game.Tests.Database await importer.Import(temp); // Update via the beatmap, not the beatmap info, to ensure correct linking - RealmBeatmapSet setToUpdate = realmFactory.Context.All().First(); + BeatmapSetInfo setToUpdate = realmFactory.Context.All().First(); var beatmapToUpdate = setToUpdate.Beatmaps.First(); realmFactory.Context.Write(() => beatmapToUpdate.DifficultyName = "updated"); - RealmBeatmap updatedInfo = realmFactory.Context.All().First(b => b.ID == beatmapToUpdate.ID); + BeatmapInfo updatedInfo = realmFactory.Context.All().First(b => b.ID == beatmapToUpdate.ID); Assert.That(updatedInfo.DifficultyName, Is.EqualTo("updated")); }); } - public static async Task LoadQuickOszIntoOsu(BeatmapImporter importer, Realm realm) + public static async Task LoadQuickOszIntoOsu(BeatmapImporter importer, Realm realm) { string? temp = TestResources.GetQuickTestBeatmapForImport(); @@ -775,10 +776,10 @@ namespace osu.Game.Tests.Database waitForOrAssert(() => !File.Exists(temp), "Temporary file still exists after standard import", 5000); - return realm.All().FirstOrDefault(beatmapSet => beatmapSet.ID == importedSet!.ID); + return realm.All().FirstOrDefault(beatmapSet => beatmapSet.ID == importedSet!.ID); } - public static async Task LoadOszIntoStore(BeatmapImporter importer, Realm realm, string? path = null, bool virtualTrack = false) + public static async Task LoadOszIntoStore(BeatmapImporter importer, Realm realm, string? path = null, bool virtualTrack = false) { string? temp = path ?? TestResources.GetTestBeatmapForImport(virtualTrack); @@ -791,20 +792,20 @@ namespace osu.Game.Tests.Database waitForOrAssert(() => !File.Exists(temp), "Temporary file still exists after standard import", 5000); - return realm.All().First(beatmapSet => beatmapSet.ID == importedSet.ID); + return realm.All().First(beatmapSet => beatmapSet.ID == importedSet.ID); } - private void deleteBeatmapSet(RealmBeatmapSet imported, Realm realm) + private void deleteBeatmapSet(BeatmapSetInfo imported, Realm realm) { realm.Write(() => imported.DeletePending = true); checkBeatmapSetCount(realm, 0); checkBeatmapSetCount(realm, 1, true); - Assert.IsTrue(realm.All().First(_ => true).DeletePending); + Assert.IsTrue(realm.All().First(_ => true).DeletePending); } - private static Task createScoreForBeatmap(Realm realm, RealmBeatmap beatmap) + private static Task createScoreForBeatmap(Realm realm, BeatmapInfo beatmap) { // TODO: reimplement when we have score support in realm. // return ImportScoreTest.LoadScoreIntoOsu(osu, new ScoreInfo @@ -820,8 +821,8 @@ namespace osu.Game.Tests.Database private static void checkBeatmapSetCount(Realm realm, int expected, bool includeDeletePending = false) { Assert.AreEqual(expected, includeDeletePending - ? realm.All().Count() - : realm.All().Count(s => !s.DeletePending)); + ? realm.All().Count() + : realm.All().Count(s => !s.DeletePending)); } private static string hashFile(string filename) @@ -832,7 +833,7 @@ namespace osu.Game.Tests.Database private static void checkBeatmapCount(Realm realm, int expected) { - Assert.AreEqual(expected, realm.All().Where(_ => true).ToList().Count); + Assert.AreEqual(expected, realm.All().Where(_ => true).ToList().Count); } private static void checkSingleReferencedFileCount(Realm realm, int expected) @@ -850,24 +851,23 @@ namespace osu.Game.Tests.Database private static void ensureLoaded(Realm realm, int timeout = 60000) { - IQueryable? resultSets = null; + IQueryable? resultSets = null; waitForOrAssert(() => - { - realm.Refresh(); - return (resultSets = realm.All().Where(s => !s.DeletePending && s.OnlineID == 241526)).Any(); - }, - @"BeatmapSet did not import to the database in allocated time.", timeout); + { + realm.Refresh(); + return (resultSets = realm.All().Where(s => !s.DeletePending && s.OnlineID == 241526)).Any(); + }, @"BeatmapSet did not import to the database in allocated time.", timeout); // ensure we were stored to beatmap database backing... Assert.IsTrue(resultSets?.Count() == 1, $@"Incorrect result count found ({resultSets?.Count()} but should be 1)."); - IEnumerable queryBeatmapSets() => realm.All().Where(s => !s.DeletePending && s.OnlineID == 241526); + IEnumerable queryBeatmapSets() => realm.All().Where(s => !s.DeletePending && s.OnlineID == 241526); var set = queryBeatmapSets().First(); // ReSharper disable once PossibleUnintendedReferenceComparison - IEnumerable queryBeatmaps() => realm.All().Where(s => s.BeatmapSet != null && s.BeatmapSet == set); + IEnumerable queryBeatmaps() => realm.All().Where(s => s.BeatmapSet != null && s.BeatmapSet == set); Assert.AreEqual(12, queryBeatmaps().Count(), @"Beatmap count was not correct"); Assert.AreEqual(1, queryBeatmapSets().Count(), @"Beatmapset count was not correct"); @@ -880,7 +880,7 @@ namespace osu.Game.Tests.Database countBeatmaps = queryBeatmaps().Count(), $@"Incorrect database beatmap count post-import ({countBeatmaps} but should be {countBeatmapSetBeatmaps})."); - foreach (RealmBeatmap b in set.Beatmaps) + foreach (BeatmapInfo b in set.Beatmaps) Assert.IsTrue(set.Beatmaps.Any(c => c.OnlineID == b.OnlineID)); Assert.IsTrue(set.Beatmaps.Count > 0); } diff --git a/osu.Game.Tests/Database/GeneralUsageTests.cs b/osu.Game.Tests/Database/GeneralUsageTests.cs index 2285b22a3a..0961ad71e4 100644 --- a/osu.Game.Tests/Database/GeneralUsageTests.cs +++ b/osu.Game.Tests/Database/GeneralUsageTests.cs @@ -5,8 +5,8 @@ using System; using System.Threading; using System.Threading.Tasks; using NUnit.Framework; +using osu.Game.Beatmaps; using osu.Game.Database; -using osu.Game.Models; #nullable enable @@ -48,7 +48,7 @@ namespace osu.Game.Tests.Database using (var context = realmFactory.CreateContext()) { - var subscription = context.All().QueryAsyncWithNotifications((sender, changes, error) => + var subscription = context.All().QueryAsyncWithNotifications((sender, changes, error) => { using (realmFactory.CreateContext()) { diff --git a/osu.Game.Tests/Database/RealmLiveTests.cs b/osu.Game.Tests/Database/RealmLiveTests.cs index 9432a56741..187fcd3ca7 100644 --- a/osu.Game.Tests/Database/RealmLiveTests.cs +++ b/osu.Game.Tests/Database/RealmLiveTests.cs @@ -8,8 +8,8 @@ using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Extensions; using osu.Framework.Testing; +using osu.Game.Beatmaps; using osu.Game.Database; -using osu.Game.Models; using Realms; #nullable enable @@ -23,9 +23,9 @@ namespace osu.Game.Tests.Database { RunTestWithRealm((realmFactory, _) => { - ILive beatmap = realmFactory.CreateContext().Write(r => r.Add(new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata()))).ToLive(realmFactory); + ILive beatmap = realmFactory.CreateContext().Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()))).ToLive(realmFactory); - ILive beatmap2 = realmFactory.CreateContext().All().First().ToLive(realmFactory); + ILive beatmap2 = realmFactory.CreateContext().All().First().ToLive(realmFactory); Assert.AreEqual(beatmap, beatmap2); }); @@ -36,9 +36,9 @@ namespace osu.Game.Tests.Database { RunTestWithRealm((realmFactory, storage) => { - var beatmap = new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata()); + var beatmap = new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()); - ILive liveBeatmap; + ILive liveBeatmap; using (var context = realmFactory.CreateContext()) { @@ -63,7 +63,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealm((realmFactory, _) => { - var beatmap = new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata()); + var beatmap = new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()); var liveBeatmap = beatmap.ToLive(realmFactory); @@ -77,7 +77,7 @@ namespace osu.Game.Tests.Database [Test] public void TestAccessNonManaged() { - var beatmap = new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata()); + var beatmap = new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()); var liveBeatmap = beatmap.ToLiveUnmanaged(); Assert.IsFalse(beatmap.Hidden); @@ -96,12 +96,12 @@ namespace osu.Game.Tests.Database { RunTestWithRealm((realmFactory, _) => { - ILive? liveBeatmap = null; + ILive? liveBeatmap = null; Task.Factory.StartNew(() => { using (var threadContext = realmFactory.CreateContext()) { - var beatmap = threadContext.Write(r => r.Add(new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata()))); + var beatmap = threadContext.Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()))); liveBeatmap = beatmap.ToLive(realmFactory); } @@ -125,12 +125,12 @@ namespace osu.Game.Tests.Database { RunTestWithRealm((realmFactory, _) => { - ILive? liveBeatmap = null; + ILive? liveBeatmap = null; Task.Factory.StartNew(() => { using (var threadContext = realmFactory.CreateContext()) { - var beatmap = threadContext.Write(r => r.Add(new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata()))); + var beatmap = threadContext.Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()))); liveBeatmap = beatmap.ToLive(realmFactory); } @@ -151,7 +151,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealm((realmFactory, _) => { - var beatmap = new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata()); + var beatmap = new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()); var liveBeatmap = beatmap.ToLive(realmFactory); Assert.DoesNotThrow(() => @@ -166,13 +166,13 @@ namespace osu.Game.Tests.Database { RunTestWithRealm((realmFactory, _) => { - ILive? liveBeatmap = null; + ILive? liveBeatmap = null; Task.Factory.StartNew(() => { using (var threadContext = realmFactory.CreateContext()) { - var beatmap = threadContext.Write(r => r.Add(new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata()))); + var beatmap = threadContext.Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()))); liveBeatmap = beatmap.ToLive(realmFactory); } @@ -205,12 +205,12 @@ namespace osu.Game.Tests.Database { RunTestWithRealm((realmFactory, _) => { - ILive? liveBeatmap = null; + ILive? liveBeatmap = null; Task.Factory.StartNew(() => { using (var threadContext = realmFactory.CreateContext()) { - var beatmap = threadContext.Write(r => r.Add(new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata()))); + var beatmap = threadContext.Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()))); liveBeatmap = beatmap.ToLive(realmFactory); } @@ -237,19 +237,19 @@ namespace osu.Game.Tests.Database using (var updateThreadContext = realmFactory.CreateContext()) { - updateThreadContext.All().QueryAsyncWithNotifications(gotChange); - ILive? liveBeatmap = null; + updateThreadContext.All().QueryAsyncWithNotifications(gotChange); + ILive? liveBeatmap = null; Task.Factory.StartNew(() => { using (var threadContext = realmFactory.CreateContext()) { var ruleset = CreateRuleset(); - var beatmap = threadContext.Write(r => r.Add(new RealmBeatmap(ruleset, new RealmBeatmapDifficulty(), new RealmBeatmapMetadata()))); + var beatmap = threadContext.Write(r => r.Add(new BeatmapInfo(ruleset, new BeatmapDifficulty(), new BeatmapMetadata()))); // add a second beatmap to ensure that a full refresh occurs below. // not just a refresh from the resolved Live. - threadContext.Write(r => r.Add(new RealmBeatmap(ruleset, new RealmBeatmapDifficulty(), new RealmBeatmapMetadata()))); + threadContext.Write(r => r.Add(new BeatmapInfo(ruleset, new BeatmapDifficulty(), new BeatmapMetadata()))); liveBeatmap = beatmap.ToLive(realmFactory); } @@ -258,14 +258,14 @@ namespace osu.Game.Tests.Database Debug.Assert(liveBeatmap != null); // not yet seen by main context - Assert.AreEqual(0, updateThreadContext.All().Count()); + Assert.AreEqual(0, updateThreadContext.All().Count()); Assert.AreEqual(0, changesTriggered); liveBeatmap.PerformRead(resolved => { // retrieval causes an implicit refresh. even changes that aren't related to the retrieval are fired at this point. // ReSharper disable once AccessToDisposedClosure - Assert.AreEqual(2, updateThreadContext.All().Count()); + Assert.AreEqual(2, updateThreadContext.All().Count()); Assert.AreEqual(1, changesTriggered); // can access properties without a crash. @@ -280,7 +280,7 @@ namespace osu.Game.Tests.Database }); } - void gotChange(IRealmCollection sender, ChangeSet changes, Exception error) + void gotChange(IRealmCollection sender, ChangeSet changes, Exception error) { changesTriggered++; } diff --git a/osu.Game.Tests/Database/RealmTest.cs b/osu.Game.Tests/Database/RealmTest.cs index 4e67f09dca..0cee165f75 100644 --- a/osu.Game.Tests/Database/RealmTest.cs +++ b/osu.Game.Tests/Database/RealmTest.cs @@ -9,9 +9,11 @@ using osu.Framework.Extensions; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Testing; +using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.IO; using osu.Game.Models; +using osu.Game.Rulesets; #nullable enable @@ -74,24 +76,24 @@ namespace osu.Game.Tests.Database } } - protected static RealmBeatmapSet CreateBeatmapSet(RealmRuleset ruleset) + protected static BeatmapSetInfo CreateBeatmapSet(RulesetInfo ruleset) { RealmFile createRealmFile() => new RealmFile { Hash = Guid.NewGuid().ToString().ComputeSHA2Hash() }; - var metadata = new RealmBeatmapMetadata + var metadata = new BeatmapMetadata { Title = "My Love", Artist = "Kuba Oms" }; - var beatmapSet = new RealmBeatmapSet + var beatmapSet = new BeatmapSetInfo { Beatmaps = { - new RealmBeatmap(ruleset, new RealmBeatmapDifficulty(), metadata) { DifficultyName = "Easy", }, - new RealmBeatmap(ruleset, new RealmBeatmapDifficulty(), metadata) { DifficultyName = "Normal", }, - new RealmBeatmap(ruleset, new RealmBeatmapDifficulty(), metadata) { DifficultyName = "Hard", }, - new RealmBeatmap(ruleset, new RealmBeatmapDifficulty(), metadata) { DifficultyName = "Insane", } + new BeatmapInfo(ruleset, new BeatmapDifficulty(), metadata) { DifficultyName = "Easy", }, + new BeatmapInfo(ruleset, new BeatmapDifficulty(), metadata) { DifficultyName = "Normal", }, + new BeatmapInfo(ruleset, new BeatmapDifficulty(), metadata) { DifficultyName = "Hard", }, + new BeatmapInfo(ruleset, new BeatmapDifficulty(), metadata) { DifficultyName = "Insane", } }, Files = { @@ -111,8 +113,8 @@ namespace osu.Game.Tests.Database return beatmapSet; } - protected static RealmRuleset CreateRuleset() => - new RealmRuleset(0, "osu!", "osu", true); + protected static RulesetInfo CreateRuleset() => + new RulesetInfo(0, "osu!", "osu", true); private class RealmTestGame : Framework.Game { diff --git a/osu.Game.Tests/Database/RulesetStoreTests.cs b/osu.Game.Tests/Database/RulesetStoreTests.cs index cc7e8a0c97..d2de31d11a 100644 --- a/osu.Game.Tests/Database/RulesetStoreTests.cs +++ b/osu.Game.Tests/Database/RulesetStoreTests.cs @@ -3,7 +3,7 @@ using System.Linq; using NUnit.Framework; -using osu.Game.Models; +using osu.Game.Rulesets; using osu.Game.Stores; namespace osu.Game.Tests.Database @@ -18,7 +18,7 @@ namespace osu.Game.Tests.Database var rulesets = new RealmRulesetStore(realmFactory, storage); Assert.AreEqual(4, rulesets.AvailableRulesets.Count()); - Assert.AreEqual(4, realmFactory.Context.All().Count()); + Assert.AreEqual(4, realmFactory.Context.All().Count()); }); } @@ -34,7 +34,7 @@ namespace osu.Game.Tests.Database Assert.AreEqual(4, rulesets2.AvailableRulesets.Count()); Assert.AreEqual(rulesets.AvailableRulesets.First(), rulesets2.AvailableRulesets.First()); - Assert.AreEqual(4, realmFactory.Context.All().Count()); + Assert.AreEqual(4, realmFactory.Context.All().Count()); }); } diff --git a/osu.Game/Models/RealmBeatmapDifficulty.cs b/osu.Game/Beatmaps/BeatmapDifficulty.cs similarity index 76% rename from osu.Game/Models/RealmBeatmapDifficulty.cs rename to osu.Game/Beatmaps/BeatmapDifficulty.cs index 3c1dad69e4..6425c7b291 100644 --- a/osu.Game/Models/RealmBeatmapDifficulty.cs +++ b/osu.Game/Beatmaps/BeatmapDifficulty.cs @@ -2,16 +2,15 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Testing; -using osu.Game.Beatmaps; using Realms; #nullable enable -namespace osu.Game.Models +namespace osu.Game.Beatmaps { [ExcludeFromDynamicCompile] [MapTo("BeatmapDifficulty")] - public class RealmBeatmapDifficulty : EmbeddedObject, IBeatmapDifficultyInfo + public class BeatmapDifficulty : EmbeddedObject, IBeatmapDifficultyInfo { public float DrainRate { get; set; } = IBeatmapDifficultyInfo.DEFAULT_DIFFICULTY; public float CircleSize { get; set; } = IBeatmapDifficultyInfo.DEFAULT_DIFFICULTY; @@ -22,16 +21,16 @@ namespace osu.Game.Models public double SliderTickRate { get; set; } = 1; /// - /// Returns a shallow-clone of this . + /// Returns a shallow-clone of this . /// - public RealmBeatmapDifficulty Clone() + public BeatmapDifficulty Clone() { - var diff = new RealmBeatmapDifficulty(); + var diff = new BeatmapDifficulty(); CopyTo(diff); return diff; } - public void CopyTo(RealmBeatmapDifficulty difficulty) + public void CopyTo(BeatmapDifficulty difficulty) { difficulty.ApproachRate = ApproachRate; difficulty.DrainRate = DrainRate; diff --git a/osu.Game/Models/RealmBeatmap.cs b/osu.Game/Beatmaps/BeatmapInfo.cs similarity index 80% rename from osu.Game/Models/RealmBeatmap.cs rename to osu.Game/Beatmaps/BeatmapInfo.cs index 8e132687f7..b000862457 100644 --- a/osu.Game/Models/RealmBeatmap.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -6,14 +6,14 @@ using System.Linq; using JetBrains.Annotations; using Newtonsoft.Json; using osu.Framework.Testing; -using osu.Game.Beatmaps; using osu.Game.Database; +using osu.Game.Models; using osu.Game.Rulesets; using Realms; #nullable enable -namespace osu.Game.Models +namespace osu.Game.Beatmaps { /// /// A single beatmap difficulty. @@ -21,20 +21,20 @@ namespace osu.Game.Models [ExcludeFromDynamicCompile] [Serializable] [MapTo("Beatmap")] - public class RealmBeatmap : RealmObject, IHasGuidPrimaryKey, IBeatmapInfo, IEquatable + public class BeatmapInfo : RealmObject, IHasGuidPrimaryKey, IBeatmapInfo, IEquatable { [PrimaryKey] public Guid ID { get; set; } = Guid.NewGuid(); public string DifficultyName { get; set; } = string.Empty; - public RealmRuleset Ruleset { get; set; } = null!; + public RulesetInfo Ruleset { get; set; } = null!; - public RealmBeatmapDifficulty Difficulty { get; set; } = null!; + public BeatmapDifficulty Difficulty { get; set; } = null!; - public RealmBeatmapMetadata Metadata { get; set; } = null!; + public BeatmapMetadata Metadata { get; set; } = null!; - public RealmBeatmapSet? BeatmapSet { get; set; } + public BeatmapSetInfo? BeatmapSet { get; set; } [Ignored] public RealmNamedFileUsage? File => BeatmapSet?.Files.First(f => f.File.Hash == Hash); @@ -64,7 +64,7 @@ namespace osu.Game.Models [JsonIgnore] public bool Hidden { get; set; } - public RealmBeatmap(RealmRuleset ruleset, RealmBeatmapDifficulty difficulty, RealmBeatmapMetadata metadata) + public BeatmapInfo(RulesetInfo ruleset, BeatmapDifficulty difficulty, BeatmapMetadata metadata) { Ruleset = ruleset; Difficulty = difficulty; @@ -72,7 +72,7 @@ namespace osu.Game.Models } [UsedImplicitly] - private RealmBeatmap() + private BeatmapInfo() { } @@ -102,7 +102,7 @@ namespace osu.Game.Models #endregion - public bool Equals(RealmBeatmap? other) + public bool Equals(BeatmapInfo? other) { if (ReferenceEquals(this, other)) return true; if (other == null) return false; @@ -110,15 +110,15 @@ namespace osu.Game.Models return ID == other.ID; } - public bool Equals(IBeatmapInfo? other) => other is RealmBeatmap b && Equals(b); + public bool Equals(IBeatmapInfo? other) => other is BeatmapInfo b && Equals(b); - public bool AudioEquals(RealmBeatmap? other) => other != null + public bool AudioEquals(BeatmapInfo? other) => other != null && BeatmapSet != null && other.BeatmapSet != null && BeatmapSet.Hash == other.BeatmapSet.Hash && Metadata.AudioFile == other.Metadata.AudioFile; - public bool BackgroundEquals(RealmBeatmap? other) => other != null + public bool BackgroundEquals(BeatmapInfo? other) => other != null && BeatmapSet != null && other.BeatmapSet != null && BeatmapSet.Hash == other.BeatmapSet.Hash diff --git a/osu.Game/Models/RealmBeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs similarity index 91% rename from osu.Game/Models/RealmBeatmapMetadata.cs rename to osu.Game/Beatmaps/BeatmapMetadata.cs index db1b09e6ad..387641db40 100644 --- a/osu.Game/Models/RealmBeatmapMetadata.cs +++ b/osu.Game/Beatmaps/BeatmapMetadata.cs @@ -4,18 +4,18 @@ using System; using Newtonsoft.Json; using osu.Framework.Testing; -using osu.Game.Beatmaps; +using osu.Game.Models; using osu.Game.Users; using Realms; #nullable enable -namespace osu.Game.Models +namespace osu.Game.Beatmaps { [ExcludeFromDynamicCompile] [Serializable] [MapTo("BeatmapMetadata")] - public class RealmBeatmapMetadata : RealmObject, IBeatmapMetadataInfo + public class BeatmapMetadata : RealmObject, IBeatmapMetadataInfo { public string Title { get; set; } = string.Empty; diff --git a/osu.Game/Models/RealmBeatmapSet.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs similarity index 84% rename from osu.Game/Models/RealmBeatmapSet.cs rename to osu.Game/Beatmaps/BeatmapSetInfo.cs index 3566ff5321..4aea0c6ce8 100644 --- a/osu.Game/Models/RealmBeatmapSet.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -5,18 +5,18 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Testing; -using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Extensions; +using osu.Game.Models; using Realms; #nullable enable -namespace osu.Game.Models +namespace osu.Game.Beatmaps { [ExcludeFromDynamicCompile] [MapTo("BeatmapSet")] - public class RealmBeatmapSet : RealmObject, IHasGuidPrimaryKey, IHasRealmFiles, ISoftDelete, IEquatable, IBeatmapSetInfo + public class BeatmapSetInfo : RealmObject, IHasGuidPrimaryKey, IHasRealmFiles, ISoftDelete, IEquatable, IBeatmapSetInfo { [PrimaryKey] public Guid ID { get; set; } = Guid.NewGuid(); @@ -26,9 +26,9 @@ namespace osu.Game.Models public DateTimeOffset DateAdded { get; set; } - public IBeatmapMetadataInfo Metadata => Beatmaps.FirstOrDefault()?.Metadata ?? new RealmBeatmapMetadata(); + public IBeatmapMetadataInfo Metadata => Beatmaps.FirstOrDefault()?.Metadata ?? new BeatmapMetadata(); - public IList Beatmaps { get; } = null!; + public IList Beatmaps { get; } = null!; public IList Files { get; } = null!; @@ -63,7 +63,7 @@ namespace osu.Game.Models /// The name of the file to get the storage path of. public string? GetPathForFile(string filename) => Files.SingleOrDefault(f => string.Equals(f.Filename, filename, StringComparison.OrdinalIgnoreCase))?.File.GetStoragePath(); - public bool Equals(RealmBeatmapSet? other) + public bool Equals(BeatmapSetInfo? other) { if (ReferenceEquals(this, other)) return true; if (other == null) return false; @@ -73,7 +73,7 @@ namespace osu.Game.Models public override string ToString() => Metadata.GetDisplayString(); - public bool Equals(IBeatmapSetInfo? other) => other is RealmBeatmapSet b && Equals(b); + public bool Equals(IBeatmapSetInfo? other) => other is BeatmapSetInfo b && Equals(b); IEnumerable IBeatmapSetInfo.Beatmaps => Beatmaps; IEnumerable IHasNamedFiles.Files => Files; diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 96c24837a1..946dda1f39 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -12,6 +12,7 @@ using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Statistics; using osu.Game.Configuration; +using osu.Game.Beatmaps; using osu.Game.Input.Bindings; using osu.Game.Models; using osu.Game.Skinning; @@ -109,7 +110,7 @@ namespace osu.Game.Database using (var realm = CreateContext()) using (var transaction = realm.BeginWrite()) { - var pendingDeleteSets = realm.All().Where(s => s.DeletePending); + var pendingDeleteSets = realm.All().Where(s => s.DeletePending); foreach (var s in pendingDeleteSets) { @@ -206,9 +207,9 @@ namespace osu.Game.Database switch (targetVersion) { case 7: - convertOnlineIDs(); - convertOnlineIDs(); - convertOnlineIDs(); + convertOnlineIDs(); + convertOnlineIDs(); + convertOnlineIDs(); void convertOnlineIDs() where T : RealmObject { @@ -253,14 +254,14 @@ namespace osu.Game.Database case 9: // Pretty pointless to do this as beatmaps aren't really loaded via realm yet, but oh well. - string metadataClassName = getMappedOrOriginalName(typeof(RealmBeatmapMetadata)); + string metadataClassName = getMappedOrOriginalName(typeof(BeatmapMetadata)); // May be coming from a version before `RealmBeatmapMetadata` existed. if (!migration.OldRealm.Schema.TryFindObjectSchema(metadataClassName, out _)) return; var oldMetadata = migration.OldRealm.DynamicApi.All(metadataClassName); - var newMetadata = migration.NewRealm.All(); + var newMetadata = migration.NewRealm.All(); int metadataCount = newMetadata.Count(); diff --git a/osu.Game/Models/RealmRuleset.cs b/osu.Game/Rulesets/RulesetInfo.cs similarity index 74% rename from osu.Game/Models/RealmRuleset.cs rename to osu.Game/Rulesets/RulesetInfo.cs index b959d0b4dc..91a3d181f8 100644 --- a/osu.Game/Models/RealmRuleset.cs +++ b/osu.Game/Rulesets/RulesetInfo.cs @@ -4,16 +4,15 @@ using System; using JetBrains.Annotations; using osu.Framework.Testing; -using osu.Game.Rulesets; using Realms; #nullable enable -namespace osu.Game.Models +namespace osu.Game.Rulesets { [ExcludeFromDynamicCompile] [MapTo("Ruleset")] - public class RealmRuleset : RealmObject, IEquatable, IRulesetInfo + public class RulesetInfo : RealmObject, IEquatable, IRulesetInfo { [PrimaryKey] public string ShortName { get; set; } = string.Empty; @@ -25,7 +24,7 @@ namespace osu.Game.Models public string InstantiationInfo { get; set; } = string.Empty; - public RealmRuleset(string shortName, string name, string instantiationInfo, int onlineID) + public RulesetInfo(string shortName, string name, string instantiationInfo, int onlineID) { ShortName = shortName; Name = name; @@ -34,11 +33,11 @@ namespace osu.Game.Models } [UsedImplicitly] - private RealmRuleset() + private RulesetInfo() { } - public RealmRuleset(int? onlineID, string name, string shortName, bool available) + public RulesetInfo(int? onlineID, string name, string shortName, bool available) { OnlineID = onlineID ?? -1; Name = name; @@ -48,13 +47,13 @@ namespace osu.Game.Models public bool Available { get; set; } - public bool Equals(RealmRuleset? other) => other != null && OnlineID == other.OnlineID && Available == other.Available && Name == other.Name && InstantiationInfo == other.InstantiationInfo; + public bool Equals(RulesetInfo? other) => other != null && OnlineID == other.OnlineID && Available == other.Available && Name == other.Name && InstantiationInfo == other.InstantiationInfo; - public bool Equals(IRulesetInfo? other) => other is RealmRuleset b && Equals(b); + public bool Equals(IRulesetInfo? other) => other is RulesetInfo b && Equals(b); public override string ToString() => Name; - public RealmRuleset Clone() => new RealmRuleset + public RulesetInfo Clone() => new RulesetInfo { OnlineID = OnlineID, Name = Name, diff --git a/osu.Game/Stores/BeatmapImporter.cs b/osu.Game/Stores/BeatmapImporter.cs index 8ab6941885..69c136b51c 100644 --- a/osu.Game/Stores/BeatmapImporter.cs +++ b/osu.Game/Stores/BeatmapImporter.cs @@ -34,7 +34,7 @@ namespace osu.Game.Stores /// Handles the storage and retrieval of Beatmaps/WorkingBeatmaps. /// [ExcludeFromDynamicCompile] - public class BeatmapImporter : RealmArchiveModelImporter, IDisposable + public class BeatmapImporter : RealmArchiveModelImporter, IDisposable { public override IEnumerable HandledExtensions => new[] { ".osz" }; @@ -53,12 +53,12 @@ namespace osu.Game.Stores protected override bool ShouldDeleteArchive(string path) => Path.GetExtension(path).ToLowerInvariant() == ".osz"; - protected override Task Populate(RealmBeatmapSet beatmapSet, ArchiveReader? archive, Realm realm, CancellationToken cancellationToken = default) + protected override Task Populate(BeatmapSetInfo beatmapSet, ArchiveReader? archive, Realm realm, CancellationToken cancellationToken = default) { if (archive != null) beatmapSet.Beatmaps.AddRange(createBeatmapDifficulties(beatmapSet.Files, realm)); - foreach (RealmBeatmap b in beatmapSet.Beatmaps) + foreach (BeatmapInfo b in beatmapSet.Beatmaps) b.BeatmapSet = beatmapSet; validateOnlineIds(beatmapSet, realm); @@ -84,7 +84,7 @@ namespace osu.Game.Stores return Task.CompletedTask; } - protected override void PreImport(RealmBeatmapSet beatmapSet, Realm realm) + protected override void PreImport(BeatmapSetInfo beatmapSet, Realm realm) { // We are about to import a new beatmap. Before doing so, ensure that no other set shares the online IDs used by the new one. // Note that this means if the previous beatmap is restored by the user, it will no longer be linked to its online IDs. @@ -93,7 +93,7 @@ namespace osu.Game.Stores if (beatmapSet.OnlineID > 0) { - var existingSetWithSameOnlineID = realm.All().SingleOrDefault(b => b.OnlineID == beatmapSet.OnlineID); + var existingSetWithSameOnlineID = realm.All().SingleOrDefault(b => b.OnlineID == beatmapSet.OnlineID); if (existingSetWithSameOnlineID != null) { @@ -108,7 +108,7 @@ namespace osu.Game.Stores } } - private void validateOnlineIds(RealmBeatmapSet beatmapSet, Realm realm) + private void validateOnlineIds(BeatmapSetInfo beatmapSet, Realm realm) { var beatmapIds = beatmapSet.Beatmaps.Where(b => b.OnlineID > 0).Select(b => b.OnlineID).ToList(); @@ -121,10 +121,10 @@ namespace osu.Game.Stores } // find any existing beatmaps in the database that have matching online ids - List existingBeatmaps = new List(); + List existingBeatmaps = new List(); foreach (int id in beatmapIds) - existingBeatmaps.AddRange(realm.All().Where(b => b.OnlineID == id)); + existingBeatmaps.AddRange(realm.All().Where(b => b.OnlineID == id)); if (existingBeatmaps.Any()) { @@ -143,7 +143,7 @@ namespace osu.Game.Stores void resetIds() => beatmapSet.Beatmaps.ForEach(b => b.OnlineID = -1); } - protected override bool CanSkipImport(RealmBeatmapSet existing, RealmBeatmapSet import) + protected override bool CanSkipImport(BeatmapSetInfo existing, BeatmapSetInfo import) { if (!base.CanSkipImport(existing, import)) return false; @@ -151,7 +151,7 @@ namespace osu.Game.Stores return existing.Beatmaps.Any(b => b.OnlineID > 0); } - protected override bool CanReuseExisting(RealmBeatmapSet existing, RealmBeatmapSet import) + protected override bool CanReuseExisting(BeatmapSetInfo existing, BeatmapSetInfo import) { if (!base.CanReuseExisting(existing, import)) return false; @@ -165,7 +165,7 @@ namespace osu.Game.Stores public override string HumanisedModelName => "beatmap"; - protected override RealmBeatmapSet? CreateModel(ArchiveReader reader) + protected override BeatmapSetInfo? CreateModel(ArchiveReader reader) { // let's make sure there are actually .osu files to import. string? mapName = reader.Filenames.FirstOrDefault(f => f.EndsWith(".osu", StringComparison.OrdinalIgnoreCase)); @@ -180,7 +180,7 @@ namespace osu.Game.Stores using (var stream = new LineBufferedReader(reader.GetStream(mapName))) beatmap = Decoder.GetDecoder(stream).Decode(stream); - return new RealmBeatmapSet + return new BeatmapSetInfo { OnlineID = beatmap.BeatmapInfo.BeatmapSet?.OnlineID ?? -1, // Metadata = beatmap.Metadata, @@ -189,11 +189,11 @@ namespace osu.Game.Stores } /// - /// Create all required s for the provided archive. + /// Create all required s for the provided archive. /// - private List createBeatmapDifficulties(IList files, Realm realm) + private List createBeatmapDifficulties(IList files, Realm realm) { - var beatmaps = new List(); + var beatmaps = new List(); foreach (var file in files.Where(f => f.Filename.EndsWith(".osu", StringComparison.OrdinalIgnoreCase))) { @@ -214,7 +214,7 @@ namespace osu.Game.Stores var decodedInfo = decoded.BeatmapInfo; var decodedDifficulty = decodedInfo.BaseDifficulty; - var ruleset = realm.All().FirstOrDefault(r => r.OnlineID == decodedInfo.RulesetID); + var ruleset = realm.All().FirstOrDefault(r => r.OnlineID == decodedInfo.RulesetID); if (ruleset?.Available != true) { @@ -222,7 +222,7 @@ namespace osu.Game.Stores continue; } - var difficulty = new RealmBeatmapDifficulty + var difficulty = new BeatmapDifficulty { DrainRate = decodedDifficulty.DrainRate, CircleSize = decodedDifficulty.CircleSize, @@ -232,7 +232,7 @@ namespace osu.Game.Stores SliderTickRate = decodedDifficulty.SliderTickRate, }; - var metadata = new RealmBeatmapMetadata + var metadata = new BeatmapMetadata { Title = decoded.Metadata.Title, TitleUnicode = decoded.Metadata.TitleUnicode, @@ -250,7 +250,7 @@ namespace osu.Game.Stores BackgroundFile = decoded.Metadata.BackgroundFile, }; - var beatmap = new RealmBeatmap(ruleset, difficulty, metadata) + var beatmap = new BeatmapInfo(ruleset, difficulty, metadata) { Hash = hash, DifficultyName = decodedInfo.DifficultyName, @@ -278,7 +278,7 @@ namespace osu.Game.Stores return beatmaps; } - private void updateBeatmapStatistics(RealmBeatmap beatmap, IBeatmap decoded) + private void updateBeatmapStatistics(BeatmapInfo beatmap, IBeatmap decoded) { var rulesetInstance = ((IRulesetInfo)beatmap.Ruleset).CreateInstance(); diff --git a/osu.Game/Stores/RealmRulesetStore.cs b/osu.Game/Stores/RealmRulesetStore.cs index 93b6d29e7d..f208b392cb 100644 --- a/osu.Game/Stores/RealmRulesetStore.cs +++ b/osu.Game/Stores/RealmRulesetStore.cs @@ -11,7 +11,6 @@ using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Database; -using osu.Game.Models; using osu.Game.Rulesets; #nullable enable @@ -106,7 +105,7 @@ namespace osu.Game.Stores { context.Write(realm => { - var rulesets = realm.All(); + var rulesets = realm.All(); List instances = loadedAssemblies.Values .Select(r => Activator.CreateInstance(r) as Ruleset) @@ -117,8 +116,8 @@ namespace osu.Game.Stores // add all legacy rulesets first to ensure they have exclusive choice of primary key. foreach (var r in instances.Where(r => r is ILegacyRuleset)) { - if (realm.All().FirstOrDefault(rr => rr.OnlineID == r.RulesetInfo.OnlineID) == null) - realm.Add(new RealmRuleset(r.RulesetInfo.ShortName, r.RulesetInfo.Name, r.RulesetInfo.InstantiationInfo, r.RulesetInfo.OnlineID)); + if (realm.All().FirstOrDefault(rr => rr.OnlineID == r.RulesetInfo.OnlineID) == null) + realm.Add(new RulesetInfo(r.RulesetInfo.ShortName, r.RulesetInfo.Name, r.RulesetInfo.InstantiationInfo, r.RulesetInfo.OnlineID)); } // add any other rulesets which have assemblies present but are not yet in the database. @@ -136,11 +135,11 @@ namespace osu.Game.Stores existingSameShortName.InstantiationInfo = r.RulesetInfo.InstantiationInfo; } else - realm.Add(new RealmRuleset(r.RulesetInfo.ShortName, r.RulesetInfo.Name, r.RulesetInfo.InstantiationInfo, r.RulesetInfo.OnlineID)); + realm.Add(new RulesetInfo(r.RulesetInfo.ShortName, r.RulesetInfo.Name, r.RulesetInfo.InstantiationInfo, r.RulesetInfo.OnlineID)); } } - List detachedRulesets = new List(); + List detachedRulesets = new List(); // perform a consistency check and detach final rulesets from realm for cross-thread runtime usage. foreach (var r in rulesets) From c3df58e01c741c9ed204b66fe666b6c87855b1e3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Nov 2021 19:24:07 +0900 Subject: [PATCH 167/996] Add required properties to make realm models backwards compatible --- osu.Game/Beatmaps/BeatmapDifficulty.cs | 27 +++++++++++- osu.Game/Beatmaps/BeatmapInfo.cs | 59 ++++++++++++++++++++++---- osu.Game/Beatmaps/BeatmapMetadata.cs | 10 +++++ osu.Game/Models/RealmNamedFileUsage.cs | 10 +++++ osu.Game/Rulesets/RulesetInfo.cs | 8 +++- 5 files changed, 103 insertions(+), 11 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapDifficulty.cs b/osu.Game/Beatmaps/BeatmapDifficulty.cs index 6425c7b291..90a60c0f0c 100644 --- a/osu.Game/Beatmaps/BeatmapDifficulty.cs +++ b/osu.Game/Beatmaps/BeatmapDifficulty.cs @@ -12,6 +12,11 @@ namespace osu.Game.Beatmaps [MapTo("BeatmapDifficulty")] public class BeatmapDifficulty : EmbeddedObject, IBeatmapDifficultyInfo { + /// + /// The default value used for all difficulty settings except and . + /// + public const float DEFAULT_DIFFICULTY = 5; + public float DrainRate { get; set; } = IBeatmapDifficultyInfo.DEFAULT_DIFFICULTY; public float CircleSize { get; set; } = IBeatmapDifficultyInfo.DEFAULT_DIFFICULTY; public float OverallDifficulty { get; set; } = IBeatmapDifficultyInfo.DEFAULT_DIFFICULTY; @@ -20,6 +25,15 @@ namespace osu.Game.Beatmaps public double SliderMultiplier { get; set; } = 1; public double SliderTickRate { get; set; } = 1; + public BeatmapDifficulty() + { + } + + public BeatmapDifficulty(IBeatmapDifficultyInfo source) + { + CopyFrom(source); + } + /// /// Returns a shallow-clone of this . /// @@ -30,7 +44,7 @@ namespace osu.Game.Beatmaps return diff; } - public void CopyTo(BeatmapDifficulty difficulty) + public virtual void CopyTo(BeatmapDifficulty difficulty) { difficulty.ApproachRate = ApproachRate; difficulty.DrainRate = DrainRate; @@ -40,5 +54,16 @@ namespace osu.Game.Beatmaps difficulty.SliderMultiplier = SliderMultiplier; difficulty.SliderTickRate = SliderTickRate; } + + public virtual void CopyFrom(IBeatmapDifficultyInfo other) + { + ApproachRate = other.ApproachRate; + DrainRate = other.DrainRate; + CircleSize = other.CircleSize; + OverallDifficulty = other.OverallDifficulty; + + SliderMultiplier = other.SliderMultiplier; + SliderTickRate = other.SliderTickRate; + } } } diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index b000862457..796c2b2e90 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -8,6 +8,7 @@ using Newtonsoft.Json; using osu.Framework.Testing; using osu.Game.Database; using osu.Game.Models; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets; using Realms; @@ -72,7 +73,7 @@ namespace osu.Game.Beatmaps } [UsedImplicitly] - private BeatmapInfo() + public BeatmapInfo() { } @@ -100,6 +101,13 @@ namespace osu.Game.Beatmaps public double TimelineZoom { get; set; } + public CountdownType Countdown { get; set; } = CountdownType.Normal; + + /// + /// The number of beats to move the countdown backwards (compared to its default location). + /// + public int CountdownOffset { get; set; } + #endregion public bool Equals(BeatmapInfo? other) @@ -113,20 +121,53 @@ namespace osu.Game.Beatmaps public bool Equals(IBeatmapInfo? other) => other is BeatmapInfo b && Equals(b); public bool AudioEquals(BeatmapInfo? other) => other != null - && BeatmapSet != null - && other.BeatmapSet != null - && BeatmapSet.Hash == other.BeatmapSet.Hash - && Metadata.AudioFile == other.Metadata.AudioFile; + && BeatmapSet != null + && other.BeatmapSet != null + && BeatmapSet.Hash == other.BeatmapSet.Hash + && Metadata.AudioFile == other.Metadata.AudioFile; public bool BackgroundEquals(BeatmapInfo? other) => other != null - && BeatmapSet != null - && other.BeatmapSet != null - && BeatmapSet.Hash == other.BeatmapSet.Hash - && Metadata.BackgroundFile == other.Metadata.BackgroundFile; + && BeatmapSet != null + && other.BeatmapSet != null + && BeatmapSet.Hash == other.BeatmapSet.Hash + && Metadata.BackgroundFile == other.Metadata.BackgroundFile; IBeatmapMetadataInfo IBeatmapInfo.Metadata => Metadata; IBeatmapSetInfo? IBeatmapInfo.BeatmapSet => BeatmapSet; IRulesetInfo IBeatmapInfo.Ruleset => Ruleset; IBeatmapDifficultyInfo IBeatmapInfo.Difficulty => Difficulty; + + #region Compatibility properties + + [Ignored] + public int RulesetID => Ruleset.OnlineID; + + [Ignored] + public Guid BeatmapSetInfoID => BeatmapSet?.ID ?? Guid.Empty; + + [Ignored] + public BeatmapDifficulty BaseDifficulty + { + get => Difficulty; + set => Difficulty = value; + } + + [Ignored] + public string? Path => File?.Filename; + + [Ignored] + public APIBeatmap? OnlineInfo { get; set; } + + [Ignored] + public int? MaxCombo { get; set; } + + [Ignored] + public int[] Bookmarks { get; set; } = Array.Empty(); + + public int BeatmapVersion; + + public BeatmapInfo Clone() => this.Detach(); + + #endregion } } diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs index 387641db40..dc5517375d 100644 --- a/osu.Game/Beatmaps/BeatmapMetadata.cs +++ b/osu.Game/Beatmaps/BeatmapMetadata.cs @@ -44,5 +44,15 @@ namespace osu.Game.Beatmaps public string BackgroundFile { get; set; } = string.Empty; IUser IBeatmapMetadataInfo.Author => Author; + + #region Compatibility properties + + public string AuthorString + { + get => Author.Username; + set => Author.Username = value; + } + + #endregion } } diff --git a/osu.Game/Models/RealmNamedFileUsage.cs b/osu.Game/Models/RealmNamedFileUsage.cs index 17e32510a8..801c826292 100644 --- a/osu.Game/Models/RealmNamedFileUsage.cs +++ b/osu.Game/Models/RealmNamedFileUsage.cs @@ -31,5 +31,15 @@ namespace osu.Game.Models } IFileInfo INamedFileUsage.File => File; + + #region Compatibility properties + + public RealmFile FileInfo + { + get => File; + set => File = value; + } + + #endregion } } diff --git a/osu.Game/Rulesets/RulesetInfo.cs b/osu.Game/Rulesets/RulesetInfo.cs index 91a3d181f8..99b4f5915f 100644 --- a/osu.Game/Rulesets/RulesetInfo.cs +++ b/osu.Game/Rulesets/RulesetInfo.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets } [UsedImplicitly] - private RulesetInfo() + public RulesetInfo() { } @@ -83,5 +83,11 @@ namespace osu.Game.Rulesets return ruleset; } + + #region Compatibility properties + + public int ID => OnlineID; + + #endregion } } From 4f66e8f88121cffadeab4e5df618cbb3642b35b3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Nov 2021 14:24:17 +0900 Subject: [PATCH 168/996] Fix issues with editor check tests --- .../Editing/Checks/CheckAudioInVideoTest.cs | 2 +- .../Editing/Checks/CheckFilePresenceTest.cs | 2 +- osu.Game.Tests/Editing/Checks/CheckTestHelpers.cs | 11 +++-------- .../Editing/Checks/CheckTooShortAudioFilesTest.cs | 3 +++ 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Editing/Checks/CheckAudioInVideoTest.cs b/osu.Game.Tests/Editing/Checks/CheckAudioInVideoTest.cs index f9b7bfa586..614b9b4ac1 100644 --- a/osu.Game.Tests/Editing/Checks/CheckAudioInVideoTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckAudioInVideoTest.cs @@ -74,7 +74,7 @@ namespace osu.Game.Tests.Editing.Checks [Test] public void TestMissingFile() { - beatmap.BeatmapInfo.BeatmapSet.Files.Clear(); + beatmap.BeatmapInfo.BeatmapSet?.Files.Clear(); var issues = check.Run(getContext(null)).ToList(); diff --git a/osu.Game.Tests/Editing/Checks/CheckFilePresenceTest.cs b/osu.Game.Tests/Editing/Checks/CheckFilePresenceTest.cs index f36454aa71..01baaadc7d 100644 --- a/osu.Game.Tests/Editing/Checks/CheckFilePresenceTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckFilePresenceTest.cs @@ -46,7 +46,7 @@ namespace osu.Game.Tests.Editing.Checks [Test] public void TestBackgroundSetAndNotInFiles() { - beatmap.BeatmapInfo.BeatmapSet.Files.Clear(); + beatmap.BeatmapInfo.BeatmapSet?.Files.Clear(); var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap)); var issues = check.Run(context).ToList(); diff --git a/osu.Game.Tests/Editing/Checks/CheckTestHelpers.cs b/osu.Game.Tests/Editing/Checks/CheckTestHelpers.cs index f702921986..9067714ff9 100644 --- a/osu.Game.Tests/Editing/Checks/CheckTestHelpers.cs +++ b/osu.Game.Tests/Editing/Checks/CheckTestHelpers.cs @@ -1,18 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Game.Beatmaps; -using osu.Game.IO; +using osu.Game.Models; namespace osu.Game.Tests.Editing.Checks { public static class CheckTestHelpers { - public static BeatmapSetFileInfo CreateMockFile(string extension) => - new BeatmapSetFileInfo - { - Filename = $"abc123.{extension}", - FileInfo = new FileInfo { Hash = "abcdef" } - }; + public static RealmNamedFileUsage CreateMockFile(string extension) => + new RealmNamedFileUsage(new RealmFile { Hash = "abcdef" }, $"abc123.{extension}"); } } diff --git a/osu.Game.Tests/Editing/Checks/CheckTooShortAudioFilesTest.cs b/osu.Game.Tests/Editing/Checks/CheckTooShortAudioFilesTest.cs index 8adf0d3764..242fec2f68 100644 --- a/osu.Game.Tests/Editing/Checks/CheckTooShortAudioFilesTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckTooShortAudioFilesTest.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.Diagnostics; using System.IO; using System.Linq; using ManagedBass; @@ -45,6 +46,8 @@ namespace osu.Game.Tests.Editing.Checks [Test] public void TestDifferentExtension() { + Debug.Assert(beatmap.BeatmapInfo.BeatmapSet != null); + beatmap.BeatmapInfo.BeatmapSet.Files.Clear(); beatmap.BeatmapInfo.BeatmapSet.Files.Add(CheckTestHelpers.CreateMockFile("jpg")); From e6f6558ddff8b02a4cbb002bce38259993de73a4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Nov 2021 14:26:51 +0900 Subject: [PATCH 169/996] Update mock model usage to set `GUID`s instead of `int`s --- .../TestSceneBeatmapDifficultyCache.cs | 18 ++++++++++-------- .../NonVisual/BeatmapSetInfoEqualityTest.cs | 9 ++++++--- .../SongSelect/TestSceneBeatmapCarousel.cs | 2 +- .../SongSelect/TestScenePlaySongSelect.cs | 2 +- .../UserInterface/TestSceneDeleteLocalScore.cs | 4 ++-- 5 files changed, 20 insertions(+), 15 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs b/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs index 26ab8808b9..298e474fd1 100644 --- a/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs +++ b/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs @@ -24,6 +24,8 @@ namespace osu.Game.Tests.Beatmaps { public const double BASE_STARS = 5.55; + private static readonly Guid guid = Guid.NewGuid(); + private BeatmapSetInfo importedSet; private TestBeatmapDifficultyCache difficultyCache; @@ -98,8 +100,8 @@ namespace osu.Game.Tests.Beatmaps [Test] public void TestKeyEqualsWithDifferentModInstances() { - var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModHardRock(), new OsuModHidden() }); - var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModHardRock(), new OsuModHidden() }); + var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = guid }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModHardRock(), new OsuModHidden() }); + var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = guid }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModHardRock(), new OsuModHidden() }); Assert.That(key1, Is.EqualTo(key2)); Assert.That(key1.GetHashCode(), Is.EqualTo(key2.GetHashCode())); @@ -108,8 +110,8 @@ namespace osu.Game.Tests.Beatmaps [Test] public void TestKeyEqualsWithDifferentModOrder() { - var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModHardRock(), new OsuModHidden() }); - var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModHidden(), new OsuModHardRock() }); + var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = guid }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModHardRock(), new OsuModHidden() }); + var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = guid }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModHidden(), new OsuModHardRock() }); Assert.That(key1, Is.EqualTo(key2)); Assert.That(key1.GetHashCode(), Is.EqualTo(key2.GetHashCode())); @@ -118,8 +120,8 @@ namespace osu.Game.Tests.Beatmaps [Test] public void TestKeyDoesntEqualWithDifferentModSettings() { - var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.1 } } }); - var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.9 } } }); + var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = guid }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.1 } } }); + var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = guid }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.9 } } }); Assert.That(key1, Is.Not.EqualTo(key2)); Assert.That(key1.GetHashCode(), Is.Not.EqualTo(key2.GetHashCode())); @@ -128,8 +130,8 @@ namespace osu.Game.Tests.Beatmaps [Test] public void TestKeyEqualWithMatchingModSettings() { - var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.25 } } }); - var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.25 } } }); + var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = guid }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.25 } } }); + var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = guid }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.25 } } }); Assert.That(key1, Is.EqualTo(key2)); Assert.That(key1.GetHashCode(), Is.EqualTo(key2.GetHashCode())); diff --git a/osu.Game.Tests/NonVisual/BeatmapSetInfoEqualityTest.cs b/osu.Game.Tests/NonVisual/BeatmapSetInfoEqualityTest.cs index 534983f869..1b6049fcb7 100644 --- a/osu.Game.Tests/NonVisual/BeatmapSetInfoEqualityTest.cs +++ b/osu.Game.Tests/NonVisual/BeatmapSetInfoEqualityTest.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 NUnit.Framework; using osu.Game.Beatmaps; using osu.Game.Extensions; @@ -23,8 +24,10 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestDatabasedWithDatabased() { - var ourInfo = new BeatmapSetInfo { ID = 123 }; - var otherInfo = new BeatmapSetInfo { ID = 123 }; + var guid = Guid.NewGuid(); + + var ourInfo = new BeatmapSetInfo { ID = guid }; + var otherInfo = new BeatmapSetInfo { ID = guid }; Assert.AreEqual(ourInfo, otherInfo); } @@ -32,7 +35,7 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestDatabasedWithOnline() { - var ourInfo = new BeatmapSetInfo { ID = 123, OnlineID = 12 }; + var ourInfo = new BeatmapSetInfo { ID = Guid.NewGuid(), OnlineID = 12 }; var otherInfo = new BeatmapSetInfo { OnlineID = 12 }; Assert.AreNotEqual(ourInfo, otherInfo); diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index f637c715a1..06ef3278ab 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -27,7 +27,7 @@ namespace osu.Game.Tests.Visual.SongSelect private RulesetStore rulesets; private readonly Stack selectedSets = new Stack(); - private readonly HashSet eagerSelectedIDs = new HashSet(); + private readonly HashSet eagerSelectedIDs = new HashSet(); private BeatmapInfo currentSelection => carousel.SelectedBeatmapInfo; diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 37f110e727..586cd273be 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -585,7 +585,7 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestHideSetSelectsCorrectBeatmap() { - int? previousID = null; + Guid? previousID = null; createSongSelect(); addRulesetImportStep(0); AddStep("Move to last difficulty", () => songSelect.Carousel.SelectBeatmap(songSelect.Carousel.BeatmapSets.First().Beatmaps.Last())); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index a436fc0bfa..d9ba443eca 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; @@ -59,10 +60,9 @@ namespace osu.Game.Tests.Visual.UserInterface Scope = BeatmapLeaderboardScope.Local, BeatmapInfo = new BeatmapInfo { - ID = 1, + ID = Guid.NewGuid(), Metadata = new BeatmapMetadata { - ID = 1, Title = "TestSong", Artist = "TestArtist", Author = new APIUser From 37673f4cf84b4edd49703aebadf96680ac355a54 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Nov 2021 14:29:46 +0900 Subject: [PATCH 170/996] Update sets of `BeatmapSet.Metadata` to instead create a `Beatmap` --- .../Online/TestSceneBeatmapDownloading.cs | 17 ++++++++++++----- .../SongSelect/TestSceneBeatmapCarousel.cs | 19 +++++++++++++------ 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Online/TestSceneBeatmapDownloading.cs b/osu.Game.Tests/Online/TestSceneBeatmapDownloading.cs index 4e77973655..ad9ea79646 100644 --- a/osu.Game.Tests/Online/TestSceneBeatmapDownloading.cs +++ b/osu.Game.Tests/Online/TestSceneBeatmapDownloading.cs @@ -5,6 +5,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Testing; using osu.Game.Beatmaps; +using osu.Game.Models; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.Notifications; using osu.Game.Tests.Visual; @@ -20,13 +21,19 @@ namespace osu.Game.Tests.Online private static readonly BeatmapSetInfo test_db_model = new BeatmapSetInfo { OnlineID = 1, - Metadata = new BeatmapMetadata + Beatmaps = { - Artist = "test author", - Title = "test title", - Author = new APIUser + new BeatmapInfo { - Username = "mapper" + Metadata = new BeatmapMetadata + { + Artist = "test author", + Title = "test title", + Author = new RealmUser + { + Username = "mapper" + } + } } } }; diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 06ef3278ab..7db71fb21e 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; @@ -409,10 +410,10 @@ namespace osu.Game.Tests.Visual.SongSelect var set = TestResources.CreateTestBeatmapSetInfo(); if (i == 4) - set.Metadata.Artist = zzz_string; + set.Beatmaps.ForEach(b => b.Metadata.Artist = zzz_string); if (i == 16) - set.Metadata.AuthorString = zzz_string; + set.Beatmaps.ForEach(b => b.Metadata.AuthorString = zzz_string); sets.Add(set); } @@ -432,13 +433,19 @@ namespace osu.Game.Tests.Visual.SongSelect for (int i = 0; i < 20; i++) { + // index + 1 because we are using OnlineID which should never be zero. var set = TestResources.CreateTestBeatmapSetInfo(); - set.Metadata.Artist = "same artist"; - set.Metadata.Title = "same title"; + + // only need to set the first as they are a shared reference. + var beatmap = set.Beatmaps.First(); + + beatmap.Metadata.Artist = "same artist"; + beatmap.Metadata.Title = "same title"; + sets.Add(set); } - int idOffset = sets.First().OnlineID ?? 0; + int idOffset = sets.First().OnlineID; loadBeatmaps(sets); @@ -674,7 +681,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("Restore different ruleset filter", () => { carousel.Filter(new FilterCriteria { Ruleset = rulesets.GetRuleset(1) }, false); - eagerSelectedIDs.Add(carousel.SelectedBeatmapSet.OnlineID ?? -1); + eagerSelectedIDs.Add(carousel.SelectedBeatmapSet.ID); }); AddAssert("selection changed", () => !carousel.SelectedBeatmapInfo.Equals(manySets.First().Beatmaps.First())); From 213d89b479e83bcc03bdeb38c5dee944f8ee68a7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Nov 2021 14:55:41 +0900 Subject: [PATCH 171/996] Update null fallback cases involving `OnlineID` --- .../Visual/Gameplay/TestScenePlayerScoreSubmission.cs | 2 +- osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs | 2 +- .../Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs | 2 +- .../TestSceneMultiplayerGameplayLeaderboard.cs | 2 +- .../TestSceneMultiplayerGameplayLeaderboardTeams.cs | 2 +- .../Visual/Multiplayer/TestSceneMultiplayerResults.cs | 2 +- .../Visual/Multiplayer/TestSceneMultiplayerTeamResults.cs | 2 +- osu.Game/Beatmaps/BeatmapModelManager.cs | 8 ++++---- osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs | 6 +++--- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 4 ++-- .../Screens/Select/Carousel/DrawableCarouselBeatmap.cs | 4 ++-- osu.Game/Stores/BeatmapImporter.cs | 4 ++-- 12 files changed, 20 insertions(+), 20 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs index cf5aadde6d..9f34931f54 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs @@ -237,7 +237,7 @@ namespace osu.Game.Tests.Visual.Gameplay createPlayerTest(false, r => { var beatmap = createTestBeatmap(r); - beatmap.BeatmapInfo.OnlineID = null; + beatmap.BeatmapInfo.OnlineID = -1; return beatmap; }); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index 242eca0bbc..d8964c531f 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -62,7 +62,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("import beatmap", () => { importedBeatmap = ImportBeatmapTest.LoadOszIntoOsu(game, virtualTrack: true).GetResultSafely(); - importedBeatmapId = importedBeatmap.Beatmaps.First(b => b.RulesetID == 0).OnlineID ?? -1; + importedBeatmapId = importedBeatmap.Beatmaps.First(b => b.RulesetID == 0).OnlineID; }); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index 61058bc87a..c78d1a2f62 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -49,7 +49,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { importedSet = ImportBeatmapTest.LoadOszIntoOsu(game, virtualTrack: true).GetResultSafely(); importedBeatmap = importedSet.Beatmaps.First(b => b.RulesetID == 0); - importedBeatmapId = importedBeatmap.OnlineID ?? -1; + importedBeatmapId = importedBeatmap.OnlineID; } [SetUp] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs index 07a8ef66e1..9b8e67b07a 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs @@ -58,7 +58,7 @@ namespace osu.Game.Tests.Visual.Multiplayer foreach (int user in users) { - SpectatorClient.StartPlay(user, Beatmap.Value.BeatmapInfo.OnlineID ?? 0); + SpectatorClient.StartPlay(user, Beatmap.Value.BeatmapInfo.OnlineID); multiplayerUsers.Add(OnlinePlayDependencies.Client.AddUser(new APIUser { Id = user }, true)); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs index 1237a21e94..8a78c12042 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs @@ -62,7 +62,7 @@ namespace osu.Game.Tests.Visual.Multiplayer foreach (int user in users) { - SpectatorClient.StartPlay(user, Beatmap.Value.BeatmapInfo.OnlineID ?? 0); + SpectatorClient.StartPlay(user, Beatmap.Value.BeatmapInfo.OnlineID); var roomUser = OnlinePlayDependencies.Client.AddUser(new APIUser { Id = user }, true); roomUser.MatchState = new TeamVersusUserState diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerResults.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerResults.cs index 4674601f28..44a1745eee 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerResults.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerResults.cs @@ -24,7 +24,7 @@ namespace osu.Game.Tests.Visual.Multiplayer PlaylistItem playlistItem = new PlaylistItem { - BeatmapID = beatmapInfo.OnlineID ?? -1, + BeatmapID = beatmapInfo.OnlineID, }; Stack.Push(screen = new MultiplayerResultsScreen(score, 1, playlistItem)); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerTeamResults.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerTeamResults.cs index f5df8d7507..dfc16c44f2 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerTeamResults.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerTeamResults.cs @@ -28,7 +28,7 @@ namespace osu.Game.Tests.Visual.Multiplayer PlaylistItem playlistItem = new PlaylistItem { - BeatmapID = beatmapInfo.OnlineID ?? -1, + BeatmapID = beatmapInfo.OnlineID, }; SortedDictionary teamScores = new SortedDictionary diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs index d0c41e0fb8..2a9b215f32 100644 --- a/osu.Game/Beatmaps/BeatmapModelManager.cs +++ b/osu.Game/Beatmaps/BeatmapModelManager.cs @@ -101,7 +101,7 @@ namespace osu.Game.Beatmaps { if (beatmapSet.OnlineID != null) { - beatmapSet.OnlineID = null; + beatmapSet.OnlineID = -1; LogForModel(beatmapSet, "Disassociating beatmap set ID due to loss of all beatmap IDs"); } } @@ -122,9 +122,9 @@ namespace osu.Game.Beatmaps Delete(existingSetWithSameOnlineID); // in order to avoid a unique key constraint, immediately remove the online ID from the previous set. - existingSetWithSameOnlineID.OnlineID = null; + existingSetWithSameOnlineID.OnlineID = -1; foreach (var b in existingSetWithSameOnlineID.Beatmaps) - b.OnlineID = null; + b.OnlineID = -1; LogForModel(beatmapSet, $"Found existing beatmap set with same OnlineBeatmapSetID ({beatmapSet.OnlineID}). It has been deleted."); } @@ -159,7 +159,7 @@ namespace osu.Game.Beatmaps } } - void resetIds() => beatmapSet.Beatmaps.ForEach(b => b.OnlineID = null); + void resetIds() => beatmapSet.Beatmaps.ForEach(b => b.OnlineID = -1); } /// diff --git a/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs b/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs index 76232c2932..3c4da12833 100644 --- a/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs +++ b/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs @@ -103,7 +103,7 @@ namespace osu.Game.Beatmaps void fail(Exception e) { - beatmapInfo.OnlineID = null; + beatmapInfo.OnlineID = -1; logForModel(set, $"Online retrieval failed for {beatmapInfo} ({e.Message})"); } } @@ -161,7 +161,7 @@ namespace osu.Game.Beatmaps if (string.IsNullOrEmpty(beatmapInfo.MD5Hash) && string.IsNullOrEmpty(beatmapInfo.Path) - && beatmapInfo.OnlineID == null) + && beatmapInfo.OnlineID <= 0) return false; try @@ -175,7 +175,7 @@ namespace osu.Game.Beatmaps cmd.CommandText = "SELECT beatmapset_id, beatmap_id, approved, user_id FROM osu_beatmaps WHERE checksum = @MD5Hash OR beatmap_id = @OnlineID OR filename = @Path"; cmd.Parameters.Add(new SqliteParameter("@MD5Hash", beatmapInfo.MD5Hash)); - cmd.Parameters.Add(new SqliteParameter("@OnlineID", beatmapInfo.OnlineID ?? (object)DBNull.Value)); + cmd.Parameters.Add(new SqliteParameter("@OnlineID", beatmapInfo.OnlineID)); cmd.Parameters.Add(new SqliteParameter("@Path", beatmapInfo.Path)); using (var reader = cmd.ExecuteReader()) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 49853418d6..ebdc882d2f 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -133,8 +133,8 @@ namespace osu.Game.Beatmaps.Formats writer.WriteLine(FormattableString.Invariant($"Version: {beatmap.BeatmapInfo.DifficultyName}")); if (!string.IsNullOrEmpty(beatmap.Metadata.Source)) writer.WriteLine(FormattableString.Invariant($"Source: {beatmap.Metadata.Source}")); if (!string.IsNullOrEmpty(beatmap.Metadata.Tags)) writer.WriteLine(FormattableString.Invariant($"Tags: {beatmap.Metadata.Tags}")); - if (beatmap.BeatmapInfo.OnlineID != null) writer.WriteLine(FormattableString.Invariant($"BeatmapID: {beatmap.BeatmapInfo.OnlineID}")); - if (beatmap.BeatmapInfo.BeatmapSet?.OnlineID != null) writer.WriteLine(FormattableString.Invariant($"BeatmapSetID: {beatmap.BeatmapInfo.BeatmapSet.OnlineID}")); + if (beatmap.BeatmapInfo.OnlineID > 0) writer.WriteLine(FormattableString.Invariant($"BeatmapID: {beatmap.BeatmapInfo.OnlineID}")); + if (beatmap.BeatmapInfo.BeatmapSet?.OnlineID > 0) writer.WriteLine(FormattableString.Invariant($"BeatmapSetID: {beatmap.BeatmapInfo.BeatmapSet.OnlineID}")); } private void handleDifficulty(TextWriter writer) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index d0f9d835fd..2eb226df70 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -238,8 +238,8 @@ namespace osu.Game.Screens.Select.Carousel if (editRequested != null) items.Add(new OsuMenuItem("Edit", MenuItemType.Standard, () => editRequested(beatmapInfo))); - if (beatmapInfo.OnlineID.HasValue && beatmapOverlay != null) - items.Add(new OsuMenuItem("Details...", MenuItemType.Standard, () => beatmapOverlay.FetchAndShowBeatmap(beatmapInfo.OnlineID.Value))); + if (beatmapInfo.OnlineID > 0 && beatmapOverlay != null) + items.Add(new OsuMenuItem("Details...", MenuItemType.Standard, () => beatmapOverlay.FetchAndShowBeatmap(beatmapInfo.OnlineID))); if (collectionManager != null) { diff --git a/osu.Game/Stores/BeatmapImporter.cs b/osu.Game/Stores/BeatmapImporter.cs index 69c136b51c..de92da394d 100644 --- a/osu.Game/Stores/BeatmapImporter.cs +++ b/osu.Game/Stores/BeatmapImporter.cs @@ -240,7 +240,7 @@ namespace osu.Game.Stores ArtistUnicode = decoded.Metadata.ArtistUnicode, Author = { - OnlineID = decoded.Metadata.Author.Id, + OnlineID = decoded.Metadata.Author.OnlineID, Username = decoded.Metadata.Author.Username }, Source = decoded.Metadata.Source, @@ -254,7 +254,7 @@ namespace osu.Game.Stores { Hash = hash, DifficultyName = decodedInfo.DifficultyName, - OnlineID = decodedInfo.OnlineID ?? -1, + OnlineID = decodedInfo.OnlineID, AudioLeadIn = decodedInfo.AudioLeadIn, StackLeniency = decodedInfo.StackLeniency, SpecialStyle = decodedInfo.SpecialStyle, From fda529de2674be00a7b386c23df37f2e092ed762 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Nov 2021 15:52:55 +0900 Subject: [PATCH 172/996] Update usages of `APIUser` to `RealmUser` --- osu.Game.Tests/Beatmaps/ToStringFormattingTest.cs | 6 +++--- .../Multiplayer/TestSceneDrawableRoomPlaylist.cs | 4 ++-- .../Ranking/TestSceneExpandedPanelMiddleContent.cs | 12 ++++++------ .../UserInterface/TestSceneDeleteLocalScore.cs | 3 ++- osu.Game/Beatmaps/BeatmapManager.cs | 7 ++++++- .../Online/API/Requests/Responses/APIBeatmapSet.cs | 8 ++++++-- 6 files changed, 25 insertions(+), 15 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/ToStringFormattingTest.cs b/osu.Game.Tests/Beatmaps/ToStringFormattingTest.cs index 4a7d7505ad..10cac4ed9d 100644 --- a/osu.Game.Tests/Beatmaps/ToStringFormattingTest.cs +++ b/osu.Game.Tests/Beatmaps/ToStringFormattingTest.cs @@ -3,7 +3,7 @@ using NUnit.Framework; using osu.Game.Beatmaps; -using osu.Game.Online.API.Requests.Responses; +using osu.Game.Models; namespace osu.Game.Tests.Beatmaps { @@ -34,7 +34,7 @@ namespace osu.Game.Tests.Beatmaps { Artist = "artist", Title = "title", - Author = new APIUser { Username = "creator" } + Author = new RealmUser { Username = "creator" } } }; @@ -50,7 +50,7 @@ namespace osu.Game.Tests.Beatmaps { Artist = "artist", Title = "title", - Author = new APIUser { Username = "creator" } + Author = new RealmUser { Username = "creator" } }, DifficultyName = "difficulty" }; diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index 147bbf2626..1607750d15 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -17,7 +17,7 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Database; using osu.Game.Graphics.Containers; -using osu.Game.Online.API.Requests.Responses; +using osu.Game.Models; using osu.Game.Online.Rooms; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; @@ -305,7 +305,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Metadata = new BeatmapMetadata { Artist = "Artist", - Author = new APIUser { Username = "Creator name here" }, + Author = new RealmUser { Username = "Creator name here" }, Title = "Long title used to check background colour", }, BeatmapSet = new BeatmapSetInfo() diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs b/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs index 2cb4fb6b6b..8b646df362 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs @@ -12,7 +12,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Graphics.Sprites; -using osu.Game.Online.API.Requests.Responses; +using osu.Game.Models; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; @@ -33,7 +33,7 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestMapWithKnownMapper() { - var author = new APIUser { Username = "mapper_name" }; + var author = new RealmUser { Username = "mapper_name" }; AddStep("show example score", () => showPanel(TestResources.CreateTestScoreInfo(createTestBeatmap(author)))); } @@ -43,7 +43,7 @@ namespace osu.Game.Tests.Visual.Ranking { AddStep("show excess mods score", () => { - var author = new APIUser { Username = "mapper_name" }; + var author = new RealmUser { Username = "mapper_name" }; var score = TestResources.CreateTestScoreInfo(createTestBeatmap(author)); score.Mods = score.BeatmapInfo.Ruleset.CreateInstance().CreateAllMods().ToArray(); @@ -57,7 +57,7 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestMapWithUnknownMapper() { - AddStep("show example score", () => showPanel(TestResources.CreateTestScoreInfo(createTestBeatmap(new APIUser())))); + AddStep("show example score", () => showPanel(TestResources.CreateTestScoreInfo(createTestBeatmap(new RealmUser())))); AddAssert("mapped by text not present", () => this.ChildrenOfType().All(spriteText => !containsAny(spriteText.Text.ToString(), "mapped", "by"))); @@ -73,7 +73,7 @@ namespace osu.Game.Tests.Visual.Ranking var ruleset = new OsuRuleset(); var mods = new Mod[] { ruleset.GetAutoplayMod() }; - var beatmap = createTestBeatmap(new APIUser()); + var beatmap = createTestBeatmap(new RealmUser()); var score = TestResources.CreateTestScoreInfo(beatmap); @@ -89,7 +89,7 @@ namespace osu.Game.Tests.Visual.Ranking private void showPanel(ScoreInfo score) => Child = new ExpandedPanelMiddleContentContainer(score); - private BeatmapInfo createTestBeatmap([NotNull] APIUser author) + private BeatmapInfo createTestBeatmap([NotNull] RealmUser author) { var beatmap = new TestBeatmap(rulesetStore.GetRuleset(0)).BeatmapInfo; diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index d9ba443eca..ecc8f697c7 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -17,6 +17,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; @@ -65,7 +66,7 @@ namespace osu.Game.Tests.Visual.UserInterface { Title = "TestSong", Artist = "TestArtist", - Author = new APIUser + Author = new RealmUser { Username = "TestAuthor" }, diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index ed7fe0bc91..dc7b5c7f29 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -18,6 +18,7 @@ using osu.Framework.Testing; using osu.Game.Database; using osu.Game.IO; using osu.Game.IO.Archives; +using osu.Game.Models; using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.Notifications; @@ -73,7 +74,11 @@ namespace osu.Game.Beatmaps { var metadata = new BeatmapMetadata { - Author = user, + Author = new RealmUser + { + OnlineID = user.OnlineID, + Username = user.Username, + } }; var set = new BeatmapSetInfo diff --git a/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs b/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs index 57c45faed3..d99c13b977 100644 --- a/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs +++ b/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs @@ -7,6 +7,7 @@ using Newtonsoft.Json; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Extensions; +using osu.Game.Models; #nullable enable @@ -123,8 +124,11 @@ namespace osu.Game.Online.API.Requests.Responses TitleUnicode = TitleUnicode, Artist = Artist, ArtistUnicode = ArtistUnicode, - AuthorID = AuthorID, - Author = Author, + Author = new RealmUser + { + OnlineID = Author.OnlineID, + Username = Author.Username + }, Source = Source, Tags = Tags, }; From 6a671b0a52fb4e3656e83e2ab4e94d5fcf117155 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Nov 2021 16:22:31 +0900 Subject: [PATCH 173/996] Remove unnecessary assigns of `BeatmapSetInfo.Metadata` --- .../Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs | 1 - osu.Game.Tests/Resources/TestResources.cs | 1 - .../Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs | 1 - osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs | 1 - osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs | 1 - osu.Game/Beatmaps/BeatmapManager.cs | 1 - 6 files changed, 6 deletions(-) diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index a7b431fb6e..48b22788a7 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -154,7 +154,6 @@ namespace osu.Game.Tests.Online Debug.Assert(info.BeatmapSet != null); info.BeatmapSet.Beatmaps.Add(info); - info.BeatmapSet.Metadata = info.Metadata; info.MD5Hash = stream.ComputeMD5Hash(); info.Hash = stream.ComputeSHA2Hash(); } diff --git a/osu.Game.Tests/Resources/TestResources.cs b/osu.Game.Tests/Resources/TestResources.cs index 445394fc77..c16f71334f 100644 --- a/osu.Game.Tests/Resources/TestResources.cs +++ b/osu.Game.Tests/Resources/TestResources.cs @@ -97,7 +97,6 @@ namespace osu.Game.Tests.Resources OnlineID = setId, Hash = new MemoryStream(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())).ComputeMD5Hash(), DateAdded = DateTimeOffset.UtcNow, - Metadata = metadata }; foreach (var b in getBeatmaps(difficultyCount ?? RNG.Next(1, 20))) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index bd4b38b9c0..7f7c97ac48 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -58,7 +58,6 @@ namespace osu.Game.Tests.Visual.Multiplayer { OnlineID = 10, Hash = Guid.NewGuid().ToString().ComputeMD5Hash(), - Metadata = metadata, DateAdded = DateTimeOffset.UtcNow }; diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs b/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs index 6420e7b849..81aacf61cd 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs @@ -109,7 +109,6 @@ namespace osu.Game.Tests.Visual.Navigation { Hash = Guid.NewGuid().ToString(), OnlineID = i, - Metadata = metadata, Beatmaps = { new BeatmapInfo diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs index 5dc1808c12..dbff5964df 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs @@ -40,7 +40,6 @@ namespace osu.Game.Tests.Visual.Navigation { Hash = Guid.NewGuid().ToString(), OnlineID = 1, - Metadata = metadata, Beatmaps = { new BeatmapInfo diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index dc7b5c7f29..c558a807e5 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -83,7 +83,6 @@ namespace osu.Game.Beatmaps var set = new BeatmapSetInfo { - Metadata = metadata, Beatmaps = { new BeatmapInfo From 2cb97dd5994ae887007e941409e92a495c5c9e10 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Nov 2021 17:11:29 +0900 Subject: [PATCH 174/996] Remove unnecessary assigns of EF foreign `ID` fields in tests --- osu.Game.Tests/Resources/TestResources.cs | 1 - osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs | 2 -- .../Visual/UserInterface/TestSceneDeleteLocalScore.cs | 1 - 3 files changed, 4 deletions(-) diff --git a/osu.Game.Tests/Resources/TestResources.cs b/osu.Game.Tests/Resources/TestResources.cs index c16f71334f..cd8b031f64 100644 --- a/osu.Game.Tests/Resources/TestResources.cs +++ b/osu.Game.Tests/Resources/TestResources.cs @@ -131,7 +131,6 @@ namespace osu.Game.Tests.Resources Length = length, BPM = bpm, Ruleset = rulesetInfo, - RulesetID = rulesetInfo.ID ?? -1, Metadata = metadata, BaseDifficulty = new BeatmapDifficulty { diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 7db71fb21e..87192badd6 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -584,7 +584,6 @@ namespace osu.Game.Tests.Visual.SongSelect for (int i = 0; i <= 2; i++) { testMixed.Beatmaps[i].Ruleset = rulesets.AvailableRulesets.ElementAt(i); - testMixed.Beatmaps[i].RulesetID = i; } carousel.UpdateBeatmapSet(testMixed); @@ -606,7 +605,6 @@ namespace osu.Game.Tests.Visual.SongSelect testSingle.Beatmaps.ForEach(b => { b.Ruleset = rulesets.AvailableRulesets.ElementAt(1); - b.RulesetID = b.Ruleset.ID ?? 1; }); carousel.UpdateBeatmapSet(testSingle); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index ecc8f697c7..cdb81f059b 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -95,7 +95,6 @@ namespace osu.Game.Tests.Visual.UserInterface { OnlineID = i, BeatmapInfo = beatmapInfo, - BeatmapInfoID = beatmapInfo.ID, Accuracy = RNG.NextDouble(), TotalScore = RNG.Next(1, 1000000), MaxCombo = RNG.Next(1, 1000), From df088f96f476b1e80fe9b66c60b83e72548edc83 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Nov 2021 17:12:26 +0900 Subject: [PATCH 175/996] Fix incorrect `Metadata`-related null checks --- osu.Game/Beatmaps/WorkingBeatmap.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 8289b32d31..397d47c389 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -31,7 +31,7 @@ namespace osu.Game.Beatmaps public readonly BeatmapSetInfo BeatmapSetInfo; // TODO: remove once the fallback lookup is not required (and access via `working.BeatmapInfo.Metadata` directly). - public BeatmapMetadata Metadata => BeatmapInfo.Metadata ?? BeatmapSetInfo?.Metadata ?? new BeatmapMetadata(); + public BeatmapMetadata Metadata => BeatmapInfo.Metadata; public Waveform Waveform => waveform.Value; @@ -57,7 +57,7 @@ namespace osu.Game.Beatmaps this.audioManager = audioManager; BeatmapInfo = beatmapInfo; - BeatmapSetInfo = beatmapInfo.BeatmapSet; + BeatmapSetInfo = beatmapInfo.BeatmapSet ?? new BeatmapSetInfo(); waveform = new Lazy(GetWaveform); storyboard = new Lazy(GetStoryboard); From e6fdd0e9697146bf2aab7a90015ba6009d8dcde3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Nov 2021 16:10:13 +0900 Subject: [PATCH 176/996] Miscellaneous fixes that don't fit elsewhere --- osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs | 1 - .../Visual/SongSelect/TestSceneBeatmapCarousel.cs | 6 +----- osu.Game/OsuGame.cs | 2 +- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 2 +- osu.Game/Storyboards/Storyboard.cs | 3 ++- 5 files changed, 5 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs index d57b3dec5d..e916e8d9ac 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs @@ -37,7 +37,6 @@ namespace osu.Game.Tests.Visual.SongSelect private BeatmapInfo exampleBeatmapInfo => new BeatmapInfo { - RulesetID = 0, Ruleset = rulesets.AvailableRulesets.First(), BaseDifficulty = new BeatmapDifficulty { diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 87192badd6..28f5c3ff60 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -378,11 +378,7 @@ namespace osu.Game.Tests.Visual.SongSelect var rulesetBeatmapSet = TestResources.CreateTestBeatmapSetInfo(1); var taikoRuleset = rulesets.AvailableRulesets.ElementAt(1); - rulesetBeatmapSet.Beatmaps.ForEach(b => - { - b.Ruleset = taikoRuleset; - b.RulesetID = 1; - }); + rulesetBeatmapSet.Beatmaps.ForEach(b => b.Ruleset = taikoRuleset); sets.Add(rulesetBeatmapSet); }); diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index c5a465ae96..0d0a572663 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -458,7 +458,7 @@ namespace osu.Game // Use all beatmaps if predicate matched nothing if (beatmaps.Count == 0) - beatmaps = databasedSet.Beatmaps; + beatmaps = databasedSet.Beatmaps.ToList(); // Prefer recommended beatmap if recommendations are available, else fallback to a sane selection. var selection = difficultyRecommender.GetRecommendedBeatmap(beatmaps) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 6791565828..b37877b35f 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -184,7 +184,7 @@ namespace osu.Game.Screens.Select private void load(OsuColour colours, LocalisationManager localisation, BeatmapDifficultyCache difficultyCache) { var beatmapInfo = working.BeatmapInfo; - var metadata = beatmapInfo.Metadata ?? working.BeatmapSetInfo?.Metadata ?? new BeatmapMetadata(); + var metadata = beatmapInfo.Metadata; RelativeSizeAxes = Axes.Both; diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs index fa90a00f0d..9502dd8968 100644 --- a/osu.Game/Storyboards/Storyboard.cs +++ b/osu.Game/Storyboards/Storyboard.cs @@ -96,7 +96,8 @@ namespace osu.Game.Storyboards public Drawable CreateSpriteFromResourcePath(string path, TextureStore textureStore) { Drawable drawable = null; - string storyboardPath = BeatmapInfo.BeatmapSet?.Files.Find(f => f.Filename.Equals(path, StringComparison.OrdinalIgnoreCase))?.FileInfo.GetStoragePath(); + + string storyboardPath = BeatmapInfo.BeatmapSet?.Files.FirstOrDefault(f => f.Filename.Equals(path, StringComparison.OrdinalIgnoreCase))?.FileInfo.GetStoragePath(); if (!string.IsNullOrEmpty(storyboardPath)) drawable = new Sprite { Texture = textureStore.Get(storyboardPath) }; From 83cbee39de86fe230967a7ec6b51eff991bcc241 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Nov 2021 17:59:54 +0900 Subject: [PATCH 177/996] Mark cases where `BeatmapSet` is generally guaranteed to be non-null --- osu.Game/Screens/Edit/Editor.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 48489c60ab..7955464590 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using JetBrains.Annotations; using osu.Framework; @@ -777,6 +778,8 @@ namespace osu.Game.Screens.Edit var beatmapSet = beatmapManager.QueryBeatmapSet(bs => bs.ID == Beatmap.Value.BeatmapSetInfo.ID) ?? playableBeatmap.BeatmapInfo.BeatmapSet; + Debug.Assert(beatmapSet != null); + var difficultyItems = new List(); foreach (var rulesetBeatmaps in beatmapSet.Beatmaps.GroupBy(b => b.RulesetID).OrderBy(group => group.Key)) From 89d6ffa7f384e333832a267b8f120580237cd4fb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Nov 2021 13:24:26 +0900 Subject: [PATCH 178/996] Use `RealmContextFactory` instead of EF --- ...ceneOnlinePlayBeatmapAvailabilityTracker.cs | 2 +- osu.Game/Beatmaps/BeatmapManager.cs | 2 +- osu.Game/Scoring/ScoreManager.cs | 2 +- osu.Game/Tests/Visual/OsuTestScene.cs | 18 ++++++------------ 4 files changed, 9 insertions(+), 15 deletions(-) diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index 48b22788a7..2cab823526 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -167,7 +167,7 @@ namespace osu.Game.Tests.Online public Task> CurrentImportTask { get; private set; } - public TestBeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore resources, GameHost host = null, WorkingBeatmap defaultBeatmap = null) + public TestBeatmapManager(Storage storage, RealmContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore resources, GameHost host = null, WorkingBeatmap defaultBeatmap = null) : base(storage, contextFactory, rulesets, api, audioManager, resources, host, defaultBeatmap) { } diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index c558a807e5..59adcfca60 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -40,7 +40,7 @@ namespace osu.Game.Beatmaps private readonly WorkingBeatmapCache workingBeatmapCache; private readonly BeatmapOnlineLookupQueue onlineBeatmapLookupQueue; - public BeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore gameResources, GameHost host = null, WorkingBeatmap defaultBeatmap = null, bool performOnlineLookups = false) + public BeatmapManager(Storage storage, RealmContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore gameResources, GameHost host = null, WorkingBeatmap defaultBeatmap = null, bool performOnlineLookups = false) { var userResources = new FileStore(contextFactory, storage).Store; diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 39cd28cad2..9f248ffdca 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -30,7 +30,7 @@ namespace osu.Game.Scoring private readonly OsuConfigManager configManager; private readonly ScoreModelManager scoreModelManager; - public ScoreManager(RulesetStore rulesets, Func beatmaps, Storage storage, IDatabaseContextFactory contextFactory, Scheduler scheduler, + public ScoreManager(RulesetStore rulesets, Func beatmaps, Storage storage, RealmContextFactory contextFactory, Scheduler scheduler, IIpcHost importHost = null, Func difficulties = null, OsuConfigManager configManager = null) { this.scheduler = scheduler; diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 6b029729ea..c631286d11 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -73,9 +73,9 @@ namespace osu.Game.Tests.Visual /// /// In interactive runs (ie. VisualTests) this will use the user's database if is not set to true. /// - protected DatabaseContextFactory ContextFactory => contextFactory.Value; + protected RealmContextFactory ContextFactory => contextFactory.Value; - private Lazy contextFactory; + private Lazy contextFactory; /// /// Whether a fresh storage should be initialised per test (method) run. @@ -117,14 +117,7 @@ namespace osu.Game.Tests.Visual Resources = parent.Get().Resources; - contextFactory = new Lazy(() => - { - var factory = new DatabaseContextFactory(LocalStorage); - - using (var usage = factory.Get()) - usage.Migrate(); - return factory; - }); + contextFactory = new Lazy(() => new RealmContextFactory(LocalStorage, "client")); RecycleLocalStorage(false); @@ -301,8 +294,9 @@ namespace osu.Game.Tests.Visual if (MusicController?.TrackLoaded == true) MusicController.Stop(); - if (contextFactory?.IsValueCreated == true) - contextFactory.Value.ResetDatabase(); + // TODO: what should we do here, if anything? should we use an in-memory realm in this instance? + // if (contextFactory?.IsValueCreated == true) + // contextFactory.Value.ResetDatabase(); RecycleLocalStorage(true); } From 3ecd889fef5f1b9a7882d40148795ac330ed4c2a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Nov 2021 13:00:33 +0900 Subject: [PATCH 179/996] Replace EF `RulesetStore` with realm version Pass full EF context factory to `RealmContextFactory` for migration purposes --- .../Database/BeatmapImporterTests.cs | 42 +-- osu.Game.Tests/Database/RulesetStoreTests.cs | 9 +- osu.Game/Database/RealmContextFactory.cs | 1 + osu.Game/OsuGameBase.cs | 10 +- .../Sections/Input/RulesetBindingsSection.cs | 3 + osu.Game/Rulesets/RulesetStore.cs | 164 ++++++----- osu.Game/Stores/RealmRulesetStore.cs | 269 ------------------ 7 files changed, 123 insertions(+), 375 deletions(-) delete mode 100644 osu.Game/Stores/RealmRulesetStore.cs diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index 37c2007681..adee545c1d 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -41,7 +41,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realmFactory, storage) => { using (var importer = new BeatmapImporter(realmFactory, storage)) - using (new RealmRulesetStore(realmFactory, storage)) + using (new RulesetStore(realmFactory, storage)) { ILive? imported; @@ -70,7 +70,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realmFactory, storage) => { using var importer = new BeatmapImporter(realmFactory, storage); - using var store = new RealmRulesetStore(realmFactory, storage); + using var store = new RulesetStore(realmFactory, storage); await LoadOszIntoStore(importer, realmFactory.Context); }); @@ -82,7 +82,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realmFactory, storage) => { using var importer = new BeatmapImporter(realmFactory, storage); - using var store = new RealmRulesetStore(realmFactory, storage); + using var store = new RulesetStore(realmFactory, storage); var imported = await LoadOszIntoStore(importer, realmFactory.Context); @@ -100,7 +100,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realmFactory, storage) => { using var importer = new BeatmapImporter(realmFactory, storage); - using var store = new RealmRulesetStore(realmFactory, storage); + using var store = new RulesetStore(realmFactory, storage); var imported = await LoadOszIntoStore(importer, realmFactory.Context); @@ -114,7 +114,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realmFactory, storage) => { using var importer = new BeatmapImporter(realmFactory, storage); - using var store = new RealmRulesetStore(realmFactory, storage); + using var store = new RulesetStore(realmFactory, storage); string? tempPath = TestResources.GetTestBeatmapForImport(); @@ -144,7 +144,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realmFactory, storage) => { using var importer = new BeatmapImporter(realmFactory, storage); - using var store = new RealmRulesetStore(realmFactory, storage); + using var store = new RulesetStore(realmFactory, storage); var imported = await LoadOszIntoStore(importer, realmFactory.Context); var importedSecondTime = await LoadOszIntoStore(importer, realmFactory.Context); @@ -164,7 +164,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realmFactory, storage) => { using var importer = new BeatmapImporter(realmFactory, storage); - using var store = new RealmRulesetStore(realmFactory, storage); + using var store = new RulesetStore(realmFactory, storage); string? temp = TestResources.GetTestBeatmapForImport(); @@ -213,7 +213,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realmFactory, storage) => { using var importer = new BeatmapImporter(realmFactory, storage); - using var store = new RealmRulesetStore(realmFactory, storage); + using var store = new RulesetStore(realmFactory, storage); string? temp = TestResources.GetTestBeatmapForImport(); @@ -265,7 +265,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realmFactory, storage) => { using var importer = new BeatmapImporter(realmFactory, storage); - using var store = new RealmRulesetStore(realmFactory, storage); + using var store = new RulesetStore(realmFactory, storage); string? temp = TestResources.GetTestBeatmapForImport(); @@ -313,7 +313,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realmFactory, storage) => { using var importer = new BeatmapImporter(realmFactory, storage); - using var store = new RealmRulesetStore(realmFactory, storage); + using var store = new RulesetStore(realmFactory, storage); string? temp = TestResources.GetTestBeatmapForImport(); @@ -362,7 +362,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realmFactory, storage) => { using var importer = new BeatmapImporter(realmFactory, storage); - using var store = new RealmRulesetStore(realmFactory, storage); + using var store = new RulesetStore(realmFactory, storage); var imported = await LoadOszIntoStore(importer, realmFactory.Context); @@ -395,7 +395,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realmFactory, storage) => { using var importer = new BeatmapImporter(realmFactory, storage); - using var store = new RealmRulesetStore(realmFactory, storage); + using var store = new RulesetStore(realmFactory, storage); var progressNotification = new ImportProgressNotification(); @@ -431,7 +431,7 @@ namespace osu.Game.Tests.Database }; using var importer = new BeatmapImporter(realmFactory, storage); - using var store = new RealmRulesetStore(realmFactory, storage); + using var store = new RulesetStore(realmFactory, storage); var imported = await LoadOszIntoStore(importer, realmFactory.Context); @@ -481,7 +481,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realmFactory, storage) => { using var importer = new BeatmapImporter(realmFactory, storage); - using var store = new RealmRulesetStore(realmFactory, storage); + using var store = new RulesetStore(realmFactory, storage); var imported = await LoadOszIntoStore(importer, realmFactory.Context); @@ -529,7 +529,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realmFactory, storage) => { using var importer = new BeatmapImporter(realmFactory, storage); - using var store = new RealmRulesetStore(realmFactory, storage); + using var store = new RulesetStore(realmFactory, storage); var imported = await LoadOszIntoStore(importer, realmFactory.Context); @@ -555,7 +555,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realmFactory, storage) => { using var importer = new BeatmapImporter(realmFactory, storage); - using var store = new RealmRulesetStore(realmFactory, storage); + using var store = new RulesetStore(realmFactory, storage); var metadata = new BeatmapMetadata { @@ -601,7 +601,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realmFactory, storage) => { using var importer = new BeatmapImporter(realmFactory, storage); - using var store = new RealmRulesetStore(realmFactory, storage); + using var store = new RulesetStore(realmFactory, storage); string? temp = TestResources.GetTestBeatmapForImport(); using (File.OpenRead(temp)) @@ -618,7 +618,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realmFactory, storage) => { using var importer = new BeatmapImporter(realmFactory, storage); - using var store = new RealmRulesetStore(realmFactory, storage); + using var store = new RulesetStore(realmFactory, storage); string? temp = TestResources.GetTestBeatmapForImport(); @@ -654,7 +654,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realmFactory, storage) => { using var importer = new BeatmapImporter(realmFactory, storage); - using var store = new RealmRulesetStore(realmFactory, storage); + using var store = new RulesetStore(realmFactory, storage); string? temp = TestResources.GetTestBeatmapForImport(); @@ -696,7 +696,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realmFactory, storage) => { using var importer = new BeatmapImporter(realmFactory, storage); - using var store = new RealmRulesetStore(realmFactory, storage); + using var store = new RulesetStore(realmFactory, storage); string? temp = TestResources.GetTestBeatmapForImport(); @@ -747,7 +747,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realmFactory, storage) => { using var importer = new BeatmapImporter(realmFactory, storage); - using var store = new RealmRulesetStore(realmFactory, storage); + using var store = new RulesetStore(realmFactory, storage); string? temp = TestResources.GetTestBeatmapForImport(); await importer.Import(temp); diff --git a/osu.Game.Tests/Database/RulesetStoreTests.cs b/osu.Game.Tests/Database/RulesetStoreTests.cs index d2de31d11a..4416da6f92 100644 --- a/osu.Game.Tests/Database/RulesetStoreTests.cs +++ b/osu.Game.Tests/Database/RulesetStoreTests.cs @@ -4,7 +4,6 @@ using System.Linq; using NUnit.Framework; using osu.Game.Rulesets; -using osu.Game.Stores; namespace osu.Game.Tests.Database { @@ -15,7 +14,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealm((realmFactory, storage) => { - var rulesets = new RealmRulesetStore(realmFactory, storage); + var rulesets = new RulesetStore(realmFactory, storage); Assert.AreEqual(4, rulesets.AvailableRulesets.Count()); Assert.AreEqual(4, realmFactory.Context.All().Count()); @@ -27,8 +26,8 @@ namespace osu.Game.Tests.Database { RunTestWithRealm((realmFactory, storage) => { - var rulesets = new RealmRulesetStore(realmFactory, storage); - var rulesets2 = new RealmRulesetStore(realmFactory, storage); + var rulesets = new RulesetStore(realmFactory, storage); + var rulesets2 = new RulesetStore(realmFactory, storage); Assert.AreEqual(4, rulesets.AvailableRulesets.Count()); Assert.AreEqual(4, rulesets2.AvailableRulesets.Count()); @@ -43,7 +42,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealm((realmFactory, storage) => { - var rulesets = new RealmRulesetStore(realmFactory, storage); + var rulesets = new RulesetStore(realmFactory, storage); Assert.IsFalse(rulesets.AvailableRulesets.First().IsManaged); Assert.IsFalse(rulesets.GetRuleset(0)?.IsManaged); diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 946dda1f39..968caaa7ce 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -17,6 +17,7 @@ using osu.Game.Input.Bindings; using osu.Game.Models; using osu.Game.Skinning; using osu.Game.Stores; +using osu.Game.Rulesets; using Realms; #nullable enable diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 9256514a0a..144ee17c29 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -40,7 +40,6 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Scoring; using osu.Game.Skinning; -using osu.Game.Stores; using osu.Game.Utils; using RuntimeInfo = osu.Framework.RuntimeInfo; @@ -166,8 +165,6 @@ namespace osu.Game private readonly BindableNumber globalTrackVolumeAdjust = new BindableNumber(global_track_volume_adjust); - private RealmRulesetStore realmRulesetStore; - public OsuGameBase() { UseDevelopmentServer = DebugUtils.IsDebugBuild; @@ -231,6 +228,8 @@ namespace osu.Game dependencies.Cache(fileStore = new FileStore(contextFactory, Storage)); + dependencies.Cache(RulesetStore = new RulesetStore(realmFactory, Storage)); + // ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup() dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, contextFactory, Scheduler, Host, () => difficultyCache, LocalConfig)); dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, contextFactory, RulesetStore, API, Audio, Resources, Host, defaultBeatmap, performOnlineLookups: true)); @@ -238,11 +237,6 @@ namespace osu.Game dependencies.Cache(BeatmapDownloader = new BeatmapModelDownloader(BeatmapManager, API)); dependencies.Cache(ScoreDownloader = new ScoreModelDownloader(ScoreManager, API)); - // the following realm components are not actively used yet, but initialised and kept up to date for initial testing. - realmRulesetStore = new RealmRulesetStore(realmFactory, Storage); - - dependencies.Cache(realmRulesetStore); - // this should likely be moved to ArchiveModelManager when another case appears where it is necessary // to have inter-dependent model managers. this could be obtained with an IHasForeign interface to // allow lookups to be done on the child (ScoreManager in this case) to perform the cascading delete. diff --git a/osu.Game/Overlays/Settings/Sections/Input/RulesetBindingsSection.cs b/osu.Game/Overlays/Settings/Sections/Input/RulesetBindingsSection.cs index b5d26d4887..dae276c711 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/RulesetBindingsSection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/RulesetBindingsSection.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.Diagnostics; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; @@ -26,6 +27,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input var r = ruleset.CreateInstance(); + Debug.Assert(r != null); + foreach (int variant in r.AvailableVariants) Add(new VariantBindingsSubsection(ruleset, variant)); } diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index 5cc6a75f43..de3bb1ac34 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -7,24 +7,33 @@ using System.IO; using System.Linq; using System.Reflection; using osu.Framework; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Database; +#nullable enable + namespace osu.Game.Rulesets { - public class RulesetStore : DatabaseBackedStore, IRulesetStore, IDisposable + public class RulesetStore : IDisposable { - private const string ruleset_library_prefix = "osu.Game.Rulesets"; + private readonly RealmContextFactory realmFactory; + + private const string ruleset_library_prefix = @"osu.Game.Rulesets"; private readonly Dictionary loadedAssemblies = new Dictionary(); - private readonly Storage rulesetStorage; + /// + /// All available rulesets. + /// + public IEnumerable AvailableRulesets => availableRulesets; - public RulesetStore(IDatabaseContextFactory factory, Storage storage = null) - : base(factory) + private readonly List availableRulesets = new List(); + + public RulesetStore(RealmContextFactory realmFactory, Storage? storage = null) { - rulesetStorage = storage?.GetStorageForDirectory("rulesets"); + this.realmFactory = realmFactory; // On android in release configuration assemblies are loaded from the apk directly into memory. // We cannot read assemblies from cwd, so should check loaded assemblies instead. @@ -40,7 +49,11 @@ namespace osu.Game.Rulesets // It needs to be attached to the assembly lookup event before the actual call to loadUserRulesets() else rulesets located out of the base game directory will fail // to load as unable to locate the game core assembly. AppDomain.CurrentDomain.AssemblyResolve += resolveRulesetDependencyAssembly; - loadUserRulesets(); + + var rulesetStorage = storage?.GetStorageForDirectory(@"rulesets"); + if (rulesetStorage != null) + loadUserRulesets(rulesetStorage); + addMissingRulesets(); } @@ -49,21 +62,16 @@ namespace osu.Game.Rulesets /// /// The ruleset's internal ID. /// A ruleset, if available, else null. - public RulesetInfo GetRuleset(int id) => AvailableRulesets.FirstOrDefault(r => r.ID == id); + public RulesetInfo? GetRuleset(int id) => AvailableRulesets.FirstOrDefault(r => r.OnlineID == id); /// /// Retrieve a ruleset using a known short name. /// /// The ruleset's short name. /// A ruleset, if available, else null. - public RulesetInfo GetRuleset(string shortName) => AvailableRulesets.FirstOrDefault(r => r.ShortName == shortName); + public RulesetInfo? GetRuleset(string shortName) => AvailableRulesets.FirstOrDefault(r => r.ShortName == shortName); - /// - /// All available rulesets. - /// - public IEnumerable AvailableRulesets { get; private set; } - - private Assembly resolveRulesetDependencyAssembly(object sender, ResolveEventArgs args) + private Assembly? resolveRulesetDependencyAssembly(object? sender, ResolveEventArgs args) { var asm = new AssemblyName(args.Name); @@ -72,7 +80,14 @@ namespace osu.Game.Rulesets // already loaded in the AppDomain. var domainAssembly = AppDomain.CurrentDomain.GetAssemblies() // Given name is always going to be equally-or-more qualified than the assembly name. - .Where(a => args.Name.Contains(a.GetName().Name, StringComparison.Ordinal)) + .Where(a => + { + string? name = a.GetName().Name; + if (name == null) + return false; + + return args.Name.Contains(name, StringComparison.Ordinal); + }) // Pick the greatest assembly version. .OrderByDescending(a => a.GetName().Version) .FirstOrDefault(); @@ -85,69 +100,73 @@ namespace osu.Game.Rulesets private void addMissingRulesets() { - using (var usage = ContextFactory.GetForWrite()) + using (var context = realmFactory.CreateContext()) { - var context = usage.Context; - - var instances = loadedAssemblies.Values.Select(r => (Ruleset)Activator.CreateInstance(r)).ToList(); - - // add all legacy rulesets first to ensure they have exclusive choice of primary key. - foreach (var r in instances.Where(r => r is ILegacyRuleset)) + context.Write(realm => { - if (context.RulesetInfo.SingleOrDefault(dbRuleset => dbRuleset.ID == r.RulesetInfo.ID) == null) - context.RulesetInfo.Add(r.RulesetInfo); - } + var rulesets = realm.All(); - context.SaveChanges(); + List instances = loadedAssemblies.Values + .Select(r => Activator.CreateInstance(r) as Ruleset) + .Where(r => r != null) + .Select(r => r.AsNonNull()) + .ToList(); - var existingRulesets = context.RulesetInfo.ToList(); - - // add any other rulesets which have assemblies present but are not yet in the database. - foreach (var r in instances.Where(r => !(r is ILegacyRuleset))) - { - if (existingRulesets.FirstOrDefault(ri => ri.InstantiationInfo.Equals(r.RulesetInfo.InstantiationInfo, StringComparison.Ordinal)) == null) + // add all legacy rulesets first to ensure they have exclusive choice of primary key. + foreach (var r in instances.Where(r => r is ILegacyRuleset)) { - var existingSameShortName = existingRulesets.FirstOrDefault(ri => ri.ShortName == r.RulesetInfo.ShortName); + if (realm.All().FirstOrDefault(rr => rr.OnlineID == r.RulesetInfo.OnlineID) == null) + realm.Add(new RulesetInfo(r.RulesetInfo.ShortName, r.RulesetInfo.Name, r.RulesetInfo.InstantiationInfo, r.RulesetInfo.OnlineID)); + } - if (existingSameShortName != null) + // add any other rulesets which have assemblies present but are not yet in the database. + foreach (var r in instances.Where(r => !(r is ILegacyRuleset))) + { + if (rulesets.FirstOrDefault(ri => ri.InstantiationInfo.Equals(r.RulesetInfo.InstantiationInfo, StringComparison.Ordinal)) == null) { - // even if a matching InstantiationInfo was not found, there may be an existing ruleset with the same ShortName. - // this generally means the user or ruleset provider has renamed their dll but the underlying ruleset is *likely* the same one. - // in such cases, update the instantiation info of the existing entry to point to the new one. - existingSameShortName.InstantiationInfo = r.RulesetInfo.InstantiationInfo; + var existingSameShortName = rulesets.FirstOrDefault(ri => ri.ShortName == r.RulesetInfo.ShortName); + + if (existingSameShortName != null) + { + // even if a matching InstantiationInfo was not found, there may be an existing ruleset with the same ShortName. + // this generally means the user or ruleset provider has renamed their dll but the underlying ruleset is *likely* the same one. + // in such cases, update the instantiation info of the existing entry to point to the new one. + existingSameShortName.InstantiationInfo = r.RulesetInfo.InstantiationInfo; + } + else + realm.Add(new RulesetInfo(r.RulesetInfo.ShortName, r.RulesetInfo.Name, r.RulesetInfo.InstantiationInfo, r.RulesetInfo.OnlineID)); } - else - context.RulesetInfo.Add(r.RulesetInfo); } - } - context.SaveChanges(); + List detachedRulesets = new List(); - // perform a consistency check - foreach (var r in context.RulesetInfo) - { - try + // perform a consistency check and detach final rulesets from realm for cross-thread runtime usage. + foreach (var r in rulesets) { - var resolvedType = Type.GetType(r.InstantiationInfo) - ?? throw new RulesetLoadException(@"Type could not be resolved"); + try + { + var resolvedType = Type.GetType(r.InstantiationInfo) + ?? throw new RulesetLoadException(@"Type could not be resolved"); - var instanceInfo = (Activator.CreateInstance(resolvedType) as Ruleset)?.RulesetInfo - ?? throw new RulesetLoadException(@"Instantiation failure"); + var instanceInfo = (Activator.CreateInstance(resolvedType) as Ruleset)?.RulesetInfo + ?? throw new RulesetLoadException(@"Instantiation failure"); - r.Name = instanceInfo.Name; - r.ShortName = instanceInfo.ShortName; - r.InstantiationInfo = instanceInfo.InstantiationInfo; - r.Available = true; + r.Name = instanceInfo.Name; + r.ShortName = instanceInfo.ShortName; + r.InstantiationInfo = instanceInfo.InstantiationInfo; + r.Available = true; + + detachedRulesets.Add(r.Clone()); + } + catch (Exception ex) + { + r.Available = false; + Logger.Log($"Could not load ruleset {r}: {ex.Message}"); + } } - catch - { - r.Available = false; - } - } - context.SaveChanges(); - - AvailableRulesets = context.RulesetInfo.Where(r => r.Available).ToList(); + availableRulesets.AddRange(detachedRulesets); + }); } } @@ -155,22 +174,23 @@ namespace osu.Game.Rulesets { foreach (var ruleset in AppDomain.CurrentDomain.GetAssemblies()) { - string rulesetName = ruleset.GetName().Name; + string? rulesetName = ruleset.GetName().Name; - if (!rulesetName.StartsWith(ruleset_library_prefix, StringComparison.InvariantCultureIgnoreCase) || ruleset.GetName().Name.Contains("Tests")) + if (rulesetName == null) + continue; + + if (!rulesetName.StartsWith(ruleset_library_prefix, StringComparison.InvariantCultureIgnoreCase) || rulesetName.Contains(@"Tests")) continue; addRuleset(ruleset); } } - private void loadUserRulesets() + private void loadUserRulesets(Storage rulesetStorage) { - if (rulesetStorage == null) return; + var rulesets = rulesetStorage.GetFiles(@".", @$"{ruleset_library_prefix}.*.dll"); - var rulesets = rulesetStorage.GetFiles(".", $"{ruleset_library_prefix}.*.dll"); - - foreach (string ruleset in rulesets.Where(f => !f.Contains("Tests"))) + foreach (string? ruleset in rulesets.Where(f => !f.Contains(@"Tests"))) loadRulesetFromFile(rulesetStorage.GetFullPath(ruleset)); } @@ -178,7 +198,7 @@ namespace osu.Game.Rulesets { try { - string[] files = Directory.GetFiles(RuntimeInfo.StartupDirectory, $"{ruleset_library_prefix}.*.dll"); + string[] files = Directory.GetFiles(RuntimeInfo.StartupDirectory, @$"{ruleset_library_prefix}.*.dll"); foreach (string file in files.Where(f => !Path.GetFileName(f).Contains("Tests"))) loadRulesetFromFile(file); @@ -191,7 +211,7 @@ namespace osu.Game.Rulesets private void loadRulesetFromFile(string file) { - string filename = Path.GetFileNameWithoutExtension(file); + string? filename = Path.GetFileNameWithoutExtension(file); if (loadedAssemblies.Values.Any(t => Path.GetFileNameWithoutExtension(t.Assembly.Location) == filename)) return; diff --git a/osu.Game/Stores/RealmRulesetStore.cs b/osu.Game/Stores/RealmRulesetStore.cs deleted file mode 100644 index f208b392cb..0000000000 --- a/osu.Game/Stores/RealmRulesetStore.cs +++ /dev/null @@ -1,269 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using osu.Framework; -using osu.Framework.Extensions.ObjectExtensions; -using osu.Framework.Logging; -using osu.Framework.Platform; -using osu.Game.Database; -using osu.Game.Rulesets; - -#nullable enable - -namespace osu.Game.Stores -{ - public class RealmRulesetStore : IRulesetStore, IDisposable - { - private readonly RealmContextFactory realmFactory; - - private const string ruleset_library_prefix = @"osu.Game.Rulesets"; - - private readonly Dictionary loadedAssemblies = new Dictionary(); - - /// - /// All available rulesets. - /// - public IEnumerable AvailableRulesets => availableRulesets; - - private readonly List availableRulesets = new List(); - - public RealmRulesetStore(RealmContextFactory realmFactory, Storage? storage = null) - { - this.realmFactory = realmFactory; - - // On android in release configuration assemblies are loaded from the apk directly into memory. - // We cannot read assemblies from cwd, so should check loaded assemblies instead. - loadFromAppDomain(); - - // This null check prevents Android from attempting to load the rulesets from disk, - // as the underlying path "AppContext.BaseDirectory", despite being non-nullable, it returns null on android. - // See https://github.com/xamarin/xamarin-android/issues/3489. - if (RuntimeInfo.StartupDirectory != null) - loadFromDisk(); - - // the event handler contains code for resolving dependency on the game assembly for rulesets located outside the base game directory. - // It needs to be attached to the assembly lookup event before the actual call to loadUserRulesets() else rulesets located out of the base game directory will fail - // to load as unable to locate the game core assembly. - AppDomain.CurrentDomain.AssemblyResolve += resolveRulesetDependencyAssembly; - - var rulesetStorage = storage?.GetStorageForDirectory(@"rulesets"); - if (rulesetStorage != null) - loadUserRulesets(rulesetStorage); - - addMissingRulesets(); - } - - /// - /// Retrieve a ruleset using a known ID. - /// - /// The ruleset's internal ID. - /// A ruleset, if available, else null. - public RealmRuleset? GetRuleset(int id) => AvailableRulesets.FirstOrDefault(r => r.OnlineID == id); - - /// - /// Retrieve a ruleset using a known short name. - /// - /// The ruleset's short name. - /// A ruleset, if available, else null. - public RealmRuleset? GetRuleset(string shortName) => AvailableRulesets.FirstOrDefault(r => r.ShortName == shortName); - - private Assembly? resolveRulesetDependencyAssembly(object? sender, ResolveEventArgs args) - { - var asm = new AssemblyName(args.Name); - - // the requesting assembly may be located out of the executable's base directory, thus requiring manual resolving of its dependencies. - // this attempts resolving the ruleset dependencies on game core and framework assemblies by returning assemblies with the same assembly name - // already loaded in the AppDomain. - var domainAssembly = AppDomain.CurrentDomain.GetAssemblies() - // Given name is always going to be equally-or-more qualified than the assembly name. - .Where(a => - { - string? name = a.GetName().Name; - if (name == null) - return false; - - return args.Name.Contains(name, StringComparison.Ordinal); - }) - // Pick the greatest assembly version. - .OrderByDescending(a => a.GetName().Version) - .FirstOrDefault(); - - if (domainAssembly != null) - return domainAssembly; - - return loadedAssemblies.Keys.FirstOrDefault(a => a.FullName == asm.FullName); - } - - private void addMissingRulesets() - { - using (var context = realmFactory.CreateContext()) - { - context.Write(realm => - { - var rulesets = realm.All(); - - List instances = loadedAssemblies.Values - .Select(r => Activator.CreateInstance(r) as Ruleset) - .Where(r => r != null) - .Select(r => r.AsNonNull()) - .ToList(); - - // add all legacy rulesets first to ensure they have exclusive choice of primary key. - foreach (var r in instances.Where(r => r is ILegacyRuleset)) - { - if (realm.All().FirstOrDefault(rr => rr.OnlineID == r.RulesetInfo.OnlineID) == null) - realm.Add(new RulesetInfo(r.RulesetInfo.ShortName, r.RulesetInfo.Name, r.RulesetInfo.InstantiationInfo, r.RulesetInfo.OnlineID)); - } - - // add any other rulesets which have assemblies present but are not yet in the database. - foreach (var r in instances.Where(r => !(r is ILegacyRuleset))) - { - if (rulesets.FirstOrDefault(ri => ri.InstantiationInfo.Equals(r.RulesetInfo.InstantiationInfo, StringComparison.Ordinal)) == null) - { - var existingSameShortName = rulesets.FirstOrDefault(ri => ri.ShortName == r.RulesetInfo.ShortName); - - if (existingSameShortName != null) - { - // even if a matching InstantiationInfo was not found, there may be an existing ruleset with the same ShortName. - // this generally means the user or ruleset provider has renamed their dll but the underlying ruleset is *likely* the same one. - // in such cases, update the instantiation info of the existing entry to point to the new one. - existingSameShortName.InstantiationInfo = r.RulesetInfo.InstantiationInfo; - } - else - realm.Add(new RulesetInfo(r.RulesetInfo.ShortName, r.RulesetInfo.Name, r.RulesetInfo.InstantiationInfo, r.RulesetInfo.OnlineID)); - } - } - - List detachedRulesets = new List(); - - // perform a consistency check and detach final rulesets from realm for cross-thread runtime usage. - foreach (var r in rulesets) - { - try - { - var resolvedType = Type.GetType(r.InstantiationInfo) - ?? throw new RulesetLoadException(@"Type could not be resolved"); - - var instanceInfo = (Activator.CreateInstance(resolvedType) as Ruleset)?.RulesetInfo - ?? throw new RulesetLoadException(@"Instantiation failure"); - - r.Name = instanceInfo.Name; - r.ShortName = instanceInfo.ShortName; - r.InstantiationInfo = instanceInfo.InstantiationInfo; - r.Available = true; - - detachedRulesets.Add(r.Clone()); - } - catch (Exception ex) - { - r.Available = false; - Logger.Log($"Could not load ruleset {r}: {ex.Message}"); - } - } - - availableRulesets.AddRange(detachedRulesets); - }); - } - } - - private void loadFromAppDomain() - { - foreach (var ruleset in AppDomain.CurrentDomain.GetAssemblies()) - { - string? rulesetName = ruleset.GetName().Name; - - if (rulesetName == null) - continue; - - if (!rulesetName.StartsWith(ruleset_library_prefix, StringComparison.InvariantCultureIgnoreCase) || rulesetName.Contains(@"Tests")) - continue; - - addRuleset(ruleset); - } - } - - private void loadUserRulesets(Storage rulesetStorage) - { - var rulesets = rulesetStorage.GetFiles(@".", @$"{ruleset_library_prefix}.*.dll"); - - foreach (string? ruleset in rulesets.Where(f => !f.Contains(@"Tests"))) - loadRulesetFromFile(rulesetStorage.GetFullPath(ruleset)); - } - - private void loadFromDisk() - { - try - { - string[] files = Directory.GetFiles(RuntimeInfo.StartupDirectory, @$"{ruleset_library_prefix}.*.dll"); - - foreach (string file in files.Where(f => !Path.GetFileName(f).Contains("Tests"))) - loadRulesetFromFile(file); - } - catch (Exception e) - { - Logger.Error(e, $"Could not load rulesets from directory {RuntimeInfo.StartupDirectory}"); - } - } - - private void loadRulesetFromFile(string file) - { - string? filename = Path.GetFileNameWithoutExtension(file); - - if (loadedAssemblies.Values.Any(t => Path.GetFileNameWithoutExtension(t.Assembly.Location) == filename)) - return; - - try - { - addRuleset(Assembly.LoadFrom(file)); - } - catch (Exception e) - { - Logger.Error(e, $"Failed to load ruleset {filename}"); - } - } - - private void addRuleset(Assembly assembly) - { - if (loadedAssemblies.ContainsKey(assembly)) - return; - - // the same assembly may be loaded twice in the same AppDomain (currently a thing in certain Rider versions https://youtrack.jetbrains.com/issue/RIDER-48799). - // as a failsafe, also compare by FullName. - if (loadedAssemblies.Any(a => a.Key.FullName == assembly.FullName)) - return; - - try - { - loadedAssemblies[assembly] = assembly.GetTypes().First(t => t.IsPublic && t.IsSubclassOf(typeof(Ruleset))); - } - catch (Exception e) - { - Logger.Error(e, $"Failed to add ruleset {assembly}"); - } - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - protected virtual void Dispose(bool disposing) - { - AppDomain.CurrentDomain.AssemblyResolve -= resolveRulesetDependencyAssembly; - } - - #region Implementation of IRulesetStore - - IRulesetInfo? IRulesetStore.GetRuleset(int id) => GetRuleset(id); - IRulesetInfo? IRulesetStore.GetRuleset(string shortName) => GetRuleset(shortName); - IEnumerable IRulesetStore.AvailableRulesets => AvailableRulesets; - - #endregion - } -} From 116f35c52ab361a2f3bc4802e656873696698524 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 Dec 2021 16:10:50 +0900 Subject: [PATCH 180/996] Remove EF `FileStore` --- .../Visual/Navigation/TestSceneOsuGame.cs | 4 +- osu.Game/Beatmaps/BeatmapManager.cs | 3 +- osu.Game/IO/FileStore.cs | 125 ------------------ osu.Game/OsuGameBase.cs | 6 - 4 files changed, 4 insertions(+), 134 deletions(-) delete mode 100644 osu.Game/IO/FileStore.cs diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs b/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs index 28ff776d5f..99904af42d 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs @@ -16,7 +16,6 @@ using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Input; using osu.Game.Input.Bindings; -using osu.Game.IO; using osu.Game.Online.API; using osu.Game.Online.Chat; using osu.Game.Overlays; @@ -25,6 +24,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Scoring; using osu.Game.Screens.Menu; using osu.Game.Skinning; +using osu.Game.Stores; using osu.Game.Utils; namespace osu.Game.Tests.Visual.Navigation @@ -69,7 +69,7 @@ namespace osu.Game.Tests.Visual.Navigation typeof(ISkinSource), typeof(IAPIProvider), typeof(RulesetStore), - typeof(FileStore), + typeof(RealmFileStore), typeof(ScoreManager), typeof(BeatmapManager), typeof(IRulesetConfigCache), diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 59adcfca60..a8897adf04 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -24,6 +24,7 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.Notifications; using osu.Game.Rulesets; using osu.Game.Skinning; +using osu.Game.Stores; namespace osu.Game.Beatmaps { @@ -42,7 +43,7 @@ namespace osu.Game.Beatmaps public BeatmapManager(Storage storage, RealmContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore gameResources, GameHost host = null, WorkingBeatmap defaultBeatmap = null, bool performOnlineLookups = false) { - var userResources = new FileStore(contextFactory, storage).Store; + var userResources = new RealmFileStore(contextFactory, storage).Store; BeatmapTrackStore = audioManager.GetTrackStore(userResources); diff --git a/osu.Game/IO/FileStore.cs b/osu.Game/IO/FileStore.cs deleted file mode 100644 index ebe1ebfe69..0000000000 --- a/osu.Game/IO/FileStore.cs +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using System.IO; -using System.Linq; -using osu.Framework.Extensions; -using osu.Framework.IO.Stores; -using osu.Framework.Logging; -using osu.Framework.Platform; -using osu.Game.Database; -using osu.Game.Extensions; - -namespace osu.Game.IO -{ - /// - /// Handles the Store and retrieval of Files/FileSets to the database backing - /// - public class FileStore : DatabaseBackedStore - { - public readonly IResourceStore Store; - - public new Storage Storage => base.Storage; - - public FileStore(IDatabaseContextFactory contextFactory, Storage storage) - : base(contextFactory, storage.GetStorageForDirectory(@"files")) - { - Store = new StorageBackedResourceStore(Storage); - } - - public FileInfo Add(Stream data, bool reference = true) - { - using (var usage = ContextFactory.GetForWrite()) - { - string hash = data.ComputeSHA2Hash(); - - var existing = usage.Context.FileInfo.FirstOrDefault(f => f.Hash == hash); - - var info = existing ?? new FileInfo { Hash = hash }; - - string path = info.GetStoragePath(); - - // we may be re-adding a file to fix missing store entries. - bool requiresCopy = !Storage.Exists(path); - - if (!requiresCopy) - { - // even if the file already exists, check the existing checksum for safety. - using (var stream = Storage.GetStream(path)) - requiresCopy |= stream.ComputeSHA2Hash() != hash; - } - - if (requiresCopy) - { - data.Seek(0, SeekOrigin.Begin); - - using (var output = Storage.GetStream(path, FileAccess.Write)) - data.CopyTo(output); - - data.Seek(0, SeekOrigin.Begin); - } - - if (reference || existing == null) - Reference(info); - - return info; - } - } - - public void Reference(params FileInfo[] files) - { - if (files.Length == 0) return; - - using (var usage = ContextFactory.GetForWrite()) - { - var context = usage.Context; - - foreach (var f in files.GroupBy(f => f.ID)) - { - var refetch = context.Find(f.First().ID) ?? f.First(); - refetch.ReferenceCount += f.Count(); - context.FileInfo.Update(refetch); - } - } - } - - public void Dereference(params FileInfo[] files) - { - if (files.Length == 0) return; - - using (var usage = ContextFactory.GetForWrite()) - { - var context = usage.Context; - - foreach (var f in files.GroupBy(f => f.ID)) - { - var refetch = context.FileInfo.Find(f.Key); - refetch.ReferenceCount -= f.Count(); - context.FileInfo.Update(refetch); - } - } - } - - public override void Cleanup() - { - using (var usage = ContextFactory.GetForWrite()) - { - var context = usage.Context; - - foreach (var f in context.FileInfo.Where(f => f.ReferenceCount < 1)) - { - try - { - Storage.Delete(f.GetStoragePath()); - context.FileInfo.Remove(f); - } - catch (Exception e) - { - Logger.Error(e, $@"Could not delete beatmap {f}"); - } - } - } - } - } -} diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 144ee17c29..065cec0e97 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -143,8 +143,6 @@ namespace osu.Game private UserLookupCache userCache; private BeatmapLookupCache beatmapCache; - private FileStore fileStore; - private RulesetConfigCache rulesetConfigCache; private SpectatorClient spectatorClient; @@ -226,8 +224,6 @@ namespace osu.Game var defaultBeatmap = new DummyWorkingBeatmap(Audio, Textures); - dependencies.Cache(fileStore = new FileStore(contextFactory, Storage)); - dependencies.Cache(RulesetStore = new RulesetStore(realmFactory, Storage)); // ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup() @@ -285,8 +281,6 @@ namespace osu.Game dependencies.CacheAs>(Beatmap); dependencies.CacheAs(Beatmap); - fileStore.Cleanup(); - // add api components to hierarchy. if (API is APIAccess apiAccess) AddInternal(apiAccess); From 4763fe54d6341b1797634dbbdb588a44f1139c07 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 Dec 2021 16:17:53 +0900 Subject: [PATCH 181/996] Remove unused store classes --- osu.Game/Skinning/SkinStore.cs | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 osu.Game/Skinning/SkinStore.cs diff --git a/osu.Game/Skinning/SkinStore.cs b/osu.Game/Skinning/SkinStore.cs deleted file mode 100644 index 922d146259..0000000000 --- a/osu.Game/Skinning/SkinStore.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Platform; -using osu.Game.Database; - -namespace osu.Game.Skinning -{ - public class SkinStore : MutableDatabaseBackedStoreWithFileIncludes - { - public SkinStore(DatabaseContextFactory contextFactory, Storage storage = null) - : base(contextFactory, storage) - { - } - } -} From b77bb2f12b853bf4be1e18e149984fad747262e1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 Dec 2021 16:19:38 +0900 Subject: [PATCH 182/996] Switch `BeatmapModelManager` to use `RealmArchiveModelManager` base class --- osu.Game/Beatmaps/BeatmapModelManager.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs index 2a9b215f32..b909175d2d 100644 --- a/osu.Game/Beatmaps/BeatmapModelManager.cs +++ b/osu.Game/Beatmaps/BeatmapModelManager.cs @@ -24,6 +24,7 @@ using osu.Game.IO.Archives; using osu.Game.Rulesets; using osu.Game.Rulesets.Objects; using osu.Game.Skinning; +using osu.Game.Stores; using Decoder = osu.Game.Beatmaps.Formats.Decoder; namespace osu.Game.Beatmaps @@ -32,7 +33,7 @@ namespace osu.Game.Beatmaps /// Handles ef-core storage of beatmaps. /// [ExcludeFromDynamicCompile] - public class BeatmapModelManager : ArchiveModelManager + public class BeatmapModelManager : RealmArchiveModelManager { /// /// Fired when a single difficulty has been hidden. From b8cd3cdbbc2f04b861a879201c828559a6b725ff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Nov 2021 12:16:08 +0900 Subject: [PATCH 183/996] Various updates to ruleset and primary key usages to move closer to realm support --- .../Database/BeatmapImporterTests.cs | 2 +- .../UserInterface/TestScenePlaylistOverlay.cs | 2 +- osu.Game/Beatmaps/Beatmap.cs | 2 +- osu.Game/Beatmaps/BeatmapManager.cs | 6 ++-- osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs | 22 +++++++-------- osu.Game/Beatmaps/WorkingBeatmapCache.cs | 5 ++-- osu.Game/Database/DatabaseBackedStore.cs | 4 +-- osu.Game/Database/ModelDownloader.cs | 2 +- .../Bindings/DatabasedKeyBindingContainer.cs | 2 +- osu.Game/OsuGameBase.cs | 28 ++++++------------- osu.Game/Rulesets/EFRulesetInfo.cs | 2 +- osu.Game/Screens/Edit/Verify/VerifyScreen.cs | 2 +- osu.Game/Screens/Play/Player.cs | 7 +++-- osu.Game/Screens/Play/SoloPlayer.cs | 2 +- osu.Game/Screens/Select/BeatmapCarousel.cs | 9 +++--- .../Select/Carousel/CarouselBeatmapSet.cs | 10 +++---- .../Select/Carousel/SetPanelContent.cs | 4 +-- osu.Game/Screens/Select/FilterControl.cs | 5 +--- .../Select/Leaderboards/BeatmapLeaderboard.cs | 4 +-- osu.Game/Screens/Select/SongSelect.cs | 2 +- .../Tests/Beatmaps/HitObjectSampleTest.cs | 13 +++++---- osu.Game/Tests/Visual/EditorTestScene.cs | 2 +- 22 files changed, 64 insertions(+), 73 deletions(-) diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index adee545c1d..c5dbcf155c 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -505,7 +505,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realmFactory, storage) => { using var importer = new NonOptimisedBeatmapImporter(realmFactory, storage); - using var store = new RealmRulesetStore(realmFactory, storage); + using var store = new RulesetStore(realmFactory, storage); var imported = await LoadOszIntoStore(importer, realmFactory.Context); diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs index 39146d584c..62f3b63780 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs @@ -71,7 +71,7 @@ namespace osu.Game.Tests.Visual.UserInterface InputManager.MoveMouseTo(item.ScreenSpaceDrawQuad.Centre); }); - AddAssert("song 1 is 5th", () => beatmapSets[4] == first); + AddAssert("song 1 is 5th", () => beatmapSets[4].Equals(first)); AddStep("release handle", () => InputManager.ReleaseButton(MouseButton.Left)); } diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index 435183fe92..ccecd69d21 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -62,7 +62,7 @@ namespace osu.Game.Beatmaps } [JsonIgnore] - public BeatmapMetadata Metadata => BeatmapInfo?.Metadata ?? BeatmapInfo?.BeatmapSet?.Metadata; + public BeatmapMetadata Metadata => BeatmapInfo.Metadata; public ControlPointInfo ControlPointInfo { get; set; } = new ControlPointInfo(); diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index a8897adf04..5c8cf38682 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -32,7 +32,7 @@ namespace osu.Game.Beatmaps /// Handles general operations related to global beatmap management. /// [ExcludeFromDynamicCompile] - public class BeatmapManager : IModelManager, IModelFileManager, IModelImporter, IWorkingBeatmapCache, IDisposable + public class BeatmapManager : IModelManager, IModelFileManager, IModelImporter, IWorkingBeatmapCache, IDisposable { public ITrackStore BeatmapTrackStore { get; } @@ -294,12 +294,12 @@ namespace osu.Game.Beatmaps #region Implementation of IModelFileManager - public void ReplaceFile(BeatmapSetInfo model, BeatmapSetFileInfo file, Stream contents) + public void ReplaceFile(BeatmapSetInfo model, RealmNamedFileUsage file, Stream contents) { beatmapModelManager.ReplaceFile(model, file, contents); } - public void DeleteFile(BeatmapSetInfo model, BeatmapSetFileInfo file) + public void DeleteFile(BeatmapSetInfo model, RealmNamedFileUsage file) { beatmapModelManager.DeleteFile(model, file); } diff --git a/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs b/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs index 3c4da12833..444bf09487 100644 --- a/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs +++ b/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics; using System.IO; using System.Linq; using System.Threading; @@ -16,6 +17,7 @@ using osu.Framework.Threading; using osu.Game.Database; using osu.Game.Online.API; using osu.Game.Online.API.Requests; +using osu.Game.Stores; using SharpCompress.Compressors; using SharpCompress.Compressors.BZip2; @@ -83,15 +85,14 @@ namespace osu.Game.Beatmaps if (res != null) { beatmapInfo.Status = res.Status; + + Debug.Assert(beatmapInfo.BeatmapSet != null); + beatmapInfo.BeatmapSet.Status = res.BeatmapSet?.Status ?? BeatmapOnlineStatus.None; beatmapInfo.BeatmapSet.OnlineID = res.OnlineBeatmapSetID; beatmapInfo.OnlineID = res.OnlineID; - if (beatmapInfo.Metadata != null) - beatmapInfo.Metadata.AuthorID = res.AuthorID; - - if (beatmapInfo.BeatmapSet.Metadata != null) - beatmapInfo.BeatmapSet.Metadata.AuthorID = res.AuthorID; + beatmapInfo.Metadata.Author.OnlineID = res.AuthorID; logForModel(set, $"Online retrieval mapped {beatmapInfo} to {res.OnlineBeatmapSetID} / {res.OnlineID}."); } @@ -185,15 +186,14 @@ namespace osu.Game.Beatmaps var status = (BeatmapOnlineStatus)reader.GetByte(2); beatmapInfo.Status = status; + + Debug.Assert(beatmapInfo.BeatmapSet != null); + beatmapInfo.BeatmapSet.Status = status; beatmapInfo.BeatmapSet.OnlineID = reader.GetInt32(0); beatmapInfo.OnlineID = reader.GetInt32(1); - if (beatmapInfo.Metadata != null) - beatmapInfo.Metadata.AuthorID = reader.GetInt32(3); - - if (beatmapInfo.BeatmapSet.Metadata != null) - beatmapInfo.BeatmapSet.Metadata.AuthorID = reader.GetInt32(3); + beatmapInfo.Metadata.Author.OnlineID = reader.GetInt32(3); logForModel(set, $"Cached local retrieval for {beatmapInfo}."); return true; @@ -211,7 +211,7 @@ namespace osu.Game.Beatmaps } private void logForModel(BeatmapSetInfo set, string message) => - ArchiveModelManager.LogForModel(set, $"[{nameof(BeatmapOnlineLookupQueue)}] {message}"); + RealmArchiveModelImporter.LogForModel(set, $"[{nameof(BeatmapOnlineLookupQueue)}] {message}"); public void Dispose() { diff --git a/osu.Game/Beatmaps/WorkingBeatmapCache.cs b/osu.Game/Beatmaps/WorkingBeatmapCache.cs index 514551e184..95983ae052 100644 --- a/osu.Game/Beatmaps/WorkingBeatmapCache.cs +++ b/osu.Game/Beatmaps/WorkingBeatmapCache.cs @@ -79,7 +79,7 @@ namespace osu.Game.Beatmaps // if there are no files, presume the full beatmap info has not yet been fetched from the database. if (beatmapInfo?.BeatmapSet?.Files.Count == 0) { - int lookupId = beatmapInfo.ID; + var lookupId = beatmapInfo.ID; beatmapInfo = BeatmapManager.QueryBeatmap(b => b.ID == lookupId); } @@ -93,7 +93,8 @@ namespace osu.Game.Beatmaps if (working != null) return working; - beatmapInfo.Metadata ??= beatmapInfo.BeatmapSet.Metadata; + // TODO: is this still required..? + //beatmapInfo.Metadata ??= beatmapInfo.BeatmapSet.Metadata; workingCache.Add(working = new BeatmapManagerWorkingBeatmap(beatmapInfo, this)); diff --git a/osu.Game/Database/DatabaseBackedStore.cs b/osu.Game/Database/DatabaseBackedStore.cs index 03e1c014b2..a37155ee62 100644 --- a/osu.Game/Database/DatabaseBackedStore.cs +++ b/osu.Game/Database/DatabaseBackedStore.cs @@ -11,7 +11,7 @@ namespace osu.Game.Database { protected readonly Storage Storage; - protected readonly IDatabaseContextFactory ContextFactory; + protected readonly RealmContextFactory ContextFactory; /// /// Refresh an instance potentially from a different thread with a local context-tracked instance. @@ -36,7 +36,7 @@ namespace osu.Game.Database } } - protected DatabaseBackedStore(IDatabaseContextFactory contextFactory, Storage storage = null) + protected DatabaseBackedStore(RealmContextFactory contextFactory, Storage storage = null) { ContextFactory = contextFactory; Storage = storage; diff --git a/osu.Game/Database/ModelDownloader.cs b/osu.Game/Database/ModelDownloader.cs index 362bc68cc1..2fa3357b06 100644 --- a/osu.Game/Database/ModelDownloader.cs +++ b/osu.Game/Database/ModelDownloader.cs @@ -14,7 +14,7 @@ using osu.Game.Overlays.Notifications; namespace osu.Game.Database { public abstract class ModelDownloader : IModelDownloader - where TModel : class, IHasPrimaryKey, ISoftDelete, IEquatable, T + where TModel : class, IHasGuidPrimaryKey, ISoftDelete, IEquatable, T where T : class { public Action PostNotification { protected get; set; } diff --git a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs index f95c884fe5..9482f1c0d8 100644 --- a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs +++ b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs @@ -78,7 +78,7 @@ namespace osu.Game.Input.Bindings { var defaults = DefaultKeyBindings.ToList(); - if (ruleset != null && !ruleset.ID.HasValue) + if (ruleset != null && !ruleset.IsManaged) // some tests instantiate a ruleset which is not present in the database. // in these cases we still want key bindings to work, but matching to database instances would result in none being present, // so let's populate the defaults directly. diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 065cec0e97..c4dcc35b8e 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -190,9 +190,6 @@ namespace osu.Game runMigrations(); - dependencies.Cache(RulesetStore = new RulesetStore(contextFactory, Storage)); - dependencies.CacheAs(RulesetStore); - dependencies.Cache(realmFactory = new RealmContextFactory(Storage, "client", contextFactory)); new EFToRealmMigrator(contextFactory, realmFactory, LocalConfig).Run(); @@ -227,24 +224,12 @@ namespace osu.Game dependencies.Cache(RulesetStore = new RulesetStore(realmFactory, Storage)); // ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup() - dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, contextFactory, Scheduler, Host, () => difficultyCache, LocalConfig)); - dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, contextFactory, RulesetStore, API, Audio, Resources, Host, defaultBeatmap, performOnlineLookups: true)); + dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, API, realmFactory, Scheduler, Host, () => difficultyCache, LocalConfig)); + dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, realmFactory, RulesetStore, API, Audio, Resources, Host, defaultBeatmap, performOnlineLookups: true)); dependencies.Cache(BeatmapDownloader = new BeatmapModelDownloader(BeatmapManager, API)); dependencies.Cache(ScoreDownloader = new ScoreModelDownloader(ScoreManager, API)); - // this should likely be moved to ArchiveModelManager when another case appears where it is necessary - // to have inter-dependent model managers. this could be obtained with an IHasForeign interface to - // allow lookups to be done on the child (ScoreManager in this case) to perform the cascading delete. - List getBeatmapScores(BeatmapSetInfo set) - { - var beatmapIds = BeatmapManager.QueryBeatmaps(b => b.BeatmapSetInfoID == set.ID).Select(b => b.ID).ToList(); - return ScoreManager.QueryScores(s => beatmapIds.Contains(s.BeatmapInfo.ID)).ToList(); - } - - BeatmapManager.ItemRemoved += item => ScoreManager.Delete(getBeatmapScores(item), true); - BeatmapManager.ItemUpdated += item => ScoreManager.Undelete(getBeatmapScores(item), true); - dependencies.Cache(difficultyCache = new BeatmapDifficultyCache()); AddInternal(difficultyCache); @@ -438,7 +423,9 @@ namespace osu.Game private void onRulesetChanged(ValueChangedEvent r) { - if (r.NewValue?.Available != true) + Ruleset instance; + + if (r.NewValue?.Available != true || (instance = r.NewValue.CreateInstance()) == null) { // reject the change if the ruleset is not available. Ruleset.Value = r.OldValue?.Available == true ? r.OldValue : RulesetStore.AvailableRulesets.First(); @@ -448,7 +435,9 @@ namespace osu.Game var dict = new Dictionary>(); foreach (ModType type in Enum.GetValues(typeof(ModType))) - dict[type] = r.NewValue.CreateInstance().GetModsFor(type).ToList(); + { + dict[type] = instance.GetModsFor(type).ToList(); + } if (!SelectedMods.Disabled) SelectedMods.Value = Array.Empty(); @@ -489,7 +478,6 @@ namespace osu.Game contextFactory?.FlushConnections(); - realmRulesetStore?.Dispose(); realmFactory?.Dispose(); } } diff --git a/osu.Game/Rulesets/EFRulesetInfo.cs b/osu.Game/Rulesets/EFRulesetInfo.cs index 36e2add557..a7070ad2b6 100644 --- a/osu.Game/Rulesets/EFRulesetInfo.cs +++ b/osu.Game/Rulesets/EFRulesetInfo.cs @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets throw new RulesetLoadException(@"Instantiation failure"); // overwrite the pre-populated RulesetInfo with a potentially database attached copy. - ruleset.RulesetInfo = this; + // ruleset.RulesetInfo = this; return ruleset; } diff --git a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs index 6d7a4a72e2..08643eb8c1 100644 --- a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs +++ b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs @@ -29,7 +29,7 @@ namespace osu.Game.Screens.Edit.Verify [BackgroundDependencyLoader] private void load() { - InterpretedDifficulty.Default = EditorBeatmap.BeatmapInfo.DifficultyRating; + InterpretedDifficulty.Default = BeatmapDifficultyCache.GetDifficultyRating(EditorBeatmap.BeatmapInfo.StarRating); InterpretedDifficulty.SetDefault(); IssueList = new IssueList(); diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 2a6f5e2398..a66026b423 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -226,8 +226,8 @@ namespace osu.Game.Screens.Play // ensure the score is in a consistent state with the current player. Score.ScoreInfo.BeatmapInfo = Beatmap.Value.BeatmapInfo; Score.ScoreInfo.Ruleset = ruleset.RulesetInfo; - if (ruleset.RulesetInfo.ID != null) - Score.ScoreInfo.RulesetID = ruleset.RulesetInfo.ID.Value; + if (ruleset.RulesetInfo.OnlineID >= 0) + Score.ScoreInfo.RulesetID = ruleset.RulesetInfo.OnlineID; Score.ScoreInfo.Mods = gameplayMods; dependencies.CacheAs(GameplayState = new GameplayState(playableBeatmap, ruleset, gameplayMods, Score)); @@ -488,6 +488,9 @@ namespace osu.Game.Screens.Play var rulesetInfo = Ruleset.Value ?? Beatmap.Value.BeatmapInfo.Ruleset; ruleset = rulesetInfo.CreateInstance(); + if (ruleset == null) + throw new RulesetLoadException("Instantiation failure"); + try { playable = Beatmap.Value.GetPlayableBeatmap(ruleset.RulesetInfo, gameplayMods); diff --git a/osu.Game/Screens/Play/SoloPlayer.cs b/osu.Game/Screens/Play/SoloPlayer.cs index c8d831ebe6..eced2d142b 100644 --- a/osu.Game/Screens/Play/SoloPlayer.cs +++ b/osu.Game/Screens/Play/SoloPlayer.cs @@ -26,7 +26,7 @@ namespace osu.Game.Screens.Play protected override APIRequest CreateTokenRequest() { - int beatmapId = Beatmap.Value.BeatmapInfo.OnlineID ?? -1; + int beatmapId = Beatmap.Value.BeatmapInfo.OnlineID; int rulesetId = Ruleset.Value.OnlineID; if (beatmapId <= 0) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index b0d0821ee9..7e292e2de7 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -197,7 +197,8 @@ namespace osu.Game.Screens.Select public void UpdateBeatmapSet(BeatmapSetInfo beatmapSet) => Schedule(() => { - int? previouslySelectedID = null; + Guid? previouslySelectedID = null; + CarouselBeatmapSet existingSet = beatmapSets.FirstOrDefault(b => b.BeatmapSet.Equals(beatmapSet)); // If the selected beatmap is about to be removed, store its ID so it can be re-selected if required @@ -626,9 +627,9 @@ namespace osu.Game.Screens.Select if (beatmapSet.Beatmaps.All(b => b.Hidden)) return null; - // todo: remove the need for this. - foreach (var b in beatmapSet.Beatmaps) - b.Metadata ??= beatmapSet.Metadata; + // todo: probably not required any more. + // foreach (var b in beatmapSet.Beatmaps) + // b.Metadata ??= beatmapSet.Metadata; var set = new CarouselBeatmapSet(beatmapSet) { diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index 9e411d5daa..a1a8de04ae 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -48,7 +48,7 @@ namespace osu.Game.Screens.Select.Carousel if (LastSelected == null || LastSelected.Filtered.Value) { if (GetRecommendedBeatmap?.Invoke(Children.OfType().Where(b => !b.Filtered.Value).Select(b => b.BeatmapInfo)) is BeatmapInfo recommended) - return Children.OfType().First(b => b.BeatmapInfo == recommended); + return Children.OfType().First(b => b.BeatmapInfo.Equals(recommended)); } return base.GetNextToSelect(); @@ -63,16 +63,16 @@ namespace osu.Game.Screens.Select.Carousel { default: case SortMode.Artist: - return string.Compare(BeatmapSet.Metadata.Artist, otherSet.BeatmapSet.Metadata.Artist, StringComparison.OrdinalIgnoreCase); + return string.Compare(BeatmapSet.Metadata?.Artist, otherSet.BeatmapSet.Metadata?.Artist, StringComparison.OrdinalIgnoreCase); case SortMode.Title: - return string.Compare(BeatmapSet.Metadata.Title, otherSet.BeatmapSet.Metadata.Title, StringComparison.OrdinalIgnoreCase); + return string.Compare(BeatmapSet.Metadata?.Title, otherSet.BeatmapSet.Metadata?.Title, StringComparison.OrdinalIgnoreCase); case SortMode.Author: - return string.Compare(BeatmapSet.Metadata.Author.Username, otherSet.BeatmapSet.Metadata.Author.Username, StringComparison.OrdinalIgnoreCase); + return string.Compare(BeatmapSet.Metadata?.Author.Username, otherSet.BeatmapSet.Metadata?.Author.Username, StringComparison.OrdinalIgnoreCase); case SortMode.Source: - return string.Compare(BeatmapSet.Metadata.Source, otherSet.BeatmapSet.Metadata.Source, StringComparison.OrdinalIgnoreCase); + return string.Compare(BeatmapSet.Metadata?.Source, otherSet.BeatmapSet.Metadata?.Source, StringComparison.OrdinalIgnoreCase); case SortMode.DateAdded: return otherSet.BeatmapSet.DateAdded.CompareTo(BeatmapSet.DateAdded); diff --git a/osu.Game/Screens/Select/Carousel/SetPanelContent.cs b/osu.Game/Screens/Select/Carousel/SetPanelContent.cs index f2054677b0..9aa85c872e 100644 --- a/osu.Game/Screens/Select/Carousel/SetPanelContent.cs +++ b/osu.Game/Screens/Select/Carousel/SetPanelContent.cs @@ -41,13 +41,13 @@ namespace osu.Game.Screens.Select.Carousel { new OsuSpriteText { - Text = new RomanisableString(beatmapSet.Metadata.TitleUnicode, beatmapSet.Metadata.Title), + Text = new RomanisableString(beatmapSet.Metadata?.TitleUnicode, beatmapSet.Metadata?.Title), Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 22, italics: true), Shadow = true, }, new OsuSpriteText { - Text = new RomanisableString(beatmapSet.Metadata.ArtistUnicode, beatmapSet.Metadata.Artist), + Text = new RomanisableString(beatmapSet.Metadata?.ArtistUnicode, beatmapSet.Metadata?.Artist), Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 17, italics: true), Shadow = true, }, diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index e95bd7f653..e54b25873b 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -37,8 +36,6 @@ namespace osu.Game.Screens.Select public FilterCriteria CreateCriteria() { - Debug.Assert(ruleset.Value.ID != null); - string query = searchTextBox.Text; var criteria = new FilterCriteria @@ -56,7 +53,7 @@ namespace osu.Game.Screens.Select if (!maximumStars.IsDefault) criteria.UserStarDifficulty.Max = maximumStars.Value; - criteria.RulesetCriteria = ruleset.Value.CreateInstance().CreateRulesetFilterCriteria(); + criteria.RulesetCriteria = ruleset.Value.CreateInstance()?.CreateRulesetFilterCriteria(); FilterQueryParser.ApplyQueries(criteria, query); return criteria; diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 0102986070..3b25d34aec 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -33,7 +33,7 @@ namespace osu.Game.Screens.Select.Leaderboards get => beatmapInfo; set { - if (beatmapInfo == value) + if (beatmapInfo.Equals(value)) return; beatmapInfo = value; @@ -154,7 +154,7 @@ namespace osu.Game.Screens.Select.Leaderboards return null; } - if (fetchBeatmapInfo.OnlineID == null || fetchBeatmapInfo.Status <= BeatmapOnlineStatus.Pending) + if (fetchBeatmapInfo.OnlineID <= 0 || fetchBeatmapInfo.Status <= BeatmapOnlineStatus.Pending) { PlaceholderState = PlaceholderState.Unavailable; return null; diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 08ad9f2ec0..a40e8e2bde 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -482,7 +482,7 @@ namespace osu.Game.Screens.Select else selectionChangedDebounce = Scheduler.AddDelayed(run, 200); - if (beatmap != beatmapInfoPrevious) + if (!beatmap.Equals(beatmapInfoPrevious)) { if (beatmap != null && beatmapInfoPrevious != null && Time.Current - audioFeedbackLastPlaybackTime >= 50) { diff --git a/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs b/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs index 27d1de83ec..4e955975a0 100644 --- a/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs +++ b/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Threading; @@ -78,7 +79,11 @@ namespace osu.Game.Tests.Beatmaps currentTestBeatmap = Decoder.GetDecoder(reader).Decode(reader); // populate ruleset for beatmap converters that require it to be present. - currentTestBeatmap.BeatmapInfo.Ruleset = rulesetStore.GetRuleset(currentTestBeatmap.BeatmapInfo.RulesetID); + var ruleset = rulesetStore.GetRuleset(currentTestBeatmap.BeatmapInfo.RulesetID); + + Debug.Assert(ruleset != null); + + currentTestBeatmap.BeatmapInfo.Ruleset = ruleset; }); }); @@ -94,11 +99,7 @@ namespace osu.Game.Tests.Beatmaps userSkinInfo.Files.Add(new RealmNamedFileUsage(new RealmFile { Hash = userFile }, userFile)); beatmapInfo.BeatmapSet.Files.Clear(); - beatmapInfo.BeatmapSet.Files.Add(new BeatmapSetFileInfo - { - Filename = beatmapFile, - FileInfo = new IO.FileInfo { Hash = beatmapFile } - }); + beatmapInfo.BeatmapSet.Files.Add(new RealmNamedFileUsage(new RealmFile { Hash = beatmapFile }, beatmapFile)); // Need to refresh the cached skin source to refresh the skin resource store. dependencies.SkinSource = new SkinProvidingContainer(Skin = new LegacySkin(userSkinInfo, this)); diff --git a/osu.Game/Tests/Visual/EditorTestScene.cs b/osu.Game/Tests/Visual/EditorTestScene.cs index 07152b5a3e..a2f567f3b0 100644 --- a/osu.Game/Tests/Visual/EditorTestScene.cs +++ b/osu.Game/Tests/Visual/EditorTestScene.cs @@ -116,7 +116,7 @@ namespace osu.Game.Tests.Visual { private readonly WorkingBeatmap testBeatmap; - public TestBeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore resources, GameHost host, WorkingBeatmap defaultBeatmap, WorkingBeatmap testBeatmap) + public TestBeatmapManager(Storage storage, RealmContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore resources, GameHost host, WorkingBeatmap defaultBeatmap, WorkingBeatmap testBeatmap) : base(storage, contextFactory, rulesets, api, audioManager, resources, host, defaultBeatmap) { this.testBeatmap = testBeatmap; From 8c0db79ec1803485d74849e18551e4c06d4ee21f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 8 Dec 2021 15:59:04 +0900 Subject: [PATCH 184/996] Remove `BeatmapStore` and update surrounding code --- osu.Game/Beatmaps/BeatmapModelManager.cs | 17 ++-- osu.Game/Beatmaps/BeatmapStore.cs | 117 ----------------------- 2 files changed, 8 insertions(+), 126 deletions(-) delete mode 100644 osu.Game/Beatmaps/BeatmapStore.cs diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs index b909175d2d..f6f2e16410 100644 --- a/osu.Game/Beatmaps/BeatmapModelManager.cs +++ b/osu.Game/Beatmaps/BeatmapModelManager.cs @@ -25,6 +25,7 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Objects; using osu.Game.Skinning; using osu.Game.Stores; +using Realms; using Decoder = osu.Game.Beatmaps.Formats.Decoder; namespace osu.Game.Beatmaps @@ -59,24 +60,22 @@ namespace osu.Game.Beatmaps protected override string[] HashableFileTypes => new[] { ".osu" }; - private readonly BeatmapStore beatmaps; private readonly RulesetStore rulesets; - public BeatmapModelManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, GameHost host = null) - : base(storage, contextFactory, new BeatmapStore(contextFactory), host) + public BeatmapModelManager(Storage storage, RealmContextFactory contextFactory, RulesetStore rulesets, GameHost host = null) + : base(storage, contextFactory) { this.rulesets = rulesets; - beatmaps = (BeatmapStore)ModelStore; - beatmaps.BeatmapHidden += b => BeatmapHidden?.Invoke(b); - beatmaps.BeatmapRestored += b => BeatmapRestored?.Invoke(b); - beatmaps.ItemRemoved += b => WorkingBeatmapCache?.Invalidate(b); - beatmaps.ItemUpdated += obj => WorkingBeatmapCache?.Invalidate(obj); + // beatmaps.BeatmapHidden += b => BeatmapHidden?.Invoke(b); + // beatmaps.BeatmapRestored += b => BeatmapRestored?.Invoke(b); + // beatmaps.ItemRemoved += b => WorkingBeatmapCache?.Invalidate(b); + // beatmaps.ItemUpdated += obj => WorkingBeatmapCache?.Invalidate(obj); } protected override bool ShouldDeleteArchive(string path) => Path.GetExtension(path)?.ToLowerInvariant() == ".osz"; - protected override async Task Populate(BeatmapSetInfo beatmapSet, ArchiveReader archive, CancellationToken cancellationToken = default) + protected override async Task Populate(BeatmapSetInfo beatmapSet, ArchiveReader? archive, Realm realm, CancellationToken cancellationToken) { if (archive != null) beatmapSet.Beatmaps.AddRange(createBeatmapDifficulties(beatmapSet.Files)); diff --git a/osu.Game/Beatmaps/BeatmapStore.cs b/osu.Game/Beatmaps/BeatmapStore.cs deleted file mode 100644 index 197581db88..0000000000 --- a/osu.Game/Beatmaps/BeatmapStore.cs +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using System.Collections.Generic; -using System.Linq; -using Microsoft.EntityFrameworkCore; -using osu.Game.Database; - -namespace osu.Game.Beatmaps -{ - /// - /// Handles the storage and retrieval of Beatmaps/BeatmapSets to the database backing - /// - public class BeatmapStore : MutableDatabaseBackedStoreWithFileIncludes - { - public event Action BeatmapHidden; - public event Action BeatmapRestored; - - public BeatmapStore(IDatabaseContextFactory factory) - : base(factory) - { - } - - /// - /// Hide a in the database. - /// - /// The beatmap to hide. - /// Whether the beatmap's was changed. - public bool Hide(BeatmapInfo beatmapInfo) - { - using (ContextFactory.GetForWrite()) - { - Refresh(ref beatmapInfo, Beatmaps); - - if (beatmapInfo.Hidden) return false; - - beatmapInfo.Hidden = true; - } - - BeatmapHidden?.Invoke(beatmapInfo); - return true; - } - - /// - /// Restore a previously hidden . - /// - /// The beatmap to restore. - /// Whether the beatmap's was changed. - public bool Restore(BeatmapInfo beatmapInfo) - { - using (ContextFactory.GetForWrite()) - { - Refresh(ref beatmapInfo, Beatmaps); - - if (!beatmapInfo.Hidden) return false; - - beatmapInfo.Hidden = false; - } - - BeatmapRestored?.Invoke(beatmapInfo); - return true; - } - - protected override IQueryable AddIncludesForDeletion(IQueryable query) => - base.AddIncludesForDeletion(query) - .Include(s => s.Metadata) - .Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty) - .Include(s => s.Beatmaps).ThenInclude(b => b.Metadata); - - protected override IQueryable AddIncludesForConsumption(IQueryable query) => - base.AddIncludesForConsumption(query) - .Include(s => s.Metadata) - .Include(s => s.Beatmaps).ThenInclude(s => s.Ruleset) - .Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty) - .Include(s => s.Beatmaps).ThenInclude(b => b.Metadata); - - protected override void Purge(List items, OsuDbContext context) - { - // metadata is M-N so we can't rely on cascades - context.BeatmapMetadata.RemoveRange(items.Select(s => s.Metadata)); - context.BeatmapMetadata.RemoveRange(items.SelectMany(s => s.Beatmaps.Select(b => b.Metadata).Where(m => m != null))); - - // todo: we can probably make cascades work here with a FK in BeatmapDifficulty. just make to make it work correctly. - context.BeatmapDifficulty.RemoveRange(items.SelectMany(s => s.Beatmaps.Select(b => b.BaseDifficulty))); - - base.Purge(items, context); - } - - public IQueryable BeatmapSetsOverview => ContextFactory.Get().BeatmapSetInfo - .Include(s => s.Metadata) - .Include(s => s.Beatmaps) - .AsNoTracking(); - - public IQueryable BeatmapSetsWithoutRuleset => ContextFactory.Get().BeatmapSetInfo - .Include(s => s.Metadata) - .Include(s => s.Files).ThenInclude(f => f.FileInfo) - .Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty) - .Include(s => s.Beatmaps).ThenInclude(b => b.Metadata) - .AsNoTracking(); - - public IQueryable BeatmapSetsWithoutFiles => ContextFactory.Get().BeatmapSetInfo - .Include(s => s.Metadata) - .Include(s => s.Beatmaps).ThenInclude(s => s.Ruleset) - .Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty) - .Include(s => s.Beatmaps).ThenInclude(b => b.Metadata) - .AsNoTracking(); - - public IQueryable Beatmaps => - ContextFactory.Get().BeatmapInfo - .Include(b => b.BeatmapSet).ThenInclude(s => s.Metadata) - .Include(b => b.BeatmapSet).ThenInclude(s => s.Files).ThenInclude(f => f.FileInfo) - .Include(b => b.Metadata) - .Include(b => b.Ruleset) - .Include(b => b.BaseDifficulty); - } -} From 0dd23c46b04caebae04a115ce6c7f1591ae21e6f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 11 Nov 2021 23:18:37 +0900 Subject: [PATCH 185/996] Add basic `RealmScore` implementation --- osu.Game/Models/RealmScore.cs | 68 +++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 osu.Game/Models/RealmScore.cs diff --git a/osu.Game/Models/RealmScore.cs b/osu.Game/Models/RealmScore.cs new file mode 100644 index 0000000000..323bd2393c --- /dev/null +++ b/osu.Game/Models/RealmScore.cs @@ -0,0 +1,68 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Database; +using osu.Game.Rulesets; +using osu.Game.Scoring; +using osu.Game.Users; +using Realms; + +#nullable enable + +namespace osu.Game.Models +{ + [ExcludeFromDynamicCompile] + [MapTo("Score")] + public class RealmScore : RealmObject, IHasGuidPrimaryKey, IHasRealmFiles, ISoftDelete, IEquatable, IScoreInfo + { + [PrimaryKey] + public Guid ID { get; set; } = Guid.NewGuid(); + + public IList Files { get; } = null!; + + public string Hash { get; set; } = string.Empty; + + public bool DeletePending { get; set; } + + public bool Equals(RealmScore other) => other.ID == ID; + + [Indexed] + public long OnlineID { get; set; } = -1; + + public RealmUser User { get; set; } = null!; + + public long TotalScore { get; set; } + + public int MaxCombo { get; set; } + + public double Accuracy { get; set; } + + public bool HasReplay { get; set; } + + public DateTimeOffset Date { get; set; } + + public double? PP { get; set; } = null; + + public RealmBeatmap Beatmap { get; set; } = null!; + + public RealmRuleset Ruleset { get; set; } = null!; + + public ScoreRank Rank + { + get => (ScoreRank)RankInt; + set => RankInt = (int)value; + } + + [MapTo(nameof(Rank))] + public int RankInt { get; set; } + + IRulesetInfo IScoreInfo.Ruleset => Ruleset; + IBeatmapInfo IScoreInfo.Beatmap => Beatmap; + IUser IScoreInfo.User => User; + IEnumerable IHasNamedFiles.Files => Files; + } +} From a5df01ff47af0d44af5852a462628e3dab998236 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Nov 2021 13:07:11 +0900 Subject: [PATCH 186/996] Add score importer --- osu.Game/Beatmaps/IBeatmapSetOnlineInfo.cs | 2 +- osu.Game/Stores/ScoreImporter.cs | 64 ++++++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Stores/ScoreImporter.cs diff --git a/osu.Game/Beatmaps/IBeatmapSetOnlineInfo.cs b/osu.Game/Beatmaps/IBeatmapSetOnlineInfo.cs index 0510770d5b..33d8929008 100644 --- a/osu.Game/Beatmaps/IBeatmapSetOnlineInfo.cs +++ b/osu.Game/Beatmaps/IBeatmapSetOnlineInfo.cs @@ -28,7 +28,7 @@ namespace osu.Game.Beatmaps DateTimeOffset? LastUpdated { get; } /// - /// The status of this beatmap set. + /// The "ranked" status of this beatmap set. /// BeatmapOnlineStatus Status { get; } diff --git a/osu.Game/Stores/ScoreImporter.cs b/osu.Game/Stores/ScoreImporter.cs new file mode 100644 index 0000000000..b30dd88fbe --- /dev/null +++ b/osu.Game/Stores/ScoreImporter.cs @@ -0,0 +1,64 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using osu.Framework.Logging; +using osu.Framework.Platform; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Database; +using osu.Game.IO.Archives; +using osu.Game.Models; +using osu.Game.Scoring.Legacy; +using Realms; + +#nullable enable + +namespace osu.Game.Stores +{ + /// + /// Handles the storage and retrieval of Scores/WorkingScores. + /// + [ExcludeFromDynamicCompile] + public class ScoreImporter : RealmArchiveModelImporter + { + private readonly RealmRulesetStore rulesets; + private readonly BeatmapManager beatmaps; + + public override IEnumerable HandledExtensions => new[] { ".osr" }; + + protected override string[] HashableFileTypes => new[] { ".osr" }; + + public ScoreImporter(RealmRulesetStore rulesets, RealmContextFactory contextFactory, Storage storage, BeatmapManager beatmaps) + : base(storage, contextFactory) + { + this.rulesets = rulesets; + this.beatmaps = beatmaps; + } + + protected override RealmScore? CreateModel(ArchiveReader archive) + { + using (var stream = archive.GetStream(archive.Filenames.First(f => f.EndsWith(".osr", StringComparison.OrdinalIgnoreCase)))) + { + try + { + // TODO: make work. + // return new DatabasedLegacyScoreDecoder(rulesets, beatmaps).Parse(stream).ScoreInfo; + return new RealmScore(); + } + catch (LegacyScoreDecoder.BeatmapNotFoundException e) + { + Logger.Log(e.Message, LoggingTarget.Information, LogLevel.Error); + return null; + } + } + } + + protected override Task Populate(RealmScore model, ArchiveReader? archive, Realm realm, CancellationToken cancellationToken = default) + => Task.CompletedTask; + } +} From c5e401d6788bf7219b5118186b1c4b866c956deb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 3 Dec 2021 17:50:40 +0900 Subject: [PATCH 187/996] Update usages to consume `IRulesetStore` --- osu.Game/Scoring/Legacy/DatabasedLegacyScoreDecoder.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Scoring/Legacy/DatabasedLegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/DatabasedLegacyScoreDecoder.cs index 9b590f56dd..a49eb89ecc 100644 --- a/osu.Game/Scoring/Legacy/DatabasedLegacyScoreDecoder.cs +++ b/osu.Game/Scoring/Legacy/DatabasedLegacyScoreDecoder.cs @@ -12,10 +12,10 @@ namespace osu.Game.Scoring.Legacy /// public class DatabasedLegacyScoreDecoder : LegacyScoreDecoder { - private readonly RulesetStore rulesets; + private readonly IRulesetStore rulesets; private readonly BeatmapManager beatmaps; - public DatabasedLegacyScoreDecoder(RulesetStore rulesets, BeatmapManager beatmaps) + public DatabasedLegacyScoreDecoder(IRulesetStore rulesets, BeatmapManager beatmaps) { this.rulesets = rulesets; this.beatmaps = beatmaps; From 3da762e1457ac3631c1d6a4830b7c8fe8129692f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 6 Dec 2021 15:31:40 +0900 Subject: [PATCH 188/996] Replace EF `ScoreInfo` with realm version May contain errors. --- osu.Game/Database/OsuDbContext.cs | 2 +- osu.Game/Models/RealmScore.cs | 68 -------- osu.Game/Scoring/EFScoreInfo.cs | 265 ++++++++++++++++++++++++++++ osu.Game/Scoring/ScoreInfo.cs | 278 +++++------------------------- osu.Game/Stores/ScoreImporter.cs | 9 +- 5 files changed, 310 insertions(+), 312 deletions(-) delete mode 100644 osu.Game/Models/RealmScore.cs create mode 100644 osu.Game/Scoring/EFScoreInfo.cs diff --git a/osu.Game/Database/OsuDbContext.cs b/osu.Game/Database/OsuDbContext.cs index 9f0bae4a66..fb83592c01 100644 --- a/osu.Game/Database/OsuDbContext.cs +++ b/osu.Game/Database/OsuDbContext.cs @@ -26,7 +26,7 @@ namespace osu.Game.Database public DbSet FileInfo { get; set; } public DbSet RulesetInfo { get; set; } public DbSet SkinInfo { get; set; } - public DbSet ScoreInfo { get; set; } + public DbSet ScoreInfo { get; set; } // migrated to realm public DbSet DatabasedSetting { get; set; } diff --git a/osu.Game/Models/RealmScore.cs b/osu.Game/Models/RealmScore.cs deleted file mode 100644 index 323bd2393c..0000000000 --- a/osu.Game/Models/RealmScore.cs +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using System.Collections.Generic; -using osu.Framework.Testing; -using osu.Game.Beatmaps; -using osu.Game.Database; -using osu.Game.Rulesets; -using osu.Game.Scoring; -using osu.Game.Users; -using Realms; - -#nullable enable - -namespace osu.Game.Models -{ - [ExcludeFromDynamicCompile] - [MapTo("Score")] - public class RealmScore : RealmObject, IHasGuidPrimaryKey, IHasRealmFiles, ISoftDelete, IEquatable, IScoreInfo - { - [PrimaryKey] - public Guid ID { get; set; } = Guid.NewGuid(); - - public IList Files { get; } = null!; - - public string Hash { get; set; } = string.Empty; - - public bool DeletePending { get; set; } - - public bool Equals(RealmScore other) => other.ID == ID; - - [Indexed] - public long OnlineID { get; set; } = -1; - - public RealmUser User { get; set; } = null!; - - public long TotalScore { get; set; } - - public int MaxCombo { get; set; } - - public double Accuracy { get; set; } - - public bool HasReplay { get; set; } - - public DateTimeOffset Date { get; set; } - - public double? PP { get; set; } = null; - - public RealmBeatmap Beatmap { get; set; } = null!; - - public RealmRuleset Ruleset { get; set; } = null!; - - public ScoreRank Rank - { - get => (ScoreRank)RankInt; - set => RankInt = (int)value; - } - - [MapTo(nameof(Rank))] - public int RankInt { get; set; } - - IRulesetInfo IScoreInfo.Ruleset => Ruleset; - IBeatmapInfo IScoreInfo.Beatmap => Beatmap; - IUser IScoreInfo.User => User; - IEnumerable IHasNamedFiles.Files => Files; - } -} diff --git a/osu.Game/Scoring/EFScoreInfo.cs b/osu.Game/Scoring/EFScoreInfo.cs new file mode 100644 index 0000000000..e23662f74d --- /dev/null +++ b/osu.Game/Scoring/EFScoreInfo.cs @@ -0,0 +1,265 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using Newtonsoft.Json; +using osu.Framework.Localisation; +using osu.Game.Beatmaps; +using osu.Game.Database; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Scoring; +using osu.Game.Users; +using osu.Game.Utils; + +namespace osu.Game.Scoring +{ + public class EFScoreInfo : IScoreInfo, IHasFiles, IHasPrimaryKey, ISoftDelete, IEquatable, IDeepCloneable + { + public int ID { get; set; } + + public ScoreRank Rank { get; set; } + + public long TotalScore { get; set; } + + [Column(TypeName = "DECIMAL(1,4)")] // TODO: This data type is wrong (should contain more precision). But at the same time, we probably don't need to be storing this in the database. + public double Accuracy { get; set; } + + public LocalisableString DisplayAccuracy => Accuracy.FormatAccuracy(); + + public double? PP { get; set; } + + public int MaxCombo { get; set; } + + public int Combo { get; set; } // Todo: Shouldn't exist in here + + public int RulesetID { get; set; } + + [NotMapped] + public bool Passed { get; set; } = true; + + public RulesetInfo Ruleset { get; set; } + + private APIMod[] localAPIMods; + + private Mod[] mods; + + [NotMapped] + public Mod[] Mods + { + get + { + var rulesetInstance = Ruleset?.CreateInstance(); + if (rulesetInstance == null) + return mods ?? Array.Empty(); + + Mod[] scoreMods = Array.Empty(); + + if (mods != null) + scoreMods = mods; + else if (localAPIMods != null) + scoreMods = APIMods.Select(m => m.ToMod(rulesetInstance)).ToArray(); + + return scoreMods; + } + set + { + localAPIMods = null; + mods = value; + } + } + + // Used for API serialisation/deserialisation. + [NotMapped] + public APIMod[] APIMods + { + get + { + if (localAPIMods != null) + return localAPIMods; + + if (mods == null) + return Array.Empty(); + + return localAPIMods = mods.Select(m => new APIMod(m)).ToArray(); + } + set + { + localAPIMods = value; + + // We potentially can't update this yet due to Ruleset being late-bound, so instead update on read as necessary. + mods = null; + } + } + + // Used for database serialisation/deserialisation. + [Column("Mods")] + public string ModsJson + { + get => JsonConvert.SerializeObject(APIMods); + set => APIMods = JsonConvert.DeserializeObject(value); + } + + [NotMapped] + public APIUser User { get; set; } + + [Column("User")] + public string UserString + { + get => User?.Username; + set + { + User ??= new APIUser(); + User.Username = value; + } + } + + [Column("UserID")] + public int? UserID + { + get => User?.Id ?? 1; + set + { + User ??= new APIUser(); + User.Id = value ?? 1; + } + } + + public int BeatmapInfoID { get; set; } + + [Column("Beatmap")] + public BeatmapInfo BeatmapInfo { get; set; } + + public long? OnlineScoreID { get; set; } + + public DateTimeOffset Date { get; set; } + + [NotMapped] + public Dictionary Statistics { get; set; } = new Dictionary(); + + [Column("Statistics")] + public string StatisticsJson + { + get => JsonConvert.SerializeObject(Statistics); + set + { + if (value == null) + { + Statistics.Clear(); + return; + } + + Statistics = JsonConvert.DeserializeObject>(value); + } + } + + [NotMapped] + public List HitEvents { get; set; } + + public List Files { get; } = new List(); + + public string Hash { get; set; } + + public bool DeletePending { get; set; } + + /// + /// The position of this score, starting at 1. + /// + [NotMapped] + public int? Position { get; set; } // TODO: remove after all calls to `CreateScoreInfo` are gone. + + /// + /// Whether this represents a legacy (osu!stable) score. + /// + [NotMapped] + public bool IsLegacyScore => Mods.OfType().Any(); + + public IEnumerable GetStatisticsForDisplay() + { + foreach (var r in Ruleset.CreateInstance().GetHitResults()) + { + int value = Statistics.GetValueOrDefault(r.result); + + switch (r.result) + { + case HitResult.SmallTickHit: + { + int total = value + Statistics.GetValueOrDefault(HitResult.SmallTickMiss); + if (total > 0) + yield return new HitResultDisplayStatistic(r.result, value, total, r.displayName); + + break; + } + + case HitResult.LargeTickHit: + { + int total = value + Statistics.GetValueOrDefault(HitResult.LargeTickMiss); + if (total > 0) + yield return new HitResultDisplayStatistic(r.result, value, total, r.displayName); + + break; + } + + case HitResult.SmallTickMiss: + case HitResult.LargeTickMiss: + break; + + default: + yield return new HitResultDisplayStatistic(r.result, value, null, r.displayName); + + break; + } + } + } + + public EFScoreInfo DeepClone() + { + var clone = (EFScoreInfo)MemberwiseClone(); + + clone.Statistics = new Dictionary(clone.Statistics); + + return clone; + } + + public override string ToString() => this.GetDisplayTitle(); + + public bool Equals(EFScoreInfo other) + { + if (other == null) + return false; + + if (ID != 0 && other.ID != 0) + return ID == other.ID; + + if (OnlineScoreID.HasValue && other.OnlineScoreID.HasValue) + return OnlineScoreID == other.OnlineScoreID; + + if (!string.IsNullOrEmpty(Hash) && !string.IsNullOrEmpty(other.Hash)) + return Hash == other.Hash; + + return ReferenceEquals(this, other); + } + + #region Implementation of IHasOnlineID + + public long OnlineID => OnlineScoreID ?? -1; + + #endregion + + #region Implementation of IScoreInfo + + IBeatmapInfo IScoreInfo.Beatmap => BeatmapInfo; + IRulesetInfo IScoreInfo.Ruleset => Ruleset; + IUser IScoreInfo.User => User; + bool IScoreInfo.HasReplay => Files.Any(); + + #endregion + + IEnumerable IHasNamedFiles.Files => Files; + } +} diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 7acc7bd055..0c097d1294 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -3,266 +3,66 @@ using System; using System.Collections.Generic; -using System.ComponentModel.DataAnnotations.Schema; -using System.Linq; -using Newtonsoft.Json; -using osu.Framework.Localisation; +using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Database; -using osu.Game.Online.API; -using osu.Game.Online.API.Requests.Responses; +using osu.Game.Models; using osu.Game.Rulesets; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Scoring; using osu.Game.Users; -using osu.Game.Utils; +using Realms; + +#nullable enable namespace osu.Game.Scoring { - public class ScoreInfo : IScoreInfo, IHasFiles, IHasPrimaryKey, ISoftDelete, IEquatable, IDeepCloneable + [ExcludeFromDynamicCompile] + [MapTo("Score")] + public class ScoreInfo : RealmObject, IHasGuidPrimaryKey, IHasRealmFiles, ISoftDelete, IEquatable, IScoreInfo { - public int ID { get; set; } + [PrimaryKey] + public Guid ID { get; set; } = Guid.NewGuid(); - public bool IsManaged => ID > 0; + public IList Files { get; } = null!; - public ScoreRank Rank { get; set; } - - public long TotalScore { get; set; } - - [Column(TypeName = "DECIMAL(1,4)")] // TODO: This data type is wrong (should contain more precision). But at the same time, we probably don't need to be storing this in the database. - public double Accuracy { get; set; } - - public LocalisableString DisplayAccuracy => Accuracy.FormatAccuracy(); - - public double? PP { get; set; } - - public int MaxCombo { get; set; } - - public int Combo { get; set; } // Todo: Shouldn't exist in here - - public int RulesetID { get; set; } - - [NotMapped] - public bool Passed { get; set; } = true; - - public RulesetInfo Ruleset { get; set; } - - private APIMod[] localAPIMods; - - private Mod[] mods; - - [NotMapped] - public Mod[] Mods - { - get - { - var rulesetInstance = Ruleset?.CreateInstance(); - if (rulesetInstance == null) - return mods ?? Array.Empty(); - - Mod[] scoreMods = Array.Empty(); - - if (mods != null) - scoreMods = mods; - else if (localAPIMods != null) - scoreMods = APIMods.Select(m => m.ToMod(rulesetInstance)).ToArray(); - - return scoreMods; - } - set - { - localAPIMods = null; - mods = value; - } - } - - // Used for API serialisation/deserialisation. - [NotMapped] - public APIMod[] APIMods - { - get - { - if (localAPIMods != null) - return localAPIMods; - - if (mods == null) - return Array.Empty(); - - return localAPIMods = mods.Select(m => new APIMod(m)).ToArray(); - } - set - { - localAPIMods = value; - - // We potentially can't update this yet due to Ruleset being late-bound, so instead update on read as necessary. - mods = null; - } - } - - // Used for database serialisation/deserialisation. - [Column("Mods")] - public string ModsJson - { - get => JsonConvert.SerializeObject(APIMods); - set => APIMods = JsonConvert.DeserializeObject(value); - } - - [NotMapped] - public APIUser User { get; set; } - - [Column("User")] - public string UserString - { - get => User?.Username; - set - { - User ??= new APIUser(); - User.Username = value; - } - } - - [Column("UserID")] - public int? UserID - { - get => User?.Id ?? 1; - set - { - User ??= new APIUser(); - User.Id = value ?? 1; - } - } - - public int BeatmapInfoID { get; set; } - - [Column("Beatmap")] - public BeatmapInfo BeatmapInfo { get; set; } - - private long? onlineID; - - [Column("OnlineScoreID")] - public long? OnlineID - { - get => onlineID; - set => onlineID = value > 0 ? value : null; - } - - public DateTimeOffset Date { get; set; } - - [NotMapped] - public Dictionary Statistics { get; set; } = new Dictionary(); - - [Column("Statistics")] - public string StatisticsJson - { - get => JsonConvert.SerializeObject(Statistics); - set - { - if (value == null) - { - Statistics.Clear(); - return; - } - - Statistics = JsonConvert.DeserializeObject>(value); - } - } - - [NotMapped] - public List HitEvents { get; set; } - - public List Files { get; } = new List(); - - public string Hash { get; set; } + public string Hash { get; set; } = string.Empty; public bool DeletePending { get; set; } - /// - /// The position of this score, starting at 1. - /// - [NotMapped] - public int? Position { get; set; } // TODO: remove after all calls to `CreateScoreInfo` are gone. + public bool Equals(ScoreInfo other) => other.ID == ID; - /// - /// Whether this represents a legacy (osu!stable) score. - /// - [NotMapped] - public bool IsLegacyScore => Mods.OfType().Any(); + [Indexed] + public long OnlineID { get; set; } = -1; - public IEnumerable GetStatisticsForDisplay() + public RealmUser User { get; set; } = null!; + + public long TotalScore { get; set; } + + public int MaxCombo { get; set; } + + public double Accuracy { get; set; } + + public bool HasReplay { get; set; } + + public DateTimeOffset Date { get; set; } + + public double? PP { get; set; } + + public RealmBeatmap Beatmap { get; set; } = null!; + + public RealmRuleset Ruleset { get; set; } = null!; + + public ScoreRank Rank { - foreach (var r in Ruleset.CreateInstance().GetHitResults()) - { - int value = Statistics.GetValueOrDefault(r.result); - - switch (r.result) - { - case HitResult.SmallTickHit: - { - int total = value + Statistics.GetValueOrDefault(HitResult.SmallTickMiss); - if (total > 0) - yield return new HitResultDisplayStatistic(r.result, value, total, r.displayName); - - break; - } - - case HitResult.LargeTickHit: - { - int total = value + Statistics.GetValueOrDefault(HitResult.LargeTickMiss); - if (total > 0) - yield return new HitResultDisplayStatistic(r.result, value, total, r.displayName); - - break; - } - - case HitResult.SmallTickMiss: - case HitResult.LargeTickMiss: - break; - - default: - yield return new HitResultDisplayStatistic(r.result, value, null, r.displayName); - - break; - } - } + get => (ScoreRank)RankInt; + set => RankInt = (int)value; } - public ScoreInfo DeepClone() - { - var clone = (ScoreInfo)MemberwiseClone(); + [MapTo(nameof(Rank))] + public int RankInt { get; set; } - clone.Statistics = new Dictionary(clone.Statistics); - - return clone; - } - - public override string ToString() => this.GetDisplayTitle(); - - public bool Equals(ScoreInfo other) - { - if (ReferenceEquals(this, other)) return true; - if (other == null) return false; - - if (ID != 0 && other.ID != 0) - return ID == other.ID; - - return false; - } - - #region Implementation of IHasOnlineID - - long IHasOnlineID.OnlineID => OnlineID ?? -1; - - #endregion - - #region Implementation of IScoreInfo - - IBeatmapInfo IScoreInfo.Beatmap => BeatmapInfo; IRulesetInfo IScoreInfo.Ruleset => Ruleset; + IBeatmapInfo IScoreInfo.Beatmap => Beatmap; IUser IScoreInfo.User => User; - bool IScoreInfo.HasReplay => Files.Any(); - - #endregion - IEnumerable IHasNamedFiles.Files => Files; } } diff --git a/osu.Game/Stores/ScoreImporter.cs b/osu.Game/Stores/ScoreImporter.cs index b30dd88fbe..256f73d8e7 100644 --- a/osu.Game/Stores/ScoreImporter.cs +++ b/osu.Game/Stores/ScoreImporter.cs @@ -13,6 +13,7 @@ using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.IO.Archives; using osu.Game.Models; +using osu.Game.Scoring; using osu.Game.Scoring.Legacy; using Realms; @@ -24,7 +25,7 @@ namespace osu.Game.Stores /// Handles the storage and retrieval of Scores/WorkingScores. /// [ExcludeFromDynamicCompile] - public class ScoreImporter : RealmArchiveModelImporter + public class ScoreImporter : RealmArchiveModelImporter { private readonly RealmRulesetStore rulesets; private readonly BeatmapManager beatmaps; @@ -40,7 +41,7 @@ namespace osu.Game.Stores this.beatmaps = beatmaps; } - protected override RealmScore? CreateModel(ArchiveReader archive) + protected override ScoreInfo? CreateModel(ArchiveReader archive) { using (var stream = archive.GetStream(archive.Filenames.First(f => f.EndsWith(".osr", StringComparison.OrdinalIgnoreCase)))) { @@ -48,7 +49,7 @@ namespace osu.Game.Stores { // TODO: make work. // return new DatabasedLegacyScoreDecoder(rulesets, beatmaps).Parse(stream).ScoreInfo; - return new RealmScore(); + return new ScoreInfo(); } catch (LegacyScoreDecoder.BeatmapNotFoundException e) { @@ -58,7 +59,7 @@ namespace osu.Game.Stores } } - protected override Task Populate(RealmScore model, ArchiveReader? archive, Realm realm, CancellationToken cancellationToken = default) + protected override Task Populate(ScoreInfo model, ArchiveReader? archive, Realm realm, CancellationToken cancellationToken = default) => Task.CompletedTask; } } From 638b3d91610dd3cf548f859ee704f9963aa5bc3f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 6 Dec 2021 15:32:02 +0900 Subject: [PATCH 189/996] Add statistics storage to realm model --- osu.Game/Scoring/ScoreInfo.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 0c097d1294..1938271989 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -51,6 +51,22 @@ namespace osu.Game.Scoring public RealmRuleset Ruleset { get; set; } = null!; + [Ignored] + public Dictionary Statistics + { + get + { + if (string.IsNullOrEmpty(StatisticsJson)) + return new Dictionary(); + + return JsonConvert.DeserializeObject>(StatisticsJson) ?? new Dictionary(); + } + set => JsonConvert.SerializeObject(StatisticsJson); + } + + [MapTo("Statistics")] + public string StatisticsJson { get; set; } = null!; + public ScoreRank Rank { get => (ScoreRank)RankInt; From e44751c275918bcf19da500e40dd57e7c1672591 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 6 Dec 2021 15:35:08 +0900 Subject: [PATCH 190/996] Add required properties for compatibility with existing code --- osu.Game/Scoring/ScoreInfo.cs | 133 ++++++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 1938271989..11cd26dd3c 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -3,11 +3,17 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using Newtonsoft.Json; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Models; +using osu.Game.Online.API; using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Scoring; using osu.Game.Users; using Realms; @@ -80,5 +86,132 @@ namespace osu.Game.Scoring IBeatmapInfo IScoreInfo.Beatmap => Beatmap; IUser IScoreInfo.User => User; IEnumerable IHasNamedFiles.Files => Files; + + #region Properties required to make things work with existing usages + + private APIMod[]? localAPIMods; + + private Mod[]? mods; + + [Ignored] + public List HitEvents { get; set; } = new List(); + + public ScoreInfo DeepClone() + { + var clone = (ScoreInfo)MemberwiseClone(); + + clone.Statistics = new Dictionary(clone.Statistics); + + return clone; + } + + [Ignored] + public bool Passed { get; set; } = true; + + /// + /// The position of this score, starting at 1. + /// + [Ignored] + public int? Position { get; set; } // TODO: remove after all calls to `CreateScoreInfo` are gone. + + /// + /// Whether this represents a legacy (osu!stable) score. + /// + [Ignored] + public bool IsLegacyScore => Mods.OfType().Any(); + + [Ignored] + public Mod[] Mods + { + get + { + var rulesetInstance = Ruleset.CreateInstance(); + + Mod[] scoreMods = Array.Empty(); + + if (mods != null) + scoreMods = mods; + else if (localAPIMods != null) + scoreMods = APIMods.Select(m => m.ToMod(rulesetInstance)).ToArray(); + + return scoreMods; + } + set + { + localAPIMods = null; + mods = value; + } + } + + // Used for API serialisation/deserialisation. + [Ignored] + public APIMod[] APIMods + { + get + { + if (localAPIMods != null) + return localAPIMods; + + if (mods == null) + return Array.Empty(); + + return localAPIMods = mods.Select(m => new APIMod(m)).ToArray(); + } + set + { + localAPIMods = value; + + // We potentially can't update this yet due to Ruleset being late-bound, so instead update on read as necessary. + mods = null; + } + } + + // Used for database serialisation/deserialisation. + [Column("Mods")] + public string ModsJson + { + get => JsonConvert.SerializeObject(APIMods); + set => APIMods = JsonConvert.DeserializeObject(value) ?? Array.Empty(); + } + + public IEnumerable GetStatisticsForDisplay() + { + foreach (var r in Ruleset.CreateInstance().GetHitResults()) + { + int value = Statistics.GetValueOrDefault(r.result); + + switch (r.result) + { + case HitResult.SmallTickHit: + { + int total = value + Statistics.GetValueOrDefault(HitResult.SmallTickMiss); + if (total > 0) + yield return new HitResultDisplayStatistic(r.result, value, total, r.displayName); + + break; + } + + case HitResult.LargeTickHit: + { + int total = value + Statistics.GetValueOrDefault(HitResult.LargeTickMiss); + if (total > 0) + yield return new HitResultDisplayStatistic(r.result, value, total, r.displayName); + + break; + } + + case HitResult.SmallTickMiss: + case HitResult.LargeTickMiss: + break; + + default: + yield return new HitResultDisplayStatistic(r.result, value, null, r.displayName); + + break; + } + } + } + + #endregion } } From 2a4bee61dde38d203dcaeca6da89fe904ab2e80d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 6 Dec 2021 22:47:00 +0900 Subject: [PATCH 191/996] Update many score-related classes to move closer to being able to persist to realm --- osu.Game/Database/LegacyScoreExporter.cs | 2 +- .../Online/API/Requests/Responses/APIScore.cs | 1 - osu.Game/Online/Rooms/MultiplayerScore.cs | 2 -- osu.Game/Online/Solo/SubmittableScore.cs | 3 +- osu.Game/OsuGame.cs | 2 +- osu.Game/Scoring/EFScoreInfo.cs | 2 ++ osu.Game/Scoring/LegacyDatabasedScore.cs | 2 +- osu.Game/Scoring/ScoreInfo.cs | 36 ++++++++++++++++++- osu.Game/Scoring/ScoreManager.cs | 10 +++--- osu.Game/Scoring/ScoreModelManager.cs | 29 +++++---------- osu.Game/Scoring/ScoreStore.cs | 25 ------------- osu.Game/Screens/Ranking/ScorePanelList.cs | 2 +- .../Select/Leaderboards/BeatmapLeaderboard.cs | 2 +- osu.Game/Stores/ScoreImporter.cs | 1 - 14 files changed, 58 insertions(+), 61 deletions(-) delete mode 100644 osu.Game/Scoring/ScoreStore.cs diff --git a/osu.Game/Database/LegacyScoreExporter.cs b/osu.Game/Database/LegacyScoreExporter.cs index 41f8516880..336f50bc3d 100644 --- a/osu.Game/Database/LegacyScoreExporter.cs +++ b/osu.Game/Database/LegacyScoreExporter.cs @@ -24,7 +24,7 @@ namespace osu.Game.Database if (file == null) return; - using (var inputStream = UserFileStorage.GetStream(file.FileInfo.GetStoragePath())) + using (var inputStream = UserFileStorage.GetStream(file.File.GetStoragePath())) inputStream.CopyTo(outputStream); } } diff --git a/osu.Game/Online/API/Requests/Responses/APIScore.cs b/osu.Game/Online/API/Requests/Responses/APIScore.cs index 4f795bee6c..71f277570d 100644 --- a/osu.Game/Online/API/Requests/Responses/APIScore.cs +++ b/osu.Game/Online/API/Requests/Responses/APIScore.cs @@ -105,7 +105,6 @@ namespace osu.Game.Online.API.Requests.Responses OnlineID = OnlineID, Date = Date, PP = PP, - RulesetID = RulesetID, Hash = HasReplay ? "online" : string.Empty, // todo: temporary? Rank = Rank, Ruleset = ruleset, diff --git a/osu.Game/Online/Rooms/MultiplayerScore.cs b/osu.Game/Online/Rooms/MultiplayerScore.cs index 05c9a1b6cf..b90680a925 100644 --- a/osu.Game/Online/Rooms/MultiplayerScore.cs +++ b/osu.Game/Online/Rooms/MultiplayerScore.cs @@ -73,9 +73,7 @@ namespace osu.Game.Online.Rooms TotalScore = TotalScore, MaxCombo = MaxCombo, BeatmapInfo = beatmap, - BeatmapInfoID = playlistItem.BeatmapID, Ruleset = rulesets.GetRuleset(playlistItem.RulesetID), - RulesetID = playlistItem.RulesetID, Statistics = Statistics, User = User, Accuracy = Accuracy, diff --git a/osu.Game/Online/Solo/SubmittableScore.cs b/osu.Game/Online/Solo/SubmittableScore.cs index 5ca5ad9619..52aef82c3b 100644 --- a/osu.Game/Online/Solo/SubmittableScore.cs +++ b/osu.Game/Online/Solo/SubmittableScore.cs @@ -10,6 +10,7 @@ using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; +using osu.Game.Users; using APIUser = osu.Game.Online.API.Requests.Responses.APIUser; namespace osu.Game.Online.Solo @@ -48,7 +49,7 @@ namespace osu.Game.Online.Solo public APIMod[] Mods { get; set; } [JsonProperty("user")] - public APIUser User { get; set; } + public IUser User { get; set; } [JsonProperty("statistics")] public Dictionary Statistics { get; set; } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 0d0a572663..544e8df425 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -481,7 +481,7 @@ namespace osu.Game /// Present a score's replay immediately. /// The user should have already requested this interactively. /// - public void PresentScore(ScoreInfo score, ScorePresentType presentType = ScorePresentType.Results) + public void PresentScore(IScoreInfo score, ScorePresentType presentType = ScorePresentType.Results) { // The given ScoreInfo may have missing properties if it was retrieved from online data. Re-retrieve it from the database // to ensure all the required data for presenting a replay are present. diff --git a/osu.Game/Scoring/EFScoreInfo.cs b/osu.Game/Scoring/EFScoreInfo.cs index e23662f74d..84b41e40ef 100644 --- a/osu.Game/Scoring/EFScoreInfo.cs +++ b/osu.Game/Scoring/EFScoreInfo.cs @@ -23,6 +23,8 @@ namespace osu.Game.Scoring { public int ID { get; set; } + public bool IsManaged => ID > 0; + public ScoreRank Rank { get; set; } public long TotalScore { get; set; } diff --git a/osu.Game/Scoring/LegacyDatabasedScore.cs b/osu.Game/Scoring/LegacyDatabasedScore.cs index 69360cacc7..ac444c1bf3 100644 --- a/osu.Game/Scoring/LegacyDatabasedScore.cs +++ b/osu.Game/Scoring/LegacyDatabasedScore.cs @@ -17,7 +17,7 @@ namespace osu.Game.Scoring { ScoreInfo = score; - string replayFilename = score.Files.FirstOrDefault(f => f.Filename.EndsWith(".osr", StringComparison.InvariantCultureIgnoreCase))?.FileInfo.GetStoragePath(); + string replayFilename = score.Files.FirstOrDefault(f => f.Filename.EndsWith(".osr", StringComparison.InvariantCultureIgnoreCase))?.File.GetStoragePath(); if (replayFilename == null) return; diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 11cd26dd3c..782fe0681b 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using Newtonsoft.Json; +using osu.Framework.Localisation; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Database; @@ -15,6 +16,7 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Users; +using osu.Game.Utils; using Realms; #nullable enable @@ -39,7 +41,18 @@ namespace osu.Game.Scoring [Indexed] public long OnlineID { get; set; } = -1; - public RealmUser User { get; set; } = null!; + [MapTo("User")] + public RealmUser RealmUser { get; set; } = null!; + + public IUser User + { + get => RealmUser; + set => RealmUser = new RealmUser + { + OnlineID = value.OnlineID, + Username = value.Username + }; + } public long TotalScore { get; set; } @@ -55,11 +68,20 @@ namespace osu.Game.Scoring public RealmBeatmap Beatmap { get; set; } = null!; + public BeatmapInfo BeatmapInfo + { + get => new BeatmapInfo(); + // .. todo + set => Beatmap = new RealmBeatmap(new RealmRuleset("osu", "osu!", "wangs", 0), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata()); + } + public RealmRuleset Ruleset { get; set; } = null!; [Ignored] public Dictionary Statistics { + // TODO: this is dangerous. a get operation may then modify the dictionary, which would be a fresh copy that is not persisted with the model. + // this is already the case in multiple locations. get { if (string.IsNullOrEmpty(StatisticsJson)) @@ -93,6 +115,12 @@ namespace osu.Game.Scoring private Mod[]? mods; + public int BeatmapInfoID => BeatmapInfo.ID; + + public int UserID => RealmUser.OnlineID; + + public int RulesetID => Ruleset.OnlineID; + [Ignored] public List HitEvents { get; set; } = new List(); @@ -108,12 +136,18 @@ namespace osu.Game.Scoring [Ignored] public bool Passed { get; set; } = true; + [Ignored] + public int Combo { get; set; } + /// /// The position of this score, starting at 1. /// [Ignored] public int? Position { get; set; } // TODO: remove after all calls to `CreateScoreInfo` are gone. + [Ignored] + public LocalisableString DisplayAccuracy => Accuracy.FormatAccuracy(); + /// /// Whether this represents a legacy (osu!stable) score. /// diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 9f248ffdca..740c46387e 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -63,7 +63,7 @@ namespace osu.Game.Scoring // Compute difficulties asynchronously first to prevent blocking via the GetTotalScore() call below. foreach (var s in scores) { - await difficultyCache.GetDifficultyAsync(s.BeatmapInfo, s.Ruleset, s.Mods, cancellationToken).ConfigureAwait(false); + await difficultyCache.GetDifficultyAsync(s.Beatmap, s.Ruleset, s.Mods, cancellationToken).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); } } @@ -125,7 +125,7 @@ namespace osu.Game.Scoring /// The total score. public async Task GetTotalScoreAsync([NotNull] ScoreInfo score, ScoringMode mode = ScoringMode.Standardised, CancellationToken cancellationToken = default) { - if (score.BeatmapInfo == null) + if (score.Beatmap == null) return score.TotalScore; int beatmapMaxCombo; @@ -146,11 +146,11 @@ namespace osu.Game.Scoring // This score is guaranteed to be an osu!stable score. // The combo must be determined through either the beatmap's max combo value or the difficulty calculator, as lazer's scoring has changed and the score statistics cannot be used. - if (score.BeatmapInfo.MaxCombo != null) - beatmapMaxCombo = score.BeatmapInfo.MaxCombo.Value; + if (score.Beatmap.MaxCombo != null) + beatmapMaxCombo = score.Beatmap.MaxCombo.Value; else { - if (score.BeatmapInfo.ID == 0 || difficulties == null) + if (score.Beatmap.ID == 0 || difficulties == null) { // We don't have enough information (max combo) to compute the score, so use the provided score. return score.TotalScore; diff --git a/osu.Game/Scoring/ScoreModelManager.cs b/osu.Game/Scoring/ScoreModelManager.cs index 44f0fe4fdf..81f80df3ad 100644 --- a/osu.Game/Scoring/ScoreModelManager.cs +++ b/osu.Game/Scoring/ScoreModelManager.cs @@ -4,10 +4,8 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Linq.Expressions; using System.Threading; using System.Threading.Tasks; -using Microsoft.EntityFrameworkCore; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Beatmaps; @@ -15,10 +13,14 @@ using osu.Game.Database; using osu.Game.IO.Archives; using osu.Game.Rulesets; using osu.Game.Scoring.Legacy; +using osu.Game.Stores; +using Realms; + +#nullable enable namespace osu.Game.Scoring { - public class ScoreModelManager : ArchiveModelManager + public class ScoreModelManager : RealmArchiveModelManager { public override IEnumerable HandledExtensions => new[] { ".osr" }; @@ -27,18 +29,15 @@ namespace osu.Game.Scoring private readonly RulesetStore rulesets; private readonly Func beatmaps; - public ScoreModelManager(RulesetStore rulesets, Func beatmaps, Storage storage, IDatabaseContextFactory contextFactory, IIpcHost importHost = null) - : base(storage, contextFactory, new ScoreStore(contextFactory, storage), importHost) + public ScoreModelManager(RulesetStore rulesets, Func beatmaps, Storage storage, RealmContextFactory contextFactory) + : base(storage, contextFactory) { this.rulesets = rulesets; this.beatmaps = beatmaps; } - protected override ScoreInfo CreateModel(ArchiveReader archive) + protected override ScoreInfo? CreateModel(ArchiveReader archive) { - if (archive == null) - return null; - using (var stream = archive.GetStream(archive.Filenames.First(f => f.EndsWith(".osr", StringComparison.OrdinalIgnoreCase)))) { try @@ -55,17 +54,7 @@ namespace osu.Game.Scoring public Score GetScore(ScoreInfo score) => new LegacyDatabasedScore(score, rulesets, beatmaps(), Files.Store); - public List GetAllUsableScores() => ModelStore.ConsumableItems.Where(s => !s.DeletePending).ToList(); - - public IEnumerable QueryScores(Expression> query) => ModelStore.ConsumableItems.AsNoTracking().Where(query); - - public ScoreInfo Query(Expression> query) => ModelStore.ConsumableItems.AsNoTracking().FirstOrDefault(query); - - protected override Task Populate(ScoreInfo model, ArchiveReader archive, CancellationToken cancellationToken = default) + protected override Task Populate(ScoreInfo model, ArchiveReader? archive, Realm realm, CancellationToken cancellationToken = default) => Task.CompletedTask; - - protected override bool CheckLocalAvailability(ScoreInfo model, IQueryable items) - => base.CheckLocalAvailability(model, items) - || (model.OnlineID > 0 && items.Any(i => i.OnlineID == model.OnlineID)); } } diff --git a/osu.Game/Scoring/ScoreStore.cs b/osu.Game/Scoring/ScoreStore.cs deleted file mode 100644 index fd1f5ae3ec..0000000000 --- a/osu.Game/Scoring/ScoreStore.cs +++ /dev/null @@ -1,25 +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.Linq; -using Microsoft.EntityFrameworkCore; -using osu.Framework.Platform; -using osu.Game.Database; - -namespace osu.Game.Scoring -{ - public class ScoreStore : MutableDatabaseBackedStoreWithFileIncludes - { - public ScoreStore(IDatabaseContextFactory factory, Storage storage) - : base(factory, storage) - { - } - - protected override IQueryable AddIncludesForConsumption(IQueryable query) - => base.AddIncludesForConsumption(query) - .Include(s => s.BeatmapInfo) - .Include(s => s.BeatmapInfo).ThenInclude(b => b.Metadata) - .Include(s => s.BeatmapInfo).ThenInclude(b => b.BeatmapSet).ThenInclude(s => s.Metadata) - .Include(s => s.Ruleset); - } -} diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index f8e9d08350..eb91c256c7 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -196,7 +196,7 @@ namespace osu.Game.Screens.Ranking } // Find the panel corresponding to the new score. - var expandedTrackingComponent = flow.SingleOrDefault(t => t.Panel.Score == score.NewValue); + var expandedTrackingComponent = flow.SingleOrDefault(t => t.Panel.Score.Equals(score.NewValue)); expandedPanel = expandedTrackingComponent?.Panel; if (expandedPanel == null) diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 3b25d34aec..7026f240c8 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -127,7 +127,7 @@ namespace osu.Game.Screens.Select.Leaderboards if (Scope == BeatmapLeaderboardScope.Local) { var scores = scoreManager - .QueryScores(s => !s.DeletePending && s.BeatmapInfo.ID == fetchBeatmapInfo.ID && s.Ruleset.ID == ruleset.Value.ID); + .QueryScores(s => !s.DeletePending && s.BeatmapInfo.ID == fetchBeatmapInfo.ID && s.Ruleset.OnlineID == ruleset.Value.ID); if (filterMods && !mods.Value.Any()) { diff --git a/osu.Game/Stores/ScoreImporter.cs b/osu.Game/Stores/ScoreImporter.cs index 256f73d8e7..8b43f618cc 100644 --- a/osu.Game/Stores/ScoreImporter.cs +++ b/osu.Game/Stores/ScoreImporter.cs @@ -12,7 +12,6 @@ using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.IO.Archives; -using osu.Game.Models; using osu.Game.Scoring; using osu.Game.Scoring.Legacy; using Realms; From aac2aa341cf879c66e7b802d6d9a2ce1d1c3e890 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 13 Dec 2021 17:09:13 +0900 Subject: [PATCH 192/996] Update some more incorrect types for primary key access/set --- osu.Game.Tests/Scores/IO/TestScoreEquality.cs | 11 +++++++---- osu.Game/Online/Solo/SubmittableScore.cs | 1 - osu.Game/OsuGame.cs | 3 ++- osu.Game/Scoring/Legacy/ScoreInfoExtensions.cs | 16 ++++++++-------- 4 files changed, 17 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Scores/IO/TestScoreEquality.cs b/osu.Game.Tests/Scores/IO/TestScoreEquality.cs index 42fcb3acab..f898774ce6 100644 --- a/osu.Game.Tests/Scores/IO/TestScoreEquality.cs +++ b/osu.Game.Tests/Scores/IO/TestScoreEquality.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 NUnit.Framework; using osu.Game.Scoring; @@ -29,8 +30,8 @@ namespace osu.Game.Tests.Scores.IO [Test] public void TestNonMatchingByPrimaryKey() { - ScoreInfo score1 = new ScoreInfo { ID = 1 }; - ScoreInfo score2 = new ScoreInfo { ID = 2 }; + ScoreInfo score1 = new ScoreInfo { ID = Guid.NewGuid() }; + ScoreInfo score2 = new ScoreInfo { ID = Guid.NewGuid() }; Assert.That(score1, Is.Not.EqualTo(score2)); } @@ -38,8 +39,10 @@ namespace osu.Game.Tests.Scores.IO [Test] public void TestMatchingByPrimaryKey() { - ScoreInfo score1 = new ScoreInfo { ID = 1 }; - ScoreInfo score2 = new ScoreInfo { ID = 1 }; + Guid id = Guid.NewGuid(); + + ScoreInfo score1 = new ScoreInfo { ID = id }; + ScoreInfo score2 = new ScoreInfo { ID = id }; Assert.That(score1, Is.EqualTo(score2)); } diff --git a/osu.Game/Online/Solo/SubmittableScore.cs b/osu.Game/Online/Solo/SubmittableScore.cs index 52aef82c3b..327806f390 100644 --- a/osu.Game/Online/Solo/SubmittableScore.cs +++ b/osu.Game/Online/Solo/SubmittableScore.cs @@ -11,7 +11,6 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Users; -using APIUser = osu.Game.Online.API.Requests.Responses.APIUser; namespace osu.Game.Online.Solo { diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 544e8df425..4539409d4c 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -490,7 +490,8 @@ namespace osu.Game if (score.OnlineID > 0) databasedScoreInfo = ScoreManager.Query(s => s.OnlineID == score.OnlineID); - databasedScoreInfo ??= ScoreManager.Query(s => s.Hash == score.Hash); + if (score is ScoreInfo scoreInfo) + databasedScoreInfo ??= ScoreManager.Query(s => s.Hash == scoreInfo.Hash); if (databasedScoreInfo == null) { diff --git a/osu.Game/Scoring/Legacy/ScoreInfoExtensions.cs b/osu.Game/Scoring/Legacy/ScoreInfoExtensions.cs index fc27261225..a11cd5fcbd 100644 --- a/osu.Game/Scoring/Legacy/ScoreInfoExtensions.cs +++ b/osu.Game/Scoring/Legacy/ScoreInfoExtensions.cs @@ -9,7 +9,7 @@ namespace osu.Game.Scoring.Legacy { public static int? GetCountGeki(this ScoreInfo scoreInfo) { - switch (scoreInfo.Ruleset?.ID ?? scoreInfo.RulesetID) + switch (scoreInfo.Ruleset.OnlineID) { case 3: return getCount(scoreInfo, HitResult.Perfect); @@ -20,7 +20,7 @@ namespace osu.Game.Scoring.Legacy public static void SetCountGeki(this ScoreInfo scoreInfo, int value) { - switch (scoreInfo.Ruleset?.ID ?? scoreInfo.RulesetID) + switch (scoreInfo.Ruleset.OnlineID) { case 3: scoreInfo.Statistics[HitResult.Perfect] = value; @@ -34,7 +34,7 @@ namespace osu.Game.Scoring.Legacy public static int? GetCountKatu(this ScoreInfo scoreInfo) { - switch (scoreInfo.Ruleset?.ID ?? scoreInfo.RulesetID) + switch (scoreInfo.Ruleset.OnlineID) { case 3: return getCount(scoreInfo, HitResult.Good); @@ -48,7 +48,7 @@ namespace osu.Game.Scoring.Legacy public static void SetCountKatu(this ScoreInfo scoreInfo, int value) { - switch (scoreInfo.Ruleset?.ID ?? scoreInfo.RulesetID) + switch (scoreInfo.Ruleset.OnlineID) { case 3: scoreInfo.Statistics[HitResult.Good] = value; @@ -62,7 +62,7 @@ namespace osu.Game.Scoring.Legacy public static int? GetCount100(this ScoreInfo scoreInfo) { - switch (scoreInfo.Ruleset?.ID ?? scoreInfo.RulesetID) + switch (scoreInfo.Ruleset.OnlineID) { case 0: case 1: @@ -78,7 +78,7 @@ namespace osu.Game.Scoring.Legacy public static void SetCount100(this ScoreInfo scoreInfo, int value) { - switch (scoreInfo.Ruleset?.ID ?? scoreInfo.RulesetID) + switch (scoreInfo.Ruleset.OnlineID) { case 0: case 1: @@ -94,7 +94,7 @@ namespace osu.Game.Scoring.Legacy public static int? GetCount50(this ScoreInfo scoreInfo) { - switch (scoreInfo.Ruleset?.ID ?? scoreInfo.RulesetID) + switch (scoreInfo.Ruleset.OnlineID) { case 0: case 3: @@ -109,7 +109,7 @@ namespace osu.Game.Scoring.Legacy public static void SetCount50(this ScoreInfo scoreInfo, int value) { - switch (scoreInfo.Ruleset?.ID ?? scoreInfo.RulesetID) + switch (scoreInfo.Ruleset.OnlineID) { case 0: case 3: From e711a6d35598643c9a11ed6d895db09c4abd74f9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 13 Dec 2021 17:37:17 +0900 Subject: [PATCH 193/996] Remove unused `ScoreImporter` class --- osu.Game/Stores/ScoreImporter.cs | 64 -------------------------------- 1 file changed, 64 deletions(-) delete mode 100644 osu.Game/Stores/ScoreImporter.cs diff --git a/osu.Game/Stores/ScoreImporter.cs b/osu.Game/Stores/ScoreImporter.cs deleted file mode 100644 index 8b43f618cc..0000000000 --- a/osu.Game/Stores/ScoreImporter.cs +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using osu.Framework.Logging; -using osu.Framework.Platform; -using osu.Framework.Testing; -using osu.Game.Beatmaps; -using osu.Game.Database; -using osu.Game.IO.Archives; -using osu.Game.Scoring; -using osu.Game.Scoring.Legacy; -using Realms; - -#nullable enable - -namespace osu.Game.Stores -{ - /// - /// Handles the storage and retrieval of Scores/WorkingScores. - /// - [ExcludeFromDynamicCompile] - public class ScoreImporter : RealmArchiveModelImporter - { - private readonly RealmRulesetStore rulesets; - private readonly BeatmapManager beatmaps; - - public override IEnumerable HandledExtensions => new[] { ".osr" }; - - protected override string[] HashableFileTypes => new[] { ".osr" }; - - public ScoreImporter(RealmRulesetStore rulesets, RealmContextFactory contextFactory, Storage storage, BeatmapManager beatmaps) - : base(storage, contextFactory) - { - this.rulesets = rulesets; - this.beatmaps = beatmaps; - } - - protected override ScoreInfo? CreateModel(ArchiveReader archive) - { - using (var stream = archive.GetStream(archive.Filenames.First(f => f.EndsWith(".osr", StringComparison.OrdinalIgnoreCase)))) - { - try - { - // TODO: make work. - // return new DatabasedLegacyScoreDecoder(rulesets, beatmaps).Parse(stream).ScoreInfo; - return new ScoreInfo(); - } - catch (LegacyScoreDecoder.BeatmapNotFoundException e) - { - Logger.Log(e.Message, LoggingTarget.Information, LogLevel.Error); - return null; - } - } - } - - protected override Task Populate(ScoreInfo model, ArchiveReader? archive, Realm realm, CancellationToken cancellationToken = default) - => Task.CompletedTask; - } -} From 53792811b2648611844650934d8d24f265890597 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 13 Dec 2021 17:37:27 +0900 Subject: [PATCH 194/996] more fixes (almost compiles, except ruleset and manager) --- .../Beatmaps/IO/ImportBeatmapTest.cs | 11 ++++++++ osu.Game.Tests/Resources/TestResources.cs | 1 - .../Visual/Ranking/TestSceneScorePanelList.cs | 11 ++++---- .../SongSelect/TestSceneBeatmapLeaderboard.cs | 4 ++- .../TestSceneDeleteLocalScore.cs | 3 ++- .../Online/Leaderboards/LeaderboardScore.cs | 2 +- osu.Game/OsuGameBase.cs | 2 +- .../Scores/TopScoreStatisticsSection.cs | 4 +-- osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs | 4 +-- osu.Game/Scoring/LegacyDatabasedScore.cs | 4 +-- osu.Game/Scoring/ScoreInfo.cs | 27 +++++++++++++++---- osu.Game/Scoring/ScoreManager.cs | 10 ++++--- osu.Game/Scoring/ScoreModelManager.cs | 5 ++-- osu.Game/Screens/Play/Player.cs | 5 ++-- .../ContractedPanelMiddleContent.cs | 2 +- osu.Game/Screens/Ranking/ScorePanelList.cs | 14 +++++----- .../Ranking/Statistics/StatisticsPanel.cs | 2 +- 17 files changed, 71 insertions(+), 40 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index da414dcf8d..0d973c8aeb 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -12,7 +12,9 @@ using osu.Framework.Allocation; using osu.Framework.Extensions; using osu.Game.Beatmaps; using osu.Game.Database; +using osu.Game.Scoring; using osu.Game.Tests.Resources; +using osu.Game.Tests.Scores.IO; namespace osu.Game.Tests.Beatmaps.IO { @@ -61,6 +63,15 @@ namespace osu.Game.Tests.Beatmaps.IO Assert.IsTrue(manager.QueryBeatmapSets(_ => true).First().DeletePending); } + private static Task createScoreForBeatmap(OsuGameBase osu, BeatmapInfo beatmapInfo) + { + return ImportScoreTest.LoadScoreIntoOsu(osu, new ScoreInfo + { + OnlineID = 2, + BeatmapInfo = beatmapInfo, + }, new ImportScoreTest.TestArchiveReader()); + } + private static void checkBeatmapSetCount(OsuGameBase osu, int expected, bool includeDeletePending = false) { var manager = osu.Dependencies.Get(); diff --git a/osu.Game.Tests/Resources/TestResources.cs b/osu.Game.Tests/Resources/TestResources.cs index cd8b031f64..d0ffb9c3db 100644 --- a/osu.Game.Tests/Resources/TestResources.cs +++ b/osu.Game.Tests/Resources/TestResources.cs @@ -164,7 +164,6 @@ namespace osu.Game.Tests.Resources }, BeatmapInfo = beatmap, Ruleset = beatmap.Ruleset, - RulesetID = beatmap.Ruleset.ID ?? 0, Mods = new Mod[] { new TestModHardRock(), new TestModDoubleTime() }, TotalScore = 2845370, Accuracy = 0.95, diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs index f5ad352b9c..e786b85f78 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs @@ -7,6 +7,7 @@ using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Framework.Utils; +using osu.Game.Models; using osu.Game.Scoring; using osu.Game.Screens.Ranking; using osu.Game.Tests.Resources; @@ -157,10 +158,10 @@ namespace osu.Game.Tests.Visual.Ranking public void TestSelectMultipleScores() { var firstScore = TestResources.CreateTestScoreInfo(); - var secondScore = TestResources.CreateTestScoreInfo(); + firstScore.RealmUser = new RealmUser { Username = "A" }; - firstScore.UserString = "A"; - secondScore.UserString = "B"; + var secondScore = TestResources.CreateTestScoreInfo(); + secondScore.RealmUser = new RealmUser { Username = "B" }; createListStep(() => new ScorePanelList()); @@ -178,7 +179,7 @@ namespace osu.Game.Tests.Visual.Ranking AddStep("select second score", () => { - InputManager.MoveMouseTo(list.ChildrenOfType().Single(p => p.Score == secondScore)); + InputManager.MoveMouseTo(list.ChildrenOfType().Single(p => p.Score.Equals(secondScore))); InputManager.Click(MouseButton.Left); }); @@ -303,6 +304,6 @@ namespace osu.Game.Tests.Visual.Ranking => AddUntilStep("first panel centred", () => Precision.AlmostEquals(list.ChildrenOfType().First().ScreenSpaceDrawQuad.Centre.X, list.ScreenSpaceDrawQuad.Centre.X, 1)); private void assertScoreState(ScoreInfo score, bool expanded) - => AddUntilStep($"score expanded = {expanded}", () => (list.ChildrenOfType().Single(p => p.Score == score).State == PanelState.Expanded) == expanded); + => AddUntilStep($"score expanded = {expanded}", () => (list.ChildrenOfType().Single(p => p.Score.Equals(score)).State == PanelState.Expanded) == expanded); } } diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 605e03564d..566ee52247 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics; using osu.Framework.Platform; using osu.Framework.Testing; using osu.Game.Beatmaps; +using osu.Game.Database; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Leaderboards; using osu.Game.Overlays; @@ -19,6 +20,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Scoring; using osu.Game.Screens.Select.Leaderboards; +using osu.Game.Stores; using osu.Game.Tests.Resources; using osu.Game.Users; using osuTK; @@ -43,7 +45,7 @@ namespace osu.Game.Tests.Visual.SongSelect dependencies.Cache(rulesetStore = new RulesetStore(ContextFactory)); dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesetStore, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default)); - dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, ContextFactory, Scheduler)); + dependencies.Cache(scoreManager = new ScoreManager(dependencies.Get(), () => beatmapManager, LocalStorage, dependencies.Get(), Scheduler)); return dependencies; } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index cdb81f059b..1c75ef1895 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -24,6 +24,7 @@ using osu.Game.Overlays; using osu.Game.Rulesets; using osu.Game.Scoring; using osu.Game.Screens.Select.Leaderboards; +using osu.Game.Stores; using osu.Game.Tests.Resources; using osuTK; using osuTK.Input; @@ -85,7 +86,7 @@ namespace osu.Game.Tests.Visual.UserInterface dependencies.Cache(rulesetStore = new RulesetStore(ContextFactory)); dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesetStore, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default)); - dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, ContextFactory, Scheduler)); + dependencies.Cache(scoreManager = new ScoreManager(dependencies.Get(), () => beatmapManager, LocalStorage, dependencies.Get(), Scheduler)); beatmapInfo = beatmapManager.Import(new ImportTask(TestResources.GetQuickTestBeatmapForImport())).GetResultSafely().Value.Beatmaps[0]; diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 14eec8b388..3eb9a02a5c 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -399,7 +399,7 @@ namespace osu.Game.Online.Leaderboards if (Score.Files.Count > 0) items.Add(new OsuMenuItem("Export", MenuItemType.Standard, () => new LegacyScoreExporter(storage).Export(Score))); - if (Score.ID != 0) + if (Score.IsManaged) items.Add(new OsuMenuItem("Delete", MenuItemType.Destructive, () => dialogOverlay?.Push(new LocalScoreDeleteDialog(Score)))); return items.ToArray(); diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index c4dcc35b8e..89e3fe5742 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -224,7 +224,7 @@ namespace osu.Game dependencies.Cache(RulesetStore = new RulesetStore(realmFactory, Storage)); // ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup() - dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, API, realmFactory, Scheduler, Host, () => difficultyCache, LocalConfig)); + dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, realmFactory, Scheduler, Host, () => difficultyCache, LocalConfig)); dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, realmFactory, RulesetStore, API, Audio, Resources, Host, defaultBeatmap, performOnlineLookups: true)); dependencies.Cache(BeatmapDownloader = new BeatmapModelDownloader(BeatmapManager, API)); diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs index 630aa8fe53..d659df21c6 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs @@ -107,7 +107,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { set { - if (score == value) + if (score.Equals(value)) return; score = value; @@ -115,7 +115,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores accuracyColumn.Text = value.DisplayAccuracy; maxComboColumn.Text = value.MaxCombo.ToLocalisableString(@"0\x"); - ppColumn.Alpha = value.BeatmapInfo?.Status.GrantsPerformancePoints() == true ? 1 : 0; + ppColumn.Alpha = value.BeatmapInfo.Status.GrantsPerformancePoints() ? 1 : 0; ppColumn.Text = value.PP?.ToLocalisableString(@"N0"); statisticsColumns.ChildrenEnumerable = value.GetStatisticsForDisplay().Select(createStatisticsColumn); diff --git a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs index 3d67aa9558..2902ff7848 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs @@ -45,8 +45,8 @@ namespace osu.Game.Scoring.Legacy sw.Write((byte)(score.ScoreInfo.Ruleset.OnlineID)); sw.Write(LATEST_VERSION); sw.Write(score.ScoreInfo.BeatmapInfo.MD5Hash); - sw.Write(score.ScoreInfo.UserString); - sw.Write(FormattableString.Invariant($"lazer-{score.ScoreInfo.UserString}-{score.ScoreInfo.Date}").ComputeMD5Hash()); + sw.Write(score.ScoreInfo.User.Username); + sw.Write(FormattableString.Invariant($"lazer-{score.ScoreInfo.User.Username}-{score.ScoreInfo.Date}").ComputeMD5Hash()); sw.Write((ushort)(score.ScoreInfo.GetCount300() ?? 0)); sw.Write((ushort)(score.ScoreInfo.GetCount100() ?? 0)); sw.Write((ushort)(score.ScoreInfo.GetCount50() ?? 0)); diff --git a/osu.Game/Scoring/LegacyDatabasedScore.cs b/osu.Game/Scoring/LegacyDatabasedScore.cs index ac444c1bf3..6a8e5f8930 100644 --- a/osu.Game/Scoring/LegacyDatabasedScore.cs +++ b/osu.Game/Scoring/LegacyDatabasedScore.cs @@ -6,14 +6,14 @@ using System.Linq; using osu.Framework.IO.Stores; using osu.Game.Beatmaps; using osu.Game.Extensions; -using osu.Game.Rulesets; using osu.Game.Scoring.Legacy; +using osu.Game.Stores; namespace osu.Game.Scoring { public class LegacyDatabasedScore : Score { - public LegacyDatabasedScore(ScoreInfo score, RulesetStore rulesets, BeatmapManager beatmaps, IResourceStore store) + public LegacyDatabasedScore(ScoreInfo score, RealmRulesetStore rulesets, BeatmapManager beatmaps, IResourceStore store) { ScoreInfo = score; diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 782fe0681b..21339acd22 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -12,6 +12,7 @@ using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Models; using osu.Game.Online.API; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; @@ -44,14 +45,27 @@ namespace osu.Game.Scoring [MapTo("User")] public RealmUser RealmUser { get; set; } = null!; - public IUser User + // TODO: this is a bit temporary to account for the fact that this class is used to ferry API user data to certain UI components. + // Eventually we should either persist enough information to realm to not require the API lookups, or perform the API lookups locally. + private APIUser? user; + + public APIUser User { - get => RealmUser; - set => RealmUser = new RealmUser + get => user ??= new APIUser { - OnlineID = value.OnlineID, - Username = value.Username + Username = RealmUser.Username, + Id = RealmUser.OnlineID, }; + set + { + user = value; + + RealmUser = new RealmUser + { + OnlineID = user.OnlineID, + Username = user.Username + }; + } } public long TotalScore { get; set; } @@ -72,6 +86,7 @@ namespace osu.Game.Scoring { get => new BeatmapInfo(); // .. todo + // ReSharper disable once ValueParameterNotUsed set => Beatmap = new RealmBeatmap(new RealmRuleset("osu", "osu!", "wangs", 0), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata()); } @@ -89,6 +104,8 @@ namespace osu.Game.Scoring return JsonConvert.DeserializeObject>(StatisticsJson) ?? new Dictionary(); } + // .. todo + // ReSharper disable once ValueParameterNotUsed set => JsonConvert.SerializeObject(StatisticsJson); } diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 740c46387e..7adc70dab0 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -20,6 +20,7 @@ using osu.Game.Overlays.Notifications; using osu.Game.Rulesets; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; +using osu.Game.Stores; namespace osu.Game.Scoring { @@ -37,7 +38,7 @@ namespace osu.Game.Scoring this.difficulties = difficulties; this.configManager = configManager; - scoreModelManager = new ScoreModelManager(rulesets, beatmaps, storage, contextFactory, importHost); + scoreModelManager = new ScoreModelManager(rulesets, beatmaps, storage, contextFactory); } public Score GetScore(ScoreInfo score) => scoreModelManager.GetScore(score); @@ -125,8 +126,9 @@ namespace osu.Game.Scoring /// The total score. public async Task GetTotalScoreAsync([NotNull] ScoreInfo score, ScoringMode mode = ScoringMode.Standardised, CancellationToken cancellationToken = default) { - if (score.Beatmap == null) - return score.TotalScore; + // TODO: ?? + // if (score.Beatmap == null) + // return score.TotalScore; int beatmapMaxCombo; double accuracy = score.Accuracy; @@ -150,7 +152,7 @@ namespace osu.Game.Scoring beatmapMaxCombo = score.Beatmap.MaxCombo.Value; else { - if (score.Beatmap.ID == 0 || difficulties == null) + if (!score.Beatmap.IsManaged || difficulties == null) { // We don't have enough information (max combo) to compute the score, so use the provided score. return score.TotalScore; diff --git a/osu.Game/Scoring/ScoreModelManager.cs b/osu.Game/Scoring/ScoreModelManager.cs index 81f80df3ad..dd0ac79873 100644 --- a/osu.Game/Scoring/ScoreModelManager.cs +++ b/osu.Game/Scoring/ScoreModelManager.cs @@ -11,7 +11,6 @@ using osu.Framework.Platform; 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.Stores; using Realms; @@ -26,10 +25,10 @@ namespace osu.Game.Scoring protected override string[] HashableFileTypes => new[] { ".osr" }; - private readonly RulesetStore rulesets; + private readonly RealmRulesetStore rulesets; private readonly Func beatmaps; - public ScoreModelManager(RulesetStore rulesets, Func beatmaps, Storage storage, RealmContextFactory contextFactory) + public ScoreModelManager(RealmRulesetStore rulesets, Func beatmaps, Storage storage, RealmContextFactory contextFactory) : base(storage, contextFactory) { this.rulesets = rulesets; diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index a66026b423..5a443ec48e 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -226,8 +226,6 @@ namespace osu.Game.Screens.Play // ensure the score is in a consistent state with the current player. Score.ScoreInfo.BeatmapInfo = Beatmap.Value.BeatmapInfo; Score.ScoreInfo.Ruleset = ruleset.RulesetInfo; - if (ruleset.RulesetInfo.OnlineID >= 0) - Score.ScoreInfo.RulesetID = ruleset.RulesetInfo.OnlineID; Score.ScoreInfo.Mods = gameplayMods; dependencies.CacheAs(GameplayState = new GameplayState(playableBeatmap, ruleset, gameplayMods, Score)); @@ -1045,7 +1043,8 @@ namespace osu.Game.Screens.Play // // Until we better define the server-side logic behind this, let's not store the online ID to avoid potential unique constraint // conflicts across various systems (ie. solo and multiplayer). - long? onlineScoreId = score.ScoreInfo.OnlineID; + long onlineScoreId = score.ScoreInfo.OnlineID; + score.ScoreInfo.OnlineID = -1; await scoreManager.Import(score.ScoreInfo, replayReader).ConfigureAwait(false); diff --git a/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs index 20c603295b..f9aff28bef 100644 --- a/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs @@ -107,7 +107,7 @@ namespace osu.Game.Screens.Ranking.Contracted { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - Text = score.UserString, + Text = score.RealmUser.Username, Font = OsuFont.GetFont(size: 16, weight: FontWeight.SemiBold) }, new FillFlowContainer diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index eb91c256c7..7f270b3a2a 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -158,7 +158,7 @@ namespace osu.Game.Screens.Ranking trackingContainer.Show(); - if (SelectedScore.Value == score) + if (SelectedScore.Value.Equals(score)) { SelectedScore.TriggerChange(); } @@ -185,10 +185,10 @@ namespace osu.Game.Screens.Ranking private void selectedScoreChanged(ValueChangedEvent score) { // avoid contracting panels unnecessarily when TriggerChange is fired manually. - if (score.OldValue != score.NewValue) + if (!score.OldValue.Equals(score.NewValue)) { // Contract the old panel. - foreach (var t in flow.Where(t => t.Panel.Score == score.OldValue)) + foreach (var t in flow.Where(t => t.Panel.Score.Equals(score.OldValue))) { t.Panel.State = PanelState.Contracted; t.Margin = new MarginPadding(); @@ -269,7 +269,7 @@ namespace osu.Game.Screens.Ranking /// /// The to find the corresponding for. /// The . - public ScorePanel GetPanelForScore(ScoreInfo score) => flow.Single(t => t.Panel.Score == score).Panel; + public ScorePanel GetPanelForScore(ScoreInfo score) => flow.Single(t => t.Panel.Score.Equals(score)).Panel; /// /// Detaches a from its , allowing the panel to be moved elsewhere in the hierarchy. @@ -332,13 +332,13 @@ namespace osu.Game.Screens.Ranking { public override IEnumerable FlowingChildren => applySorting(AliveInternalChildren); - public int GetPanelIndex(ScoreInfo score) => applySorting(Children).TakeWhile(s => s.Panel.Score != score).Count(); + public int GetPanelIndex(ScoreInfo score) => applySorting(Children).TakeWhile(s => !s.Panel.Score.Equals(score)).Count(); [CanBeNull] - public ScoreInfo GetPreviousScore(ScoreInfo score) => applySorting(Children).TakeWhile(s => s.Panel.Score != score).LastOrDefault()?.Panel.Score; + public ScoreInfo GetPreviousScore(ScoreInfo score) => applySorting(Children).TakeWhile(s => !s.Panel.Score.Equals(score)).LastOrDefault()?.Panel.Score; [CanBeNull] - public ScoreInfo GetNextScore(ScoreInfo score) => applySorting(Children).SkipWhile(s => s.Panel.Score != score).ElementAtOrDefault(1)?.Panel.Score; + public ScoreInfo GetNextScore(ScoreInfo score) => applySorting(Children).SkipWhile(s => !s.Panel.Score.Equals(score)).ElementAtOrDefault(1)?.Panel.Score; private IEnumerable applySorting(IEnumerable drawables) => drawables.OfType() .OrderByDescending(GetLayoutPosition) diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index 26dc3165f8..827e128467 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -142,7 +142,7 @@ namespace osu.Game.Screens.Ranking.Statistics LoadComponentAsync(rows, d => { - if (Score.Value != newScore) + if (!Score.Value.Equals(newScore)) return; spinner.Hide(); From 4f6a05ce3dff9a71275555b188e0b48eb664f149 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 13 Dec 2021 19:01:20 +0900 Subject: [PATCH 195/996] Reimplement all query methods --- osu.Game.Tests/Scores/IO/ImportScoreTest.cs | 4 +- .../SongSelect/TestSceneBeatmapLeaderboard.cs | 2 +- .../TestSceneDeleteLocalScore.cs | 10 ++++- osu.Game/OsuGame.cs | 4 +- .../Sections/Maintenance/GeneralSettings.cs | 2 +- osu.Game/Scoring/ScoreManager.cs | 31 +++++++++++--- .../Select/BeatmapClearScoresDialog.cs | 9 ++--- .../Screens/Select/Carousel/TopLocalRank.cs | 20 +++++++--- .../Select/Leaderboards/BeatmapLeaderboard.cs | 40 +++++++++++-------- 9 files changed, 81 insertions(+), 41 deletions(-) diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs index bbc92b7817..f00fb97dfa 100644 --- a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs +++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs @@ -142,7 +142,7 @@ namespace osu.Game.Tests.Scores.IO var scoreManager = osu.Dependencies.Get(); beatmapManager.Delete(beatmapManager.QueryBeatmapSet(s => s.Beatmaps.Any(b => b.ID == imported.BeatmapInfo.ID))); - Assert.That(scoreManager.Query(s => s.Equals(imported)).DeletePending, Is.EqualTo(true)); + Assert.That(scoreManager.Query(s => s.Equals(imported)).Value.DeletePending, Is.EqualTo(true)); var secondImport = await LoadScoreIntoOsu(osu, imported); Assert.That(secondImport, Is.Null); @@ -187,7 +187,7 @@ namespace osu.Game.Tests.Scores.IO var scoreManager = osu.Dependencies.Get(); await scoreManager.Import(score, archive); - return scoreManager.GetAllUsableScores().FirstOrDefault(); + return scoreManager.Query(_ => true).Value; } internal class TestArchiveReader : ArchiveReader diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 566ee52247..62b0f10fd8 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -184,7 +184,7 @@ namespace osu.Game.Tests.Visual.SongSelect private void clearScores() { - AddStep("Clear all scores", () => scoreManager.Delete(scoreManager.GetAllUsableScores())); + AddStep("Clear all scores", () => scoreManager.Delete()); } private void checkCount(int expected) => diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index 1c75ef1895..5c4759a466 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -44,6 +44,9 @@ namespace osu.Game.Tests.Visual.UserInterface private BeatmapInfo beatmapInfo; + [Resolved] + private RealmContextFactory realmFactory { get; set; } + [Cached] private readonly DialogOverlay dialogOverlay; @@ -112,8 +115,11 @@ namespace osu.Game.Tests.Visual.UserInterface [SetUp] public void Setup() => Schedule(() => { - // Due to soft deletions, we can re-use deleted scores between test runs - scoreManager.Undelete(scoreManager.QueryScores(s => s.DeletePending).ToList()); + using (var realm = realmFactory.CreateContext()) + { + // Due to soft deletions, we can re-use deleted scores between test runs + scoreManager.Undelete(realm.All().Where(s => s.DeletePending).ToList()); + } leaderboard.Scores = null; leaderboard.FinishTransforms(true); // After setting scores, we may be waiting for transforms to expire drawables diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 4539409d4c..49893a6d36 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -488,10 +488,10 @@ namespace osu.Game ScoreInfo databasedScoreInfo = null; if (score.OnlineID > 0) - databasedScoreInfo = ScoreManager.Query(s => s.OnlineID == score.OnlineID); + databasedScoreInfo = ScoreManager.Query(s => s.OnlineID == score.OnlineID)?.Value; if (score is ScoreInfo scoreInfo) - databasedScoreInfo ??= ScoreManager.Query(s => s.Hash == scoreInfo.Hash); + databasedScoreInfo ??= ScoreManager.Query(s => s.Hash == scoreInfo.Hash)?.Value; if (databasedScoreInfo == null) { diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs index 98ccbf85fd..51e4d61d2e 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs @@ -80,7 +80,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance dialogOverlay?.Push(new MassDeleteConfirmationDialog(() => { deleteScoresButton.Enabled.Value = false; - Task.Run(() => scores.Delete(scores.GetAllUsableScores())).ContinueWith(t => Schedule(() => deleteScoresButton.Enabled.Value = true)); + Task.Run(() => scores.Delete()).ContinueWith(t => Schedule(() => deleteScoresButton.Enabled.Value = true)); })); } }); diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 7adc70dab0..d094e1f45c 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -26,6 +26,7 @@ namespace osu.Game.Scoring { public class ScoreManager : IModelManager, IModelImporter { + private readonly RealmContextFactory contextFactory; private readonly Scheduler scheduler; private readonly Func difficulties; private readonly OsuConfigManager configManager; @@ -34,6 +35,7 @@ namespace osu.Game.Scoring public ScoreManager(RulesetStore rulesets, Func beatmaps, Storage storage, RealmContextFactory contextFactory, Scheduler scheduler, IIpcHost importHost = null, Func difficulties = null, OsuConfigManager configManager = null) { + this.contextFactory = contextFactory; this.scheduler = scheduler; this.difficulties = difficulties; this.configManager = configManager; @@ -43,11 +45,16 @@ namespace osu.Game.Scoring public Score GetScore(ScoreInfo score) => scoreModelManager.GetScore(score); - public List GetAllUsableScores() => scoreModelManager.GetAllUsableScores(); - - public IEnumerable QueryScores(Expression> query) => scoreModelManager.QueryScores(query); - - public ScoreInfo Query(Expression> query) => scoreModelManager.Query(query); + /// + /// Perform a lookup query on available s. + /// + /// The query. + /// The first result for the provided query, or null if no results were found. + public ILive Query(Expression> query) + { + using (var context = contextFactory.CreateContext()) + return context.All().FirstOrDefault(query)?.ToLive(); + } /// /// Orders an array of s by total score. @@ -267,6 +274,20 @@ namespace osu.Game.Scoring return scoreModelManager.Delete(item); } + public void Delete([CanBeNull] Expression> filter = null, bool silent = false) + { + using (var context = contextFactory.CreateContext()) + { + var items = context.All() + .Where(s => !s.DeletePending); + + if (filter != null) + items = items.Where(filter); + + scoreModelManager.Delete(items.ToList(), silent); + } + } + public void Delete(List items, bool silent = false) { scoreModelManager.Delete(items, silent); diff --git a/osu.Game/Screens/Select/BeatmapClearScoresDialog.cs b/osu.Game/Screens/Select/BeatmapClearScoresDialog.cs index 4970db8955..afae858b46 100644 --- a/osu.Game/Screens/Select/BeatmapClearScoresDialog.cs +++ b/osu.Game/Screens/Select/BeatmapClearScoresDialog.cs @@ -1,14 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using System.Threading.Tasks; using osu.Framework.Allocation; +using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; using osu.Game.Overlays.Dialog; using osu.Game.Scoring; -using System; -using System.Linq; -using System.Threading.Tasks; -using osu.Framework.Graphics.Sprites; namespace osu.Game.Screens.Select { @@ -29,7 +28,7 @@ namespace osu.Game.Screens.Select Text = @"Yes. Please.", Action = () => { - Task.Run(() => scoreManager.Delete(scoreManager.QueryScores(s => !s.DeletePending && s.BeatmapInfo.ID == beatmapInfo.ID).ToList())) + Task.Run(() => scoreManager.Delete(s => !s.DeletePending && s.BeatmapInfo.ID == beatmapInfo.ID)) .ContinueWith(_ => onCompletion); } }, diff --git a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs index 34129f232c..a83cb08e1f 100644 --- a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs +++ b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs @@ -7,6 +7,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Threading; using osu.Game.Beatmaps; +using osu.Game.Database; using osu.Game.Online.API; using osu.Game.Online.Leaderboards; using osu.Game.Rulesets; @@ -24,6 +25,9 @@ namespace osu.Game.Screens.Select.Carousel [Resolved] private IBindable ruleset { get; set; } + [Resolved] + private RealmContextFactory realmFactory { get; set; } + [Resolved] private IAPIProvider api { get; set; } @@ -54,7 +58,7 @@ namespace osu.Game.Screens.Select.Carousel private void fetchAndLoadTopScore() { - var rank = fetchTopScore()?.Rank; + var rank = fetchTopScoreRank(); scheduledRankUpdate = Schedule(() => { Rank = rank; @@ -67,14 +71,18 @@ namespace osu.Game.Screens.Select.Carousel // We're present if a rank is set, or if there is a pending rank update (IsPresent = true is required for the scheduler to run). public override bool IsPresent => base.IsPresent && (Rank != null || scheduledRankUpdate?.Completed == false); - private ScoreInfo fetchTopScore() + private ScoreRank? fetchTopScoreRank() { - if (scores == null || beatmapInfo == null || ruleset?.Value == null || api?.LocalUser.Value == null) + if (realmFactory == null || beatmapInfo == null || ruleset?.Value == null || api?.LocalUser.Value == null) return null; - return scores.QueryScores(s => s.UserID == api.LocalUser.Value.Id && s.BeatmapInfoID == beatmapInfo.ID && s.RulesetID == ruleset.Value.ID && !s.DeletePending) - .OrderByDescending(s => s.TotalScore) - .FirstOrDefault(); + using (var realm = realmFactory.CreateContext()) + { + return realm.All().Where(s => s.UserID == api.LocalUser.Value.Id && s.BeatmapInfoID == beatmapInfo.ID && s.RulesetID == ruleset.Value.ID && !s.DeletePending) + .OrderByDescending(s => s.TotalScore) + .FirstOrDefault() + ?.Rank; + } } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 7026f240c8..e47acc5ba7 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -10,6 +10,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Game.Beatmaps; +using osu.Game.Database; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.Leaderboards; @@ -26,6 +27,9 @@ namespace osu.Game.Screens.Select.Leaderboards [Resolved] private RulesetStore rulesets { get; set; } + [Resolved] + private RealmContextFactory realmFactory { get; set; } + private BeatmapInfo beatmapInfo; public BeatmapInfo BeatmapInfo @@ -126,26 +130,28 @@ namespace osu.Game.Screens.Select.Leaderboards if (Scope == BeatmapLeaderboardScope.Local) { - var scores = scoreManager - .QueryScores(s => !s.DeletePending && s.BeatmapInfo.ID == fetchBeatmapInfo.ID && s.Ruleset.OnlineID == ruleset.Value.ID); - - if (filterMods && !mods.Value.Any()) + using (var realm = realmFactory.CreateContext()) { - // we need to filter out all scores that have any mods to get all local nomod scores - scores = scores.Where(s => !s.Mods.Any()); - } - else if (filterMods) - { - // otherwise find all the scores that have *any* of the currently selected mods (similar to how web applies mod filters) - // we're creating and using a string list representation of selected mods so that it can be translated into the DB query itself - var selectedMods = mods.Value.Select(m => m.Acronym); - scores = scores.Where(s => s.Mods.Any(m => selectedMods.Contains(m.Acronym))); - } + var scores = realm.All().Where(s => !s.DeletePending && s.BeatmapInfo.ID == fetchBeatmapInfo.ID && s.Ruleset.OnlineID == ruleset.Value.ID); - scoreManager.OrderByTotalScoreAsync(scores.ToArray(), cancellationToken) - .ContinueWith(task => scoresCallback?.Invoke(task.GetResultSafely()), TaskContinuationOptions.OnlyOnRanToCompletion); + if (filterMods && !mods.Value.Any()) + { + // we need to filter out all scores that have any mods to get all local nomod scores + scores = scores.Where(s => !s.Mods.Any()); + } + else if (filterMods) + { + // otherwise find all the scores that have *any* of the currently selected mods (similar to how web applies mod filters) + // we're creating and using a string list representation of selected mods so that it can be translated into the DB query itself + var selectedMods = mods.Value.Select(m => m.Acronym); + scores = scores.Where(s => s.Mods.Any(m => selectedMods.Contains(m.Acronym))); + } - return null; + scoreManager.OrderByTotalScoreAsync(scores.ToArray(), cancellationToken) + .ContinueWith(ordered => scoresCallback?.Invoke(ordered.Result), TaskContinuationOptions.OnlyOnRanToCompletion); + + return null; + } } if (api?.IsLoggedIn != true) From d70e292828b80790d0282002cfec4ece06db38a0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 14 Dec 2021 19:46:55 +0900 Subject: [PATCH 196/996] Remove old EF classes --- osu.Game/Database/ArchiveModelManager.cs | 838 ------------------ osu.Game/Database/DatabaseBackedStore.cs | 52 -- .../Database/MutableDatabaseBackedStore.cs | 161 ---- ...ableDatabaseBackedStoreWithFileIncludes.cs | 27 - osu.Game/Stores/RealmArchiveModelManager.cs | 2 +- 5 files changed, 1 insertion(+), 1079 deletions(-) delete mode 100644 osu.Game/Database/ArchiveModelManager.cs delete mode 100644 osu.Game/Database/DatabaseBackedStore.cs delete mode 100644 osu.Game/Database/MutableDatabaseBackedStore.cs delete mode 100644 osu.Game/Database/MutableDatabaseBackedStoreWithFileIncludes.cs diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs deleted file mode 100644 index 9c26451d40..0000000000 --- a/osu.Game/Database/ArchiveModelManager.cs +++ /dev/null @@ -1,838 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Humanizer; -using JetBrains.Annotations; -using Microsoft.EntityFrameworkCore; -using osu.Framework.Extensions; -using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Framework.Logging; -using osu.Framework.Platform; -using osu.Framework.Threading; -using osu.Game.Extensions; -using osu.Game.IO; -using osu.Game.IO.Archives; -using osu.Game.IPC; -using osu.Game.Overlays.Notifications; - -namespace osu.Game.Database -{ - /// - /// Encapsulates a model store class to give it import functionality. - /// Adds cross-functionality with to give access to the central file store for the provided model. - /// - /// The model type. - /// The associated file join type. - public abstract class ArchiveModelManager : IModelImporter, IModelManager, IModelFileManager - where TModel : class, IHasFiles, IHasPrimaryKey, ISoftDelete - where TFileModel : class, INamedFileInfo, IHasPrimaryKey, new() - { - private const int import_queue_request_concurrency = 1; - - /// - /// The size of a batch import operation before considering it a lower priority operation. - /// - private const int low_priority_import_batch_size = 1; - - /// - /// A singleton scheduler shared by all . - /// - /// - /// This scheduler generally performs IO and CPU intensive work so concurrency is limited harshly. - /// It is mainly being used as a queue mechanism for large imports. - /// - private static readonly ThreadedTaskScheduler import_scheduler = new ThreadedTaskScheduler(import_queue_request_concurrency, nameof(ArchiveModelManager)); - - /// - /// A second scheduler for lower priority imports. - /// For simplicity, these will just run in parallel with normal priority imports, but a future refactor would see this implemented via a custom scheduler/queue. - /// See https://gist.github.com/peppy/f0e118a14751fc832ca30dd48ba3876b for an incomplete version of this. - /// - private static readonly ThreadedTaskScheduler import_scheduler_low_priority = new ThreadedTaskScheduler(import_queue_request_concurrency, nameof(ArchiveModelManager)); - - public Action PostNotification { protected get; set; } - - /// - /// Fired when a new or updated becomes available in the database. - /// This is not guaranteed to run on the update thread. - /// - public event Action ItemUpdated; - - /// - /// Fired when a is removed from the database. - /// This is not guaranteed to run on the update thread. - /// - public event Action ItemRemoved; - - public virtual IEnumerable HandledExtensions => new[] { @".zip" }; - - protected readonly FileStore Files; - - protected readonly IDatabaseContextFactory ContextFactory; - - protected readonly MutableDatabaseBackedStore ModelStore; - - // ReSharper disable once NotAccessedField.Local (we should keep a reference to this so it is not finalised) - private ArchiveImportIPCChannel ipc; - - protected ArchiveModelManager(Storage storage, IDatabaseContextFactory contextFactory, MutableDatabaseBackedStoreWithFileIncludes modelStore, IIpcHost importHost = null) - { - ContextFactory = contextFactory; - - ModelStore = modelStore; - ModelStore.ItemUpdated += item => handleEvent(() => ItemUpdated?.Invoke(item)); - ModelStore.ItemRemoved += item => handleEvent(() => ItemRemoved?.Invoke(item)); - - Files = new FileStore(contextFactory, storage); - - if (importHost != null) - ipc = new ArchiveImportIPCChannel(importHost, this); - - ModelStore.Cleanup(); - } - - /// - /// Import one or more items from filesystem . - /// - /// - /// This will be treated as a low priority import if more than one path is specified; use to always import at standard priority. - /// This will post notifications tracking progress. - /// - /// One or more archive locations on disk. - public Task Import(params string[] paths) - { - var notification = new ImportProgressNotification(); - - PostNotification?.Invoke(notification); - - return Import(notification, paths.Select(p => new ImportTask(p)).ToArray()); - } - - public Task Import(params ImportTask[] tasks) - { - var notification = new ImportProgressNotification(); - - PostNotification?.Invoke(notification); - - return Import(notification, tasks); - } - - public async Task>> Import(ProgressNotification notification, params ImportTask[] tasks) - { - if (tasks.Length == 0) - { - notification.CompletionText = $"No {HumanisedModelName}s were found to import!"; - notification.State = ProgressNotificationState.Completed; - return Enumerable.Empty>(); - } - - notification.Progress = 0; - notification.Text = $"{HumanisedModelName.Humanize(LetterCasing.Title)} import is initialising..."; - - int current = 0; - - var imported = new List>(); - - bool isLowPriorityImport = tasks.Length > low_priority_import_batch_size; - - try - { - await Task.WhenAll(tasks.Select(async task => - { - notification.CancellationToken.ThrowIfCancellationRequested(); - - try - { - var model = await Import(task, isLowPriorityImport, notification.CancellationToken).ConfigureAwait(false); - - lock (imported) - { - if (model != null) - imported.Add(model); - current++; - - notification.Text = $"Imported {current} of {tasks.Length} {HumanisedModelName}s"; - notification.Progress = (float)current / tasks.Length; - } - } - catch (TaskCanceledException) - { - throw; - } - catch (Exception e) - { - Logger.Error(e, $@"Could not import ({task})", LoggingTarget.Database); - } - })).ConfigureAwait(false); - } - catch (OperationCanceledException) - { - if (imported.Count == 0) - { - notification.State = ProgressNotificationState.Cancelled; - return imported; - } - } - - if (imported.Count == 0) - { - notification.Text = $"{HumanisedModelName.Humanize(LetterCasing.Title)} import failed!"; - notification.State = ProgressNotificationState.Cancelled; - } - else - { - notification.CompletionText = imported.Count == 1 - ? $"Imported {imported.First().Value.GetDisplayString()}!" - : $"Imported {imported.Count} {HumanisedModelName}s!"; - - if (imported.Count > 0 && PostImport != null) - { - notification.CompletionText += " Click to view."; - notification.CompletionClickAction = () => - { - PostImport?.Invoke(imported); - return true; - }; - } - - notification.State = ProgressNotificationState.Completed; - } - - return imported; - } - - /// - /// Import one from the filesystem and delete the file on success. - /// Note that this bypasses the UI flow and should only be used for special cases or testing. - /// - /// The containing data about the to import. - /// Whether this is a low priority import. - /// An optional cancellation token. - /// The imported model, if successful. - public async Task> Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default) - { - cancellationToken.ThrowIfCancellationRequested(); - - ILive import; - using (ArchiveReader reader = task.GetReader()) - import = await Import(reader, lowPriority, cancellationToken).ConfigureAwait(false); - - // We may or may not want to delete the file depending on where it is stored. - // e.g. reconstructing/repairing database with items from default storage. - // Also, not always a single file, i.e. for LegacyFilesystemReader - // TODO: Add a check to prevent files from storage to be deleted. - try - { - if (import != null && File.Exists(task.Path) && ShouldDeleteArchive(task.Path)) - File.Delete(task.Path); - } - catch (Exception e) - { - LogForModel(import?.Value, $@"Could not delete original file after import ({task})", e); - } - - return import; - } - - public Action>> PostImport { protected get; set; } - - /// - /// Silently import an item from an . - /// - /// The archive to be imported. - /// Whether this is a low priority import. - /// An optional cancellation token. - public Task> Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default) - { - cancellationToken.ThrowIfCancellationRequested(); - - TModel model = null; - - try - { - model = CreateModel(archive); - - if (model == null) - return Task.FromResult>(null); - } - catch (TaskCanceledException) - { - throw; - } - catch (Exception e) - { - LogForModel(model, @$"Model creation of {archive.Name} failed.", e); - return null; - } - - return Import(model, archive, lowPriority, cancellationToken); - } - - /// - /// Any file extensions which should be included in hash creation. - /// Generally should include all file types which determine the file's uniqueness. - /// Large files should be avoided if possible. - /// - /// - /// This is only used by the default hash implementation. If is overridden, it will not be used. - /// - protected abstract string[] HashableFileTypes { get; } - - internal static void LogForModel(TModel model, string message, Exception e = null) - { - string prefix = $"[{(model?.Hash ?? "?????").Substring(0, 5)}]"; - - if (e != null) - Logger.Error(e, $"{prefix} {message}", LoggingTarget.Database); - else - Logger.Log($"{prefix} {message}", LoggingTarget.Database); - } - - /// - /// Whether the implementation overrides with a custom implementation. - /// Custom hash implementations must bypass the early exit in the import flow (see usage). - /// - protected virtual bool HasCustomHashFunction => false; - - /// - /// Create a SHA-2 hash from the provided archive based on file content of all files matching . - /// - /// - /// In the case of no matching files, a hash will be generated from the passed archive's . - /// - protected virtual string ComputeHash(TModel item) - { - var hashableFiles = item.Files - .Where(f => HashableFileTypes.Any(ext => f.Filename.EndsWith(ext, StringComparison.OrdinalIgnoreCase))) - .OrderBy(f => f.Filename) - .ToArray(); - - if (hashableFiles.Length > 0) - { - // for now, concatenate all hashable files in the set to create a unique hash. - MemoryStream hashable = new MemoryStream(); - - foreach (TFileModel file in hashableFiles) - { - using (Stream s = Files.Store.GetStream(file.FileInfo.GetStoragePath())) - s.CopyTo(hashable); - } - - if (hashable.Length > 0) - return hashable.ComputeSHA2Hash(); - } - - return generateFallbackHash(); - } - - /// - /// Silently import an item from a . - /// - /// The model to be imported. - /// An optional archive to use for model population. - /// Whether this is a low priority import. - /// An optional cancellation token. - public virtual async Task> Import(TModel item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) => await Task.Factory.StartNew(async () => - { - cancellationToken.ThrowIfCancellationRequested(); - - bool checkedExisting = false; - TModel existing = null; - - if (archive != null && !HasCustomHashFunction) - { - // this is a fast bail condition to improve large import performance. - item.Hash = computeHashFast(archive); - - checkedExisting = true; - existing = CheckForExisting(item); - - if (existing != null) - { - // bare minimum comparisons - // - // note that this should really be checking filesizes on disk (of existing files) for some degree of sanity. - // or alternatively doing a faster hash check. either of these require database changes and reprocessing of existing files. - if (CanSkipImport(existing, item) && - getFilenames(existing.Files).SequenceEqual(getShortenedFilenames(archive).Select(p => p.shortened).OrderBy(f => f))) - { - LogForModel(item, @$"Found existing (optimised) {HumanisedModelName} for {item} (ID {existing.ID}) – skipping import."); - Undelete(existing); - return existing.ToEntityFrameworkLive(); - } - - LogForModel(item, @"Found existing (optimised) but failed pre-check."); - } - } - - void rollback() - { - if (!Delete(item)) - { - // We may have not yet added the model to the underlying table, but should still clean up files. - LogForModel(item, @"Dereferencing files for incomplete import."); - Files.Dereference(item.Files.Select(f => f.FileInfo).ToArray()); - } - } - - delayEvents(); - - try - { - LogForModel(item, @"Beginning import..."); - - if (archive != null) - item.Files.AddRange(createFileInfos(archive, Files)); - item.Hash = ComputeHash(item); - - await Populate(item, archive, cancellationToken).ConfigureAwait(false); - - using (var write = ContextFactory.GetForWrite()) // used to share a context for full import. keep in mind this will block all writes. - { - try - { - if (!write.IsTransactionLeader) throw new InvalidOperationException(@$"Ensure there is no parent transaction so errors can correctly be handled by {this}"); - - if (!checkedExisting) - existing = CheckForExisting(item); - - if (existing != null) - { - if (CanReuseExisting(existing, item)) - { - Undelete(existing); - LogForModel(item, @$"Found existing {HumanisedModelName} for {item} (ID {existing.ID}) – skipping import."); - // existing item will be used; rollback new import and exit early. - rollback(); - flushEvents(true); - return existing.ToEntityFrameworkLive(); - } - - LogForModel(item, @"Found existing but failed re-use check."); - Delete(existing); - ModelStore.PurgeDeletable(s => s.ID == existing.ID); - } - - PreImport(item); - - // import to store - ModelStore.Add(item); - } - catch (Exception e) - { - write.Errors.Add(e); - throw; - } - } - - LogForModel(item, @"Import successfully completed!"); - } - catch (Exception e) - { - if (!(e is TaskCanceledException)) - LogForModel(item, @"Database import or population failed and has been rolled back.", e); - - rollback(); - flushEvents(false); - throw; - } - - flushEvents(true); - return item.ToEntityFrameworkLive(); - }, cancellationToken, TaskCreationOptions.HideScheduler, lowPriority ? import_scheduler_low_priority : import_scheduler).Unwrap().ConfigureAwait(false); - - /// - /// Replace an existing file with a new version. - /// - /// The item to operate on. - /// The existing file to be replaced. - /// The new file contents. - public void ReplaceFile(TModel model, TFileModel file, Stream contents) - { - using (ContextFactory.GetForWrite()) - { - DeleteFile(model, file); - AddFile(model, contents, file.Filename); - } - } - - /// - /// Delete an existing file. - /// - /// The item to operate on. - /// The existing file to be deleted. - public void DeleteFile(TModel model, TFileModel file) - { - using (var usage = ContextFactory.GetForWrite()) - { - // Dereference the existing file info, since the file model will be removed. - if (file.FileInfo != null) - { - Files.Dereference(file.FileInfo); - - if (file.IsManaged) - { - // This shouldn't be required, but here for safety in case the provided TModel is not being change tracked - // Definitely can be removed once we rework the database backend. - usage.Context.Set().Remove(file); - } - } - - model.Files.Remove(file); - } - } - - /// - /// Add a new file. - /// - /// The item to operate on. - /// The new file contents. - /// The filename for the new file. - public void AddFile(TModel model, Stream contents, string filename) - { - using (ContextFactory.GetForWrite()) - { - model.Files.Add(new TFileModel - { - Filename = filename, - FileInfo = Files.Add(contents) - }); - } - - if (model.IsManaged) - Update(model); - } - - /// - /// Perform an update of the specified item. - /// TODO: Support file additions/removals. - /// - /// The item to update. - public void Update(TModel item) - { - using (ContextFactory.GetForWrite()) - { - item.Hash = ComputeHash(item); - ModelStore.Update(item); - } - } - - /// - /// Delete an item from the manager. - /// Is a no-op for already deleted items. - /// - /// The item to delete. - /// false if no operation was performed - public bool Delete(TModel item) - { - using (ContextFactory.GetForWrite()) - { - // re-fetch the model on the import context. - var foundModel = queryModel().Include(s => s.Files).ThenInclude(f => f.FileInfo).FirstOrDefault(s => s.ID == item.ID); - - if (foundModel == null || foundModel.DeletePending) return false; - - if (ModelStore.Delete(foundModel)) - Files.Dereference(foundModel.Files.Select(f => f.FileInfo).ToArray()); - return true; - } - } - - /// - /// Delete multiple items. - /// This will post notifications tracking progress. - /// - public void Delete(List items, bool silent = false) - { - if (items.Count == 0) return; - - var notification = new ProgressNotification - { - Progress = 0, - Text = $"Preparing to delete all {HumanisedModelName}s...", - CompletionText = $"Deleted all {HumanisedModelName}s!", - State = ProgressNotificationState.Active, - }; - - if (!silent) - PostNotification?.Invoke(notification); - - int i = 0; - - foreach (var b in items) - { - if (notification.State == ProgressNotificationState.Cancelled) - // user requested abort - return; - - notification.Text = $"Deleting {HumanisedModelName}s ({++i} of {items.Count})"; - - Delete(b); - - notification.Progress = (float)i / items.Count; - } - - notification.State = ProgressNotificationState.Completed; - } - - /// - /// Restore multiple items that were previously deleted. - /// This will post notifications tracking progress. - /// - public void Undelete(List items, bool silent = false) - { - if (!items.Any()) return; - - var notification = new ProgressNotification - { - CompletionText = "Restored all deleted items!", - Progress = 0, - State = ProgressNotificationState.Active, - }; - - if (!silent) - PostNotification?.Invoke(notification); - - int i = 0; - - foreach (var item in items) - { - if (notification.State == ProgressNotificationState.Cancelled) - // user requested abort - return; - - notification.Text = $"Restoring ({++i} of {items.Count})"; - - Undelete(item); - - notification.Progress = (float)i / items.Count; - } - - notification.State = ProgressNotificationState.Completed; - } - - /// - /// Restore an item that was previously deleted. Is a no-op if the item is not in a deleted state, or has its protected flag set. - /// - /// The item to restore - public void Undelete(TModel item) - { - using (var usage = ContextFactory.GetForWrite()) - { - usage.Context.ChangeTracker.AutoDetectChangesEnabled = false; - - if (!ModelStore.Undelete(item)) return; - - Files.Reference(item.Files.Select(f => f.FileInfo).ToArray()); - - usage.Context.ChangeTracker.AutoDetectChangesEnabled = true; - } - } - - private string computeHashFast(ArchiveReader reader) - { - MemoryStream hashable = new MemoryStream(); - - foreach (string file in reader.Filenames.Where(f => HashableFileTypes.Any(ext => f.EndsWith(ext, StringComparison.OrdinalIgnoreCase))).OrderBy(f => f)) - { - using (Stream s = reader.GetStream(file)) - s.CopyTo(hashable); - } - - if (hashable.Length > 0) - return hashable.ComputeSHA2Hash(); - - return generateFallbackHash(); - } - - /// - /// Create all required s for the provided archive, adding them to the global file store. - /// - private List createFileInfos(ArchiveReader reader, FileStore files) - { - var fileInfos = new List(); - - // import files to manager - foreach (var filenames in getShortenedFilenames(reader)) - { - using (Stream s = reader.GetStream(filenames.original)) - { - fileInfos.Add(new TFileModel - { - Filename = filenames.shortened, - FileInfo = files.Add(s) - }); - } - } - - return fileInfos; - } - - private IEnumerable<(string original, string shortened)> getShortenedFilenames(ArchiveReader reader) - { - string prefix = reader.Filenames.GetCommonPrefix(); - if (!(prefix.EndsWith('/') || prefix.EndsWith('\\'))) - prefix = string.Empty; - - // import files to manager - foreach (string file in reader.Filenames) - yield return (file, file.Substring(prefix.Length).ToStandardisedPath()); - } - - #region osu-stable import - - /// - /// Whether this specified path should be removed after successful import. - /// - /// The path for consideration. May be a file or a directory. - /// Whether to perform deletion. - protected virtual bool ShouldDeleteArchive(string path) => false; - - #endregion - - /// - /// Create a barebones model from the provided archive. - /// Actual expensive population should be done in ; this should just prepare for duplicate checking. - /// - /// The archive to create the model for. - /// A model populated with minimal information. Returning a null will abort importing silently. - protected abstract TModel CreateModel(ArchiveReader archive); - - /// - /// Populate the provided model completely from the given archive. - /// After this method, the model should be in a state ready to commit to a store. - /// - /// The model to populate. - /// The archive to use as a reference for population. May be null. - /// An optional cancellation token. - protected abstract Task Populate(TModel model, [CanBeNull] ArchiveReader archive, CancellationToken cancellationToken = default); - - /// - /// Perform any final actions before the import to database executes. - /// - /// The model prepared for import. - protected virtual void PreImport(TModel model) - { - } - - /// - /// Check whether an existing model already exists for a new import item. - /// - /// The new model proposed for import. - /// An existing model which matches the criteria to skip importing, else null. - protected TModel CheckForExisting(TModel model) => model.Hash == null ? null : ModelStore.ConsumableItems.FirstOrDefault(b => b.Hash == model.Hash); - - public bool IsAvailableLocally(TModel model) => CheckLocalAvailability(model, ModelStore.ConsumableItems.Where(m => !m.DeletePending)); - - /// - /// Performs implementation specific comparisons to determine whether a given model is present in the local store. - /// - /// The whose existence needs to be checked. - /// The usable items present in the store. - /// Whether the exists. - protected virtual bool CheckLocalAvailability(TModel model, IQueryable items) - => model.IsManaged && items.Any(i => i.ID == model.ID && i.Files.Any()); - - /// - /// Whether import can be skipped after finding an existing import early in the process. - /// Only valid when is not overridden. - /// - /// The existing model. - /// The newly imported model. - /// Whether to skip this import completely. - protected virtual bool CanSkipImport(TModel existing, TModel import) => true; - - /// - /// After an existing is found during an import process, the default behaviour is to use/restore the existing - /// item and skip the import. This method allows changing that behaviour. - /// - /// The existing model. - /// The newly imported model. - /// Whether the existing model should be restored and used. Returning false will delete the existing and force a re-import. - protected virtual bool CanReuseExisting(TModel existing, TModel import) => - // for the best or worst, we copy and import files of a new import before checking whether - // it is a duplicate. so to check if anything has changed, we can just compare all FileInfo IDs. - getIDs(existing.Files).SequenceEqual(getIDs(import.Files)) && - getFilenames(existing.Files).SequenceEqual(getFilenames(import.Files)); - - private IEnumerable getIDs(List files) - { - foreach (var f in files.OrderBy(f => f.Filename)) - yield return f.FileInfo.ID; - } - - private IEnumerable getFilenames(List files) - { - foreach (var f in files.OrderBy(f => f.Filename)) - yield return f.Filename; - } - - private DbSet queryModel() => ContextFactory.Get().Set(); - - public virtual string HumanisedModelName => $"{typeof(TModel).Name.Replace(@"Info", "").ToLower()}"; - - #region Event handling / delaying - - private readonly List queuedEvents = new List(); - - /// - /// Allows delaying of outwards events until an operation is confirmed (at a database level). - /// - private bool delayingEvents; - - /// - /// Begin delaying outwards events. - /// - private void delayEvents() => delayingEvents = true; - - /// - /// Flush delayed events and disable delaying. - /// - /// Whether the flushed events should be performed. - private void flushEvents(bool perform) - { - Action[] events; - - lock (queuedEvents) - { - events = queuedEvents.ToArray(); - queuedEvents.Clear(); - } - - if (perform) - { - foreach (var a in events) - a.Invoke(); - } - - delayingEvents = false; - } - - private void handleEvent(Action a) - { - if (delayingEvents) - { - lock (queuedEvents) - queuedEvents.Add(a); - } - else - a.Invoke(); - } - - #endregion - - private static string generateFallbackHash() - { - // if a hash could no be generated from file content, presume a unique / new import. - // therefore, let's use a guaranteed unique hash. - // this doesn't follow the SHA2 hashing schema intentionally, so such entries on the data store can be identified. - return Guid.NewGuid().ToString(); - } - } -} diff --git a/osu.Game/Database/DatabaseBackedStore.cs b/osu.Game/Database/DatabaseBackedStore.cs deleted file mode 100644 index a37155ee62..0000000000 --- a/osu.Game/Database/DatabaseBackedStore.cs +++ /dev/null @@ -1,52 +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.Linq; -using Microsoft.EntityFrameworkCore; -using osu.Framework.Platform; - -namespace osu.Game.Database -{ - public abstract class DatabaseBackedStore - { - protected readonly Storage Storage; - - protected readonly RealmContextFactory ContextFactory; - - /// - /// Refresh an instance potentially from a different thread with a local context-tracked instance. - /// - /// The object to use as a reference when negotiating a local instance. - /// An optional lookup source which will be used to query and populate a freshly retrieved replacement. If not provided, the refreshed object will still be returned but will not have any includes. - /// A valid EF-stored type. - protected void Refresh(ref T obj, IQueryable lookupSource = null) where T : class, IHasPrimaryKey - { - using (var usage = ContextFactory.GetForWrite()) - { - var context = usage.Context; - - if (context.Entry(obj).State != EntityState.Detached) return; - - int id = obj.ID; - var foundObject = lookupSource?.SingleOrDefault(t => t.ID == id) ?? context.Find(id); - if (foundObject != null) - obj = foundObject; - else - context.Add(obj); - } - } - - protected DatabaseBackedStore(RealmContextFactory contextFactory, Storage storage = null) - { - ContextFactory = contextFactory; - Storage = storage; - } - - /// - /// Perform any common clean-up tasks. Should be run when idle, or whenever necessary. - /// - public virtual void Cleanup() - { - } - } -} diff --git a/osu.Game/Database/MutableDatabaseBackedStore.cs b/osu.Game/Database/MutableDatabaseBackedStore.cs deleted file mode 100644 index b0feb7bb78..0000000000 --- a/osu.Game/Database/MutableDatabaseBackedStore.cs +++ /dev/null @@ -1,161 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using osu.Framework.Platform; - -namespace osu.Game.Database -{ - /// - /// A typed store which supports basic addition, deletion and updating for soft-deletable models. - /// - /// The databased model. - public abstract class MutableDatabaseBackedStore : DatabaseBackedStore - where T : class, IHasPrimaryKey, ISoftDelete - { - /// - /// Fired when an item was added or updated. - /// - public event Action ItemUpdated; - - /// - /// Fired when an item was removed. - /// - public event Action ItemRemoved; - - protected MutableDatabaseBackedStore(IDatabaseContextFactory contextFactory, Storage storage = null) - : base(contextFactory, storage) - { - } - - /// - /// Access items pre-populated with includes for consumption. - /// - public IQueryable ConsumableItems => AddIncludesForConsumption(ContextFactory.Get().Set()); - - /// - /// Access barebones items with no includes. - /// - public IQueryable Items => ContextFactory.Get().Set(); - - /// - /// Add a to the database. - /// - /// The item to add. - public void Add(T item) - { - using (var usage = ContextFactory.GetForWrite()) - { - var context = usage.Context; - context.Attach(item); - } - - ItemUpdated?.Invoke(item); - } - - /// - /// Update a in the database. - /// - /// The item to update. - public void Update(T item) - { - using (var usage = ContextFactory.GetForWrite()) - usage.Context.Update(item); - - ItemUpdated?.Invoke(item); - } - - /// - /// Delete a from the database. - /// - /// The item to delete. - public bool Delete(T item) - { - using (ContextFactory.GetForWrite()) - { - Refresh(ref item); - - if (item.DeletePending) return false; - - item.DeletePending = true; - } - - ItemRemoved?.Invoke(item); - return true; - } - - /// - /// Restore a from a deleted state. - /// - /// The item to undelete. - public bool Undelete(T item) - { - using (ContextFactory.GetForWrite()) - { - Refresh(ref item, ConsumableItems); - - if (!item.DeletePending) return false; - - item.DeletePending = false; - } - - ItemUpdated?.Invoke(item); - return true; - } - - /// - /// Allow implementations to add database-side includes or constraints when querying for consumption of items. - /// - /// The input query. - /// A potentially modified output query. - protected virtual IQueryable AddIncludesForConsumption(IQueryable query) => query; - - /// - /// Allow implementations to add database-side includes or constraints when deleting items. - /// Included properties could then be subsequently deleted by overriding . - /// - /// The input query. - /// A potentially modified output query. - protected virtual IQueryable AddIncludesForDeletion(IQueryable query) => query; - - /// - /// Called when removing an item completely from the database. - /// - /// The items to be purged. - /// The write context which can be used to perform subsequent deletions. - protected virtual void Purge(List items, OsuDbContext context) => context.RemoveRange(items); - - public override void Cleanup() - { - base.Cleanup(); - PurgeDeletable(); - } - - /// - /// Purge items in a pending delete state. - /// - /// An optional query limiting the scope of the purge. - public void PurgeDeletable(Expression> query = null) - { - using (var usage = ContextFactory.GetForWrite()) - { - var context = usage.Context; - - var lookup = context.Set().Where(s => s.DeletePending); - - if (query != null) lookup = lookup.Where(query); - - lookup = AddIncludesForDeletion(lookup); - - var purgeable = lookup.ToList(); - - if (!purgeable.Any()) return; - - Purge(purgeable, context); - } - } - } -} diff --git a/osu.Game/Database/MutableDatabaseBackedStoreWithFileIncludes.cs b/osu.Game/Database/MutableDatabaseBackedStoreWithFileIncludes.cs deleted file mode 100644 index 102081cd65..0000000000 --- a/osu.Game/Database/MutableDatabaseBackedStoreWithFileIncludes.cs +++ /dev/null @@ -1,27 +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.Linq; -using Microsoft.EntityFrameworkCore; -using osu.Framework.Platform; - -namespace osu.Game.Database -{ - public abstract class MutableDatabaseBackedStoreWithFileIncludes : MutableDatabaseBackedStore - where T : class, IHasPrimaryKey, ISoftDelete, IHasFiles - where TFileInfo : INamedFileInfo - { - protected MutableDatabaseBackedStoreWithFileIncludes(IDatabaseContextFactory contextFactory, Storage storage = null) - : base(contextFactory, storage) - { - } - - protected override IQueryable AddIncludesForConsumption(IQueryable query) => - base.AddIncludesForConsumption(query) - .Include(s => s.Files).ThenInclude(f => f.FileInfo); - - protected override IQueryable AddIncludesForDeletion(IQueryable query) => - base.AddIncludesForDeletion(query) - .Include(s => s.Files); // don't include FileInfo. these are handled by the FileStore itself. - } -} diff --git a/osu.Game/Stores/RealmArchiveModelManager.cs b/osu.Game/Stores/RealmArchiveModelManager.cs index 87a27cbbbc..7fa8d16882 100644 --- a/osu.Game/Stores/RealmArchiveModelManager.cs +++ b/osu.Game/Stores/RealmArchiveModelManager.cs @@ -19,7 +19,7 @@ using Realms; namespace osu.Game.Stores { /// - /// Class which adds all the missing pieces bridging the gap between and . + /// Class which adds all the missing pieces bridging the gap between and (legacy) ArchiveModelManager. /// public abstract class RealmArchiveModelManager : RealmArchiveModelImporter, IModelManager, IModelFileManager where TModel : RealmObject, IHasRealmFiles, IHasGuidPrimaryKey, ISoftDelete From a3da8dc49d416d4008b4a6dc0b4d75e74211d147 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 14 Dec 2021 19:52:54 +0900 Subject: [PATCH 197/996] Fix missing interface implementation of `IRulesetStore` --- osu.Game/Rulesets/RulesetStore.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index de3bb1ac34..6ec89f9dc5 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -16,7 +16,7 @@ using osu.Game.Database; namespace osu.Game.Rulesets { - public class RulesetStore : IDisposable + public class RulesetStore : IDisposable, IRulesetStore { private readonly RealmContextFactory realmFactory; @@ -259,8 +259,8 @@ namespace osu.Game.Rulesets #region Implementation of IRulesetStore - IRulesetInfo IRulesetStore.GetRuleset(int id) => GetRuleset(id); - IRulesetInfo IRulesetStore.GetRuleset(string shortName) => GetRuleset(shortName); + IRulesetInfo? IRulesetStore.GetRuleset(int id) => GetRuleset(id); + IRulesetInfo? IRulesetStore.GetRuleset(string shortName) => GetRuleset(shortName); IEnumerable IRulesetStore.AvailableRulesets => AvailableRulesets; #endregion From 00e3af3366e80214c7f0c956d144000a78cd0b56 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 14 Dec 2021 19:47:11 +0900 Subject: [PATCH 198/996] Update model manager and many related classes to get things compiling again --- .../Database/BeatmapImporterTests.cs | 42 +- ...eneOnlinePlayBeatmapAvailabilityTracker.cs | 8 +- .../Visual/Multiplayer/QueueModeTestScene.cs | 2 +- .../Multiplayer/TestSceneMultiplayer.cs | 4 +- .../TestSceneMultiplayerMatchSubScreen.cs | 2 +- .../TestSceneMultiplayerPlaylist.cs | 4 +- .../TestSceneMultiplayerQueueList.cs | 4 +- .../TestSceneMultiplayerReadyButton.cs | 2 +- .../TestSceneMultiplayerSpectateButton.cs | 2 +- .../TestScenePlaylistsRoomSettingsPlaylist.cs | 4 +- .../Visual/Multiplayer/TestSceneTeamVersus.cs | 2 +- .../SongSelect/TestSceneBeatmapLeaderboard.cs | 3 +- .../TestSceneBeatmapMetadataDisplay.cs | 2 +- .../TestSceneDeleteLocalScore.cs | 5 +- osu.Game/Beatmaps/BeatmapManager.cs | 28 +- osu.Game/Beatmaps/BeatmapModelManager.cs | 399 +++--------------- osu.Game/Beatmaps/EFBeatmapSetInfo.cs | 4 +- osu.Game/Beatmaps/FlatFileWorkingBeatmap.cs | 2 +- .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 4 +- osu.Game/Database/ImportTask.cs | 3 +- osu.Game/Overlays/MusicController.cs | 2 +- osu.Game/Scoring/LegacyDatabasedScore.cs | 4 +- osu.Game/Scoring/ScoreInfo.cs | 20 +- osu.Game/Scoring/ScoreManager.cs | 1 - osu.Game/Scoring/ScoreModelManager.cs | 5 +- osu.Game/Screens/Menu/IntroScreen.cs | 4 +- osu.Game/Screens/Ranking/SoloResultsScreen.cs | 2 +- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- .../Carousel/DrawableCarouselBeatmapSet.cs | 10 +- osu.Game/Screens/Select/SongSelect.cs | 2 +- osu.Game/Stores/BeatmapImporter.cs | 4 +- .../Drawables/DrawableStoryboard.cs | 4 +- osu.Game/Tests/Beatmaps/TestBeatmap.cs | 2 - osu.Game/Tests/Visual/EditorTestScene.cs | 8 +- 34 files changed, 165 insertions(+), 431 deletions(-) diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index c5dbcf155c..e144f863b0 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -40,7 +40,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realmFactory, storage) => { - using (var importer = new BeatmapImporter(realmFactory, storage)) + using (var importer = new BeatmapModelManager(realmFactory, storage)) using (new RulesetStore(realmFactory, storage)) { ILive? imported; @@ -69,7 +69,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realmFactory, storage) => { - using var importer = new BeatmapImporter(realmFactory, storage); + using var importer = new BeatmapModelManager(realmFactory, storage); using var store = new RulesetStore(realmFactory, storage); await LoadOszIntoStore(importer, realmFactory.Context); @@ -81,7 +81,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realmFactory, storage) => { - using var importer = new BeatmapImporter(realmFactory, storage); + using var importer = new BeatmapModelManager(realmFactory, storage); using var store = new RulesetStore(realmFactory, storage); var imported = await LoadOszIntoStore(importer, realmFactory.Context); @@ -99,7 +99,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realmFactory, storage) => { - using var importer = new BeatmapImporter(realmFactory, storage); + using var importer = new BeatmapModelManager(realmFactory, storage); using var store = new RulesetStore(realmFactory, storage); var imported = await LoadOszIntoStore(importer, realmFactory.Context); @@ -113,7 +113,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realmFactory, storage) => { - using var importer = new BeatmapImporter(realmFactory, storage); + using var importer = new BeatmapModelManager(realmFactory, storage); using var store = new RulesetStore(realmFactory, storage); string? tempPath = TestResources.GetTestBeatmapForImport(); @@ -143,7 +143,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realmFactory, storage) => { - using var importer = new BeatmapImporter(realmFactory, storage); + using var importer = new BeatmapModelManager(realmFactory, storage); using var store = new RulesetStore(realmFactory, storage); var imported = await LoadOszIntoStore(importer, realmFactory.Context); @@ -163,7 +163,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realmFactory, storage) => { - using var importer = new BeatmapImporter(realmFactory, storage); + using var importer = new BeatmapModelManager(realmFactory, storage); using var store = new RulesetStore(realmFactory, storage); string? temp = TestResources.GetTestBeatmapForImport(); @@ -212,7 +212,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realmFactory, storage) => { - using var importer = new BeatmapImporter(realmFactory, storage); + using var importer = new BeatmapModelManager(realmFactory, storage); using var store = new RulesetStore(realmFactory, storage); string? temp = TestResources.GetTestBeatmapForImport(); @@ -264,7 +264,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realmFactory, storage) => { - using var importer = new BeatmapImporter(realmFactory, storage); + using var importer = new BeatmapModelManager(realmFactory, storage); using var store = new RulesetStore(realmFactory, storage); string? temp = TestResources.GetTestBeatmapForImport(); @@ -312,7 +312,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realmFactory, storage) => { - using var importer = new BeatmapImporter(realmFactory, storage); + using var importer = new BeatmapModelManager(realmFactory, storage); using var store = new RulesetStore(realmFactory, storage); string? temp = TestResources.GetTestBeatmapForImport(); @@ -361,7 +361,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realmFactory, storage) => { - using var importer = new BeatmapImporter(realmFactory, storage); + using var importer = new BeatmapModelManager(realmFactory, storage); using var store = new RulesetStore(realmFactory, storage); var imported = await LoadOszIntoStore(importer, realmFactory.Context); @@ -394,7 +394,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realmFactory, storage) => { - using var importer = new BeatmapImporter(realmFactory, storage); + using var importer = new BeatmapModelManager(realmFactory, storage); using var store = new RulesetStore(realmFactory, storage); var progressNotification = new ImportProgressNotification(); @@ -430,7 +430,7 @@ namespace osu.Game.Tests.Database Interlocked.Increment(ref loggedExceptionCount); }; - using var importer = new BeatmapImporter(realmFactory, storage); + using var importer = new BeatmapModelManager(realmFactory, storage); using var store = new RulesetStore(realmFactory, storage); var imported = await LoadOszIntoStore(importer, realmFactory.Context); @@ -480,7 +480,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realmFactory, storage) => { - using var importer = new BeatmapImporter(realmFactory, storage); + using var importer = new BeatmapModelManager(realmFactory, storage); using var store = new RulesetStore(realmFactory, storage); var imported = await LoadOszIntoStore(importer, realmFactory.Context); @@ -528,7 +528,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realmFactory, storage) => { - using var importer = new BeatmapImporter(realmFactory, storage); + using var importer = new BeatmapModelManager(realmFactory, storage); using var store = new RulesetStore(realmFactory, storage); var imported = await LoadOszIntoStore(importer, realmFactory.Context); @@ -554,7 +554,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realmFactory, storage) => { - using var importer = new BeatmapImporter(realmFactory, storage); + using var importer = new BeatmapModelManager(realmFactory, storage); using var store = new RulesetStore(realmFactory, storage); var metadata = new BeatmapMetadata @@ -600,7 +600,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realmFactory, storage) => { - using var importer = new BeatmapImporter(realmFactory, storage); + using var importer = new BeatmapModelManager(realmFactory, storage); using var store = new RulesetStore(realmFactory, storage); string? temp = TestResources.GetTestBeatmapForImport(); @@ -617,7 +617,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realmFactory, storage) => { - using var importer = new BeatmapImporter(realmFactory, storage); + using var importer = new BeatmapModelManager(realmFactory, storage); using var store = new RulesetStore(realmFactory, storage); string? temp = TestResources.GetTestBeatmapForImport(); @@ -653,7 +653,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realmFactory, storage) => { - using var importer = new BeatmapImporter(realmFactory, storage); + using var importer = new BeatmapModelManager(realmFactory, storage); using var store = new RulesetStore(realmFactory, storage); string? temp = TestResources.GetTestBeatmapForImport(); @@ -695,7 +695,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realmFactory, storage) => { - using var importer = new BeatmapImporter(realmFactory, storage); + using var importer = new BeatmapModelManager(realmFactory, storage); using var store = new RulesetStore(realmFactory, storage); string? temp = TestResources.GetTestBeatmapForImport(); @@ -746,7 +746,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realmFactory, storage) => { - using var importer = new BeatmapImporter(realmFactory, storage); + using var importer = new BeatmapModelManager(realmFactory, storage); using var store = new RulesetStore(realmFactory, storage); string? temp = TestResources.GetTestBeatmapForImport(); diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index 2cab823526..5a2349e776 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -172,17 +172,17 @@ namespace osu.Game.Tests.Online { } - protected override BeatmapModelManager CreateBeatmapModelManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, GameHost host) + protected override BeatmapModelManager CreateBeatmapModelManager(Storage storage, RealmContextFactory contextFactory, RulesetStore rulesets, BeatmapOnlineLookupQueue onlineLookupQueue) { - return new TestBeatmapModelManager(this, storage, contextFactory, rulesets, api, host); + return new TestBeatmapModelManager(this, storage, contextFactory, rulesets, onlineLookupQueue); } internal class TestBeatmapModelManager : BeatmapModelManager { private readonly TestBeatmapManager testBeatmapManager; - public TestBeatmapModelManager(TestBeatmapManager testBeatmapManager, Storage storage, IDatabaseContextFactory databaseContextFactory, RulesetStore rulesetStore, IAPIProvider apiProvider, GameHost gameHost) - : base(storage, databaseContextFactory, rulesetStore, gameHost) + public TestBeatmapModelManager(TestBeatmapManager testBeatmapManager, Storage storage, RealmContextFactory databaseContextFactory, RulesetStore rulesetStore, BeatmapOnlineLookupQueue beatmapOnlineLookupQueue) + : base(databaseContextFactory, storage, beatmapOnlineLookupQueue) { this.testBeatmapManager = testBeatmapManager; } diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs index c4d7bd7e6a..a993190a67 100644 --- a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs +++ b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs @@ -58,7 +58,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("import beatmap", () => { beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); - importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First(); + importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable().First(); InitialBeatmap = importedSet.Beatmaps.First(b => b.RulesetID == 0); OtherBeatmap = importedSet.Beatmaps.Last(b => b.RulesetID == 0); }); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 3d8c5298dc..18f45086f6 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -72,7 +72,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("import beatmap", () => { beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); - importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First(); + importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable().First(); }); AddStep("load multiplayer", () => LoadScreen(multiplayerComponents = new TestMultiplayerComponents())); @@ -588,7 +588,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("restore beatmap", () => { beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); - importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First(); + importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable().First(); }); AddUntilStep("play started", () => multiplayerComponents.CurrentScreen is SpectatorScreen); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index 52e46ef5af..6b6a02906e 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); - importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First(); + importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable().First(); } [SetUp] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs index 464c0ea5b6..251f407a85 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs @@ -55,7 +55,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("import beatmap", () => { beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); - importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First(); + importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable().First(); importedBeatmap = importedSet.Beatmaps.First(b => b.RulesetID == 0); }); @@ -169,7 +169,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private void addItemStep(bool expired = false) => AddStep("add item", () => Client.AddPlaylistItem(new MultiplayerPlaylistItem(new PlaylistItem { Beatmap = { Value = importedBeatmap }, - BeatmapID = importedBeatmap.OnlineID ?? -1, + BeatmapID = importedBeatmap.OnlineID, Expired = expired, PlayedAt = DateTimeOffset.Now }))); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs index 29daff546d..ad47745642 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs @@ -60,7 +60,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("import beatmap", () => { beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); - importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First(); + importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable().First(); importedBeatmap = importedSet.Beatmaps.First(b => b.RulesetID == 0); }); @@ -126,7 +126,7 @@ namespace osu.Game.Tests.Visual.Multiplayer MultiplayerPlaylistItem item = new MultiplayerPlaylistItem(new PlaylistItem { Beatmap = { Value = importedBeatmap }, - BeatmapID = importedBeatmap.OnlineID ?? -1, + BeatmapID = importedBeatmap.OnlineID, }); Client.AddUserPlaylistItem(userId(), item); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs index 8f51b1e381..59e5318263 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs @@ -50,7 +50,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { AvailabilityTracker.SelectedItem.BindTo(selectedItem); - importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First(); + importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable().First(); Beatmap.Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()); selectedItem.Value = new PlaylistItem { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs index d4ff9f8c41..5466a19657 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs @@ -51,7 +51,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { AvailabilityTracker.SelectedItem.BindTo(selectedItem); - importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First(); + importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable().First(); Beatmap.Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()); selectedItem.Value = new PlaylistItem { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomSettingsPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomSettingsPlaylist.cs index 93ccd5f1e1..e63e58824f 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomSettingsPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomSettingsPlaylist.cs @@ -12,7 +12,7 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Database; using osu.Game.Graphics.Containers; -using osu.Game.Online.API.Requests.Responses; +using osu.Game.Models; using osu.Game.Online.Rooms; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; @@ -155,7 +155,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Metadata = new BeatmapMetadata { Artist = "Artist", - Author = new APIUser { Username = "Creator name here" }, + Author = new RealmUser { Username = "Creator name here" }, Title = "Long title used to check background colour", }, BeatmapSet = new BeatmapSetInfo() diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs index d20fbd3539..2140acb2e3 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs @@ -53,7 +53,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("import beatmap", () => { beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); - importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First(); + importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable().First(); }); AddStep("load multiplayer", () => LoadScreen(multiplayerComponents = new TestMultiplayerComponents())); diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 62b0f10fd8..a5f42545c1 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -20,7 +20,6 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Scoring; using osu.Game.Screens.Select.Leaderboards; -using osu.Game.Stores; using osu.Game.Tests.Resources; using osu.Game.Users; using osuTK; @@ -45,7 +44,7 @@ namespace osu.Game.Tests.Visual.SongSelect dependencies.Cache(rulesetStore = new RulesetStore(ContextFactory)); dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesetStore, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default)); - dependencies.Cache(scoreManager = new ScoreManager(dependencies.Get(), () => beatmapManager, LocalStorage, dependencies.Get(), Scheduler)); + dependencies.Cache(scoreManager = new ScoreManager(dependencies.Get(), () => beatmapManager, LocalStorage, dependencies.Get(), Scheduler)); return dependencies; } diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs index e573c96ce9..fb6d9a0b4b 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs @@ -77,7 +77,7 @@ namespace osu.Game.Tests.Visual.SongSelect { showMetadataForBeatmap(() => { - var allBeatmapSets = manager.GetAllUsableBeatmapSets(IncludedDetails.Minimal); + var allBeatmapSets = manager.GetAllUsableBeatmapSets(); if (allBeatmapSets.Count == 0) return manager.DefaultBeatmap; diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index 5c4759a466..49200f6e1a 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -5,10 +5,10 @@ using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; -using osu.Framework.Graphics; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Extensions; +using osu.Framework.Graphics; using osu.Framework.Graphics.Cursor; using osu.Framework.Platform; using osu.Framework.Testing; @@ -24,7 +24,6 @@ using osu.Game.Overlays; using osu.Game.Rulesets; using osu.Game.Scoring; using osu.Game.Screens.Select.Leaderboards; -using osu.Game.Stores; using osu.Game.Tests.Resources; using osuTK; using osuTK.Input; @@ -89,7 +88,7 @@ namespace osu.Game.Tests.Visual.UserInterface dependencies.Cache(rulesetStore = new RulesetStore(ContextFactory)); dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesetStore, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default)); - dependencies.Cache(scoreManager = new ScoreManager(dependencies.Get(), () => beatmapManager, LocalStorage, dependencies.Get(), Scheduler)); + dependencies.Cache(scoreManager = new ScoreManager(dependencies.Get(), () => beatmapManager, LocalStorage, dependencies.Get(), Scheduler)); beatmapInfo = beatmapManager.Import(new ImportTask(TestResources.GetQuickTestBeatmapForImport())).GetResultSafely().Value.Beatmaps[0]; diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 5c8cf38682..45ae69e4ba 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -16,7 +16,6 @@ using osu.Framework.IO.Stores; using osu.Framework.Platform; using osu.Framework.Testing; using osu.Game.Database; -using osu.Game.IO; using osu.Game.IO.Archives; using osu.Game.Models; using osu.Game.Online.API; @@ -43,21 +42,18 @@ namespace osu.Game.Beatmaps public BeatmapManager(Storage storage, RealmContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore gameResources, GameHost host = null, WorkingBeatmap defaultBeatmap = null, bool performOnlineLookups = false) { + if (performOnlineLookups) + onlineBeatmapLookupQueue = new BeatmapOnlineLookupQueue(api, storage); + var userResources = new RealmFileStore(contextFactory, storage).Store; BeatmapTrackStore = audioManager.GetTrackStore(userResources); - beatmapModelManager = CreateBeatmapModelManager(storage, contextFactory, rulesets, api, host); + beatmapModelManager = CreateBeatmapModelManager(storage, contextFactory, rulesets, onlineBeatmapLookupQueue); workingBeatmapCache = CreateWorkingBeatmapCache(audioManager, gameResources, userResources, defaultBeatmap, host); workingBeatmapCache.BeatmapManager = beatmapModelManager; beatmapModelManager.WorkingBeatmapCache = workingBeatmapCache; - - if (performOnlineLookups) - { - onlineBeatmapLookupQueue = new BeatmapOnlineLookupQueue(api, storage); - beatmapModelManager.OnlineLookupQueue = onlineBeatmapLookupQueue; - } } protected virtual WorkingBeatmapCache CreateWorkingBeatmapCache(AudioManager audioManager, IResourceStore resources, IResourceStore storage, WorkingBeatmap defaultBeatmap, GameHost host) @@ -65,8 +61,8 @@ namespace osu.Game.Beatmaps return new WorkingBeatmapCache(BeatmapTrackStore, audioManager, resources, storage, defaultBeatmap, host); } - protected virtual BeatmapModelManager CreateBeatmapModelManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, GameHost host) => - new BeatmapModelManager(storage, contextFactory, rulesets, host); + protected virtual BeatmapModelManager CreateBeatmapModelManager(Storage storage, RealmContextFactory contextFactory, RulesetStore rulesets, [CanBeNull] BeatmapOnlineLookupQueue onlineLookupQueue) => + new BeatmapModelManager(contextFactory, storage, onlineLookupQueue); /// /// Create a new . @@ -97,9 +93,9 @@ namespace osu.Game.Beatmaps } }; - var imported = beatmapModelManager.Import(set).GetResultSafely().Value; + var imported = beatmapModelManager.Import(set).GetResultSafely()?.Value; - return GetWorkingBeatmap(imported.Beatmaps.First()); + return GetWorkingBeatmap(imported?.Beatmaps.First()); } #region Delegation to BeatmapModelManager (methods which previously existed locally). @@ -135,23 +131,21 @@ namespace osu.Game.Beatmaps /// Returns a list of all usable s. /// /// A list of available . - public List GetAllUsableBeatmapSets(IncludedDetails includes = IncludedDetails.All, bool includeProtected = false) => beatmapModelManager.GetAllUsableBeatmapSets(includes, includeProtected); + public List GetAllUsableBeatmapSets(bool includeProtected = false) => beatmapModelManager.GetAllUsableBeatmapSets(includeProtected); /// /// Returns a list of all usable s. Note that files are not populated. /// - /// The level of detail to include in the returned objects. /// Whether to include protected (system) beatmaps. These should not be included for gameplay playable use cases. /// A list of available . - public IEnumerable GetAllUsableBeatmapSetsEnumerable(IncludedDetails includes, bool includeProtected = false) => beatmapModelManager.GetAllUsableBeatmapSetsEnumerable(includes, includeProtected); + public IEnumerable GetAllUsableBeatmapSetsEnumerable(bool includeProtected = false) => beatmapModelManager.GetAllUsableBeatmapSetsEnumerable(includeProtected); /// /// Perform a lookup query on available s. /// /// The query. - /// The level of detail to include in the returned objects. /// Results from the provided query. - public IEnumerable QueryBeatmapSets(Expression> query, IncludedDetails includes = IncludedDetails.All) => beatmapModelManager.QueryBeatmapSets(query, includes); + public IEnumerable QueryBeatmapSets(Expression> query) => beatmapModelManager.QueryBeatmapSets(query); /// /// Perform a lookup query on available s. diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs index f6f2e16410..100189bf09 100644 --- a/osu.Game/Beatmaps/BeatmapModelManager.cs +++ b/osu.Game/Beatmaps/BeatmapModelManager.cs @@ -3,30 +3,21 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Linq.Expressions; using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.EntityFrameworkCore; -using osu.Framework.Audio.Track; using osu.Framework.Extensions; -using osu.Framework.Graphics.Textures; -using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Testing; using osu.Game.Beatmaps.Formats; using osu.Game.Database; using osu.Game.Extensions; -using osu.Game.IO; -using osu.Game.IO.Archives; -using osu.Game.Rulesets; -using osu.Game.Rulesets.Objects; using osu.Game.Skinning; using osu.Game.Stores; -using Realms; -using Decoder = osu.Game.Beatmaps.Formats.Decoder; + +#nullable enable namespace osu.Game.Beatmaps { @@ -34,145 +25,65 @@ namespace osu.Game.Beatmaps /// Handles ef-core storage of beatmaps. /// [ExcludeFromDynamicCompile] - public class BeatmapModelManager : RealmArchiveModelManager + public class BeatmapModelManager : BeatmapImporter { /// /// Fired when a single difficulty has been hidden. /// - public event Action BeatmapHidden; + public event Action? BeatmapHidden; /// /// Fired when a single difficulty has been restored. /// - public event Action BeatmapRestored; - - /// - /// An online lookup queue component which handles populating online beatmap metadata. - /// - public BeatmapOnlineLookupQueue OnlineLookupQueue { private get; set; } + public event Action? BeatmapRestored; /// /// The game working beatmap cache, used to invalidate entries on changes. /// - public IWorkingBeatmapCache WorkingBeatmapCache { private get; set; } + public IWorkingBeatmapCache? WorkingBeatmapCache { private get; set; } public override IEnumerable HandledExtensions => new[] { ".osz" }; protected override string[] HashableFileTypes => new[] { ".osu" }; - private readonly RulesetStore rulesets; - - public BeatmapModelManager(Storage storage, RealmContextFactory contextFactory, RulesetStore rulesets, GameHost host = null) - : base(storage, contextFactory) + public BeatmapModelManager(RealmContextFactory contextFactory, Storage storage, BeatmapOnlineLookupQueue? onlineLookupQueue = null) + : base(contextFactory, storage, onlineLookupQueue) { - this.rulesets = rulesets; - - // beatmaps.BeatmapHidden += b => BeatmapHidden?.Invoke(b); - // beatmaps.BeatmapRestored += b => BeatmapRestored?.Invoke(b); - // beatmaps.ItemRemoved += b => WorkingBeatmapCache?.Invalidate(b); - // beatmaps.ItemUpdated += obj => WorkingBeatmapCache?.Invalidate(obj); } protected override bool ShouldDeleteArchive(string path) => Path.GetExtension(path)?.ToLowerInvariant() == ".osz"; - protected override async Task Populate(BeatmapSetInfo beatmapSet, ArchiveReader? archive, Realm realm, CancellationToken cancellationToken) - { - if (archive != null) - beatmapSet.Beatmaps.AddRange(createBeatmapDifficulties(beatmapSet.Files)); - - foreach (BeatmapInfo b in beatmapSet.Beatmaps) - { - // remove metadata from difficulties where it matches the set - if (beatmapSet.Metadata.Equals(b.Metadata)) - b.Metadata = null; - - b.BeatmapSet = beatmapSet; - } - - validateOnlineIds(beatmapSet); - - bool hadOnlineIDs = beatmapSet.Beatmaps.Any(b => b.OnlineID > 0); - - if (OnlineLookupQueue != null) - await OnlineLookupQueue.UpdateAsync(beatmapSet, cancellationToken).ConfigureAwait(false); - - // ensure at least one beatmap was able to retrieve or keep an online ID, else drop the set ID. - if (hadOnlineIDs && !beatmapSet.Beatmaps.Any(b => b.OnlineID > 0)) - { - if (beatmapSet.OnlineID != null) - { - beatmapSet.OnlineID = -1; - LogForModel(beatmapSet, "Disassociating beatmap set ID due to loss of all beatmap IDs"); - } - } - } - - protected override void PreImport(BeatmapSetInfo beatmapSet) - { - if (beatmapSet.Beatmaps.Any(b => b.BaseDifficulty == null)) - throw new InvalidOperationException($"Cannot import {nameof(BeatmapInfo)} with null {nameof(BeatmapInfo.BaseDifficulty)}."); - - // check if a set already exists with the same online id, delete if it does. - if (beatmapSet.OnlineID != null) - { - var existingSetWithSameOnlineID = beatmaps.ConsumableItems.FirstOrDefault(b => b.OnlineID == beatmapSet.OnlineID); - - if (existingSetWithSameOnlineID != null) - { - Delete(existingSetWithSameOnlineID); - - // in order to avoid a unique key constraint, immediately remove the online ID from the previous set. - existingSetWithSameOnlineID.OnlineID = -1; - foreach (var b in existingSetWithSameOnlineID.Beatmaps) - b.OnlineID = -1; - - LogForModel(beatmapSet, $"Found existing beatmap set with same OnlineBeatmapSetID ({beatmapSet.OnlineID}). It has been deleted."); - } - } - } - - private void validateOnlineIds(BeatmapSetInfo beatmapSet) - { - var beatmapIds = beatmapSet.Beatmaps.Where(b => b.OnlineID.HasValue).Select(b => b.OnlineID).ToList(); - - // ensure all IDs are unique - if (beatmapIds.GroupBy(b => b).Any(g => g.Count() > 1)) - { - LogForModel(beatmapSet, "Found non-unique IDs, resetting..."); - resetIds(); - return; - } - - // find any existing beatmaps in the database that have matching online ids - var existingBeatmaps = QueryBeatmaps(b => beatmapIds.Contains(b.OnlineID)).ToList(); - - if (existingBeatmaps.Count > 0) - { - // reset the import ids (to force a re-fetch) *unless* they match the candidate CheckForExisting set. - // we can ignore the case where the new ids are contained by the CheckForExisting set as it will either be used (import skipped) or deleted. - var existing = CheckForExisting(beatmapSet); - - if (existing == null || existingBeatmaps.Any(b => !existing.Beatmaps.Contains(b))) - { - LogForModel(beatmapSet, "Found existing import with IDs already, resetting..."); - resetIds(); - } - } - - void resetIds() => beatmapSet.Beatmaps.ForEach(b => b.OnlineID = -1); - } - /// /// Delete a beatmap difficulty. /// /// The beatmap difficulty to hide. - public void Hide(BeatmapInfo beatmapInfo) => beatmaps.Hide(beatmapInfo); + public void Hide(BeatmapInfo beatmapInfo) + { + using (var realm = ContextFactory.CreateContext()) + using (var transaction = realm.BeginWrite()) + { + beatmapInfo.Hidden = true; + transaction.Commit(); + + BeatmapHidden?.Invoke(beatmapInfo); + } + } /// /// Restore a beatmap difficulty. /// /// The beatmap difficulty to restore. - public void Restore(BeatmapInfo beatmapInfo) => beatmaps.Restore(beatmapInfo); + public void Restore(BeatmapInfo beatmapInfo) + { + using (var realm = ContextFactory.CreateContext()) + using (var transaction = realm.BeginWrite()) + { + beatmapInfo.Hidden = false; + transaction.Commit(); + + BeatmapRestored?.Invoke(beatmapInfo); + } + } /// /// Saves an file against a given . @@ -180,10 +91,12 @@ namespace osu.Game.Beatmaps /// The to save the content against. The file referenced by will be replaced. /// The content to write. /// The beatmap content to write, null if to be omitted. - public virtual void Save(BeatmapInfo beatmapInfo, IBeatmap beatmapContent, ISkin beatmapSkin = null) + public virtual void Save(BeatmapInfo beatmapInfo, IBeatmap beatmapContent, ISkin? beatmapSkin = null) { var setInfo = beatmapInfo.BeatmapSet; + Debug.Assert(setInfo != null); + // Difficulty settings must be copied first due to the clone in `Beatmap<>.BeatmapInfo_Set`. // This should hopefully be temporary, assuming said clone is eventually removed. @@ -202,25 +115,29 @@ namespace osu.Game.Beatmaps stream.Seek(0, SeekOrigin.Begin); - using (ContextFactory.GetForWrite()) + using (var realm = ContextFactory.CreateContext()) + using (var transaction = realm.BeginWrite()) { beatmapInfo = setInfo.Beatmaps.Single(b => b.Equals(beatmapInfo)); - var metadata = beatmapInfo.Metadata ?? setInfo.Metadata; - // grab the original file (or create a new one if not found). - var fileInfo = setInfo.Files.SingleOrDefault(f => string.Equals(f.Filename, beatmapInfo.Path, StringComparison.OrdinalIgnoreCase)) ?? new BeatmapSetFileInfo(); + var existingFileInfo = setInfo.Files.SingleOrDefault(f => string.Equals(f.Filename, beatmapInfo.Path, StringComparison.OrdinalIgnoreCase)); + + if (existingFileInfo != null) + { + DeleteFile(setInfo, existingFileInfo); + } // metadata may have changed; update the path with the standard format. - beatmapInfo.Path = $"{metadata.Artist} - {metadata.Title} ({metadata.Author}) [{beatmapInfo.DifficultyName}].osu".GetValidArchiveContentFilename(); + var metadata = beatmapInfo.Metadata; + string filename = $"{metadata.Artist} - {metadata.Title} ({metadata.Author}) [{beatmapInfo.DifficultyName}].osu".GetValidArchiveContentFilename(); beatmapInfo.MD5Hash = stream.ComputeMD5Hash(); - // update existing or populate new file's filename. - fileInfo.Filename = beatmapInfo.Path; - stream.Seek(0, SeekOrigin.Begin); - ReplaceFile(setInfo, fileInfo, stream); + AddFile(setInfo, stream, filename, realm); + + transaction.Commit(); } } @@ -232,100 +149,43 @@ namespace osu.Game.Beatmaps /// /// The query. /// The first result for the provided query, or null if no results were found. - public BeatmapSetInfo QueryBeatmapSet(Expression> query) => beatmaps.ConsumableItems.AsNoTracking().FirstOrDefault(query); - - protected override bool CanSkipImport(BeatmapSetInfo existing, BeatmapSetInfo import) + public BeatmapSetInfo? QueryBeatmapSet(Expression> query) { - if (!base.CanSkipImport(existing, import)) - return false; - - return existing.Beatmaps.Any(b => b.OnlineID != null); - } - - protected override bool CanReuseExisting(BeatmapSetInfo existing, BeatmapSetInfo import) - { - if (!base.CanReuseExisting(existing, import)) - return false; - - var existingIds = existing.Beatmaps.Select(b => b.OnlineID).OrderBy(i => i); - var importIds = import.Beatmaps.Select(b => b.OnlineID).OrderBy(i => i); - - // force re-import if we are not in a sane state. - return existing.OnlineID == import.OnlineID && existingIds.SequenceEqual(importIds); + using (var context = ContextFactory.CreateContext()) + return context.All().FirstOrDefault(query); // TODO: ?.ToLive(); } /// /// Returns a list of all usable s. /// /// A list of available . - public List GetAllUsableBeatmapSets(IncludedDetails includes = IncludedDetails.All, bool includeProtected = false) => - GetAllUsableBeatmapSetsEnumerable(includes, includeProtected).ToList(); + public List GetAllUsableBeatmapSets(bool includeProtected = false) => + GetAllUsableBeatmapSetsEnumerable(includeProtected).ToList(); /// /// Returns a list of all usable s. Note that files are not populated. /// - /// The level of detail to include in the returned objects. /// Whether to include protected (system) beatmaps. These should not be included for gameplay playable use cases. /// A list of available . - public IEnumerable GetAllUsableBeatmapSetsEnumerable(IncludedDetails includes, bool includeProtected = false) + public IEnumerable GetAllUsableBeatmapSetsEnumerable(bool includeProtected = false) { - IQueryable queryable; - - switch (includes) - { - case IncludedDetails.Minimal: - queryable = beatmaps.BeatmapSetsOverview; - break; - - case IncludedDetails.AllButRuleset: - queryable = beatmaps.BeatmapSetsWithoutRuleset; - break; - - case IncludedDetails.AllButFiles: - queryable = beatmaps.BeatmapSetsWithoutFiles; - break; - - default: - queryable = beatmaps.ConsumableItems; - break; - } - - // AsEnumerable used here to avoid applying the WHERE in sql. When done so, ef core 2.x uses an incorrect ORDER BY - // clause which causes queries to take 5-10x longer. - // TODO: remove if upgrading to EF core 3.x. - return queryable.AsEnumerable().Where(s => !s.DeletePending && (includeProtected || !s.Protected)); + using (var context = ContextFactory.CreateContext()) + return context.All().Where(b => !b.DeletePending && (includeProtected || !b.Protected)); } /// /// Perform a lookup query on available s. /// /// The query. - /// The level of detail to include in the returned objects. /// Results from the provided query. - public IEnumerable QueryBeatmapSets(Expression> query, IncludedDetails includes = IncludedDetails.All) + public IEnumerable QueryBeatmapSets(Expression> query) { - IQueryable queryable; - - switch (includes) + using (var context = ContextFactory.CreateContext()) { - case IncludedDetails.Minimal: - queryable = beatmaps.BeatmapSetsOverview; - break; - - case IncludedDetails.AllButRuleset: - queryable = beatmaps.BeatmapSetsWithoutRuleset; - break; - - case IncludedDetails.AllButFiles: - queryable = beatmaps.BeatmapSetsWithoutFiles; - break; - - default: - queryable = beatmaps.ConsumableItems; - break; + return context.All() + .Where(b => !b.DeletePending) + .Where(query); } - - return queryable.AsNoTracking().Where(query); } /// @@ -333,145 +193,24 @@ namespace osu.Game.Beatmaps /// /// The query. /// The first result for the provided query, or null if no results were found. - public BeatmapInfo QueryBeatmap(Expression> query) => beatmaps.Beatmaps.AsNoTracking().FirstOrDefault(query); + public BeatmapInfo? QueryBeatmap(Expression> query) + { + using (var context = ContextFactory.CreateContext()) + return context.All().FirstOrDefault(query); // TODO: ?.ToLive(); + } /// /// Perform a lookup query on available s. /// /// The query. /// Results from the provided query. - public IQueryable QueryBeatmaps(Expression> query) => beatmaps.Beatmaps.AsNoTracking().Where(query); - - public override string HumanisedModelName => "beatmap"; - - protected override bool CheckLocalAvailability(BeatmapSetInfo model, IQueryable items) - => base.CheckLocalAvailability(model, items) - || (model.OnlineID != null && items.Any(b => b.OnlineID == model.OnlineID)); - - protected override BeatmapSetInfo CreateModel(ArchiveReader reader) + public IQueryable QueryBeatmaps(Expression> query) { - // let's make sure there are actually .osu files to import. - string mapName = reader.Filenames.FirstOrDefault(f => f.EndsWith(".osu", StringComparison.OrdinalIgnoreCase)); - - if (string.IsNullOrEmpty(mapName)) + using (var context = ContextFactory.CreateContext()) { - Logger.Log($"No beatmap files found in the beatmap archive ({reader.Name}).", LoggingTarget.Database); - return null; + return context.All() + .Where(query); } - - Beatmap beatmap; - using (var stream = new LineBufferedReader(reader.GetStream(mapName))) - beatmap = Decoder.GetDecoder(stream).Decode(stream); - - return new BeatmapSetInfo - { - OnlineID = beatmap.BeatmapInfo.BeatmapSet?.OnlineID, - Metadata = beatmap.Metadata, - DateAdded = DateTimeOffset.UtcNow - }; } - - /// - /// Create all required s for the provided archive. - /// - private List createBeatmapDifficulties(List files) - { - var beatmapInfos = new List(); - - foreach (var file in files.Where(f => f.Filename.EndsWith(".osu", StringComparison.OrdinalIgnoreCase))) - { - using (var raw = Files.Store.GetStream(file.FileInfo.GetStoragePath())) - using (var ms = new MemoryStream()) // we need a memory stream so we can seek - using (var sr = new LineBufferedReader(ms)) - { - raw.CopyTo(ms); - ms.Position = 0; - - var decoder = Decoder.GetDecoder(sr); - IBeatmap beatmap = decoder.Decode(sr); - - string hash = ms.ComputeSHA2Hash(); - - if (beatmapInfos.Any(b => b.Hash == hash)) - continue; - - beatmap.BeatmapInfo.Path = file.Filename; - beatmap.BeatmapInfo.Hash = hash; - beatmap.BeatmapInfo.MD5Hash = ms.ComputeMD5Hash(); - - var ruleset = rulesets.GetRuleset(beatmap.BeatmapInfo.RulesetID); - beatmap.BeatmapInfo.Ruleset = ruleset; - - // TODO: this should be done in a better place once we actually need to dynamically update it. - beatmap.BeatmapInfo.StarRating = ruleset?.CreateInstance().CreateDifficultyCalculator(new DummyConversionBeatmap(beatmap)).Calculate().StarRating ?? 0; - beatmap.BeatmapInfo.Length = calculateLength(beatmap); - beatmap.BeatmapInfo.BPM = 60000 / beatmap.GetMostCommonBeatLength(); - - beatmapInfos.Add(beatmap.BeatmapInfo); - } - } - - return beatmapInfos; - } - - private double calculateLength(IBeatmap b) - { - if (!b.HitObjects.Any()) - return 0; - - var lastObject = b.HitObjects.Last(); - - //TODO: this isn't always correct (consider mania where a non-last object may last for longer than the last in the list). - double endTime = lastObject.GetEndTime(); - double startTime = b.HitObjects.First().StartTime; - - return endTime - startTime; - } - - /// - /// A dummy WorkingBeatmap for the purpose of retrieving a beatmap for star difficulty calculation. - /// - private class DummyConversionBeatmap : WorkingBeatmap - { - private readonly IBeatmap beatmap; - - public DummyConversionBeatmap(IBeatmap beatmap) - : base(beatmap.BeatmapInfo, null) - { - this.beatmap = beatmap; - } - - protected override IBeatmap GetBeatmap() => beatmap; - protected override Texture GetBackground() => null; - protected override Track GetBeatmapTrack() => null; - protected internal override ISkin GetSkin() => null; - public override Stream GetStream(string storagePath) => null; - } - } - - /// - /// The level of detail to include in database results. - /// - public enum IncludedDetails - { - /// - /// Only include beatmap difficulties and set level metadata. - /// - Minimal, - - /// - /// Include all difficulties, rulesets, difficulty metadata but no files. - /// - AllButFiles, - - /// - /// Include everything except ruleset. Used for cases where we aren't sure the ruleset is present but still want to consume the beatmap. - /// - AllButRuleset, - - /// - /// Include everything. - /// - All } } diff --git a/osu.Game/Beatmaps/EFBeatmapSetInfo.cs b/osu.Game/Beatmaps/EFBeatmapSetInfo.cs index c0310f5a76..10910d9817 100644 --- a/osu.Game/Beatmaps/EFBeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/EFBeatmapSetInfo.cs @@ -34,7 +34,7 @@ namespace osu.Game.Beatmaps public EFBeatmapMetadata Metadata { get; set; } [NotNull] - public List Beatmaps { get; } = new List(); + public List Beatmaps { get; } = new List(); public BeatmapOnlineStatus Status { get; set; } = BeatmapOnlineStatus.None; @@ -95,7 +95,7 @@ namespace osu.Game.Beatmaps #region Implementation of IBeatmapSetInfo - IBeatmapMetadataInfo IBeatmapSetInfo.Metadata => Metadata ?? Beatmaps.FirstOrDefault()?.Metadata ?? new BeatmapMetadata(); + IBeatmapMetadataInfo IBeatmapSetInfo.Metadata => Metadata ?? Beatmaps.FirstOrDefault()?.Metadata ?? new EFBeatmapMetadata(); IEnumerable IBeatmapSetInfo.Beatmaps => Beatmaps; IEnumerable IHasNamedFiles.Files => Files; diff --git a/osu.Game/Beatmaps/FlatFileWorkingBeatmap.cs b/osu.Game/Beatmaps/FlatFileWorkingBeatmap.cs index 8c915e2872..dc8201a402 100644 --- a/osu.Game/Beatmaps/FlatFileWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/FlatFileWorkingBeatmap.cs @@ -33,7 +33,7 @@ namespace osu.Game.Beatmaps beatmap.BeatmapInfo.Ruleset = rulesetProvider(beatmap.BeatmapInfo.RulesetID).RulesetInfo; if (beatmapId.HasValue) - beatmap.BeatmapInfo.OnlineID = beatmapId; + beatmap.BeatmapInfo.OnlineID = beatmapId.Value; } private static Beatmap readFromFile(string filename) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index e5db9d045a..5c61d302c2 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -11,6 +11,7 @@ using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Legacy; using osu.Game.Beatmaps.Timing; using osu.Game.IO; +using osu.Game.Rulesets; using osu.Game.Rulesets.Objects.Legacy; namespace osu.Game.Beatmaps.Formats @@ -141,7 +142,8 @@ namespace osu.Game.Beatmaps.Formats break; case @"Mode": - beatmap.BeatmapInfo.RulesetID = Parsing.ParseInt(pair.Value); + // TODO: ha ha ha. + beatmap.BeatmapInfo.Ruleset = new RulesetInfo(Parsing.ParseInt(pair.Value), "some ruleset", "wangs", true /* probably not */); switch (beatmap.BeatmapInfo.RulesetID) { diff --git a/osu.Game/Database/ImportTask.cs b/osu.Game/Database/ImportTask.cs index 1fb5a42630..cd9e396d13 100644 --- a/osu.Game/Database/ImportTask.cs +++ b/osu.Game/Database/ImportTask.cs @@ -5,13 +5,14 @@ using System.IO; using osu.Game.IO.Archives; +using osu.Game.Stores; using osu.Game.Utils; using SharpCompress.Common; namespace osu.Game.Database { /// - /// An encapsulated import task to be imported to an . + /// An encapsulated import task to be imported to an . /// public class ImportTask { diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 3346c6d97d..310f4946f3 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -71,7 +71,7 @@ namespace osu.Game.Overlays beatmaps.ItemUpdated += beatmapUpdated; beatmaps.ItemRemoved += beatmapRemoved; - beatmapSets.AddRange(beatmaps.GetAllUsableBeatmapSets(IncludedDetails.Minimal, true).OrderBy(_ => RNG.Next())); + beatmapSets.AddRange(beatmaps.GetAllUsableBeatmapSets(true).OrderBy(_ => RNG.Next())); // Todo: These binds really shouldn't be here, but are unlikely to cause any issues for now. // They are placed here for now since some tests rely on setting the beatmap _and_ their hierarchies inside their load(), which runs before the MusicController's load(). diff --git a/osu.Game/Scoring/LegacyDatabasedScore.cs b/osu.Game/Scoring/LegacyDatabasedScore.cs index 6a8e5f8930..ac444c1bf3 100644 --- a/osu.Game/Scoring/LegacyDatabasedScore.cs +++ b/osu.Game/Scoring/LegacyDatabasedScore.cs @@ -6,14 +6,14 @@ using System.Linq; using osu.Framework.IO.Stores; using osu.Game.Beatmaps; using osu.Game.Extensions; +using osu.Game.Rulesets; using osu.Game.Scoring.Legacy; -using osu.Game.Stores; namespace osu.Game.Scoring { public class LegacyDatabasedScore : Score { - public LegacyDatabasedScore(ScoreInfo score, RealmRulesetStore rulesets, BeatmapManager beatmaps, IResourceStore store) + public LegacyDatabasedScore(ScoreInfo score, RulesetStore rulesets, BeatmapManager beatmaps, IResourceStore store) { ScoreInfo = score; diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 21339acd22..20b6ba7a93 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -80,17 +80,9 @@ namespace osu.Game.Scoring public double? PP { get; set; } - public RealmBeatmap Beatmap { get; set; } = null!; + public BeatmapInfo Beatmap { get; set; } = null!; - public BeatmapInfo BeatmapInfo - { - get => new BeatmapInfo(); - // .. todo - // ReSharper disable once ValueParameterNotUsed - set => Beatmap = new RealmBeatmap(new RealmRuleset("osu", "osu!", "wangs", 0), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata()); - } - - public RealmRuleset Ruleset { get; set; } = null!; + public RulesetInfo Ruleset { get; set; } = null!; [Ignored] public Dictionary Statistics @@ -132,7 +124,13 @@ namespace osu.Game.Scoring private Mod[]? mods; - public int BeatmapInfoID => BeatmapInfo.ID; + public Guid BeatmapInfoID => Beatmap.ID; + + public BeatmapInfo BeatmapInfo + { + get => Beatmap; + set => Beatmap = value; + } public int UserID => RealmUser.OnlineID; diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index d094e1f45c..0a1b616422 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -20,7 +20,6 @@ using osu.Game.Overlays.Notifications; using osu.Game.Rulesets; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; -using osu.Game.Stores; namespace osu.Game.Scoring { diff --git a/osu.Game/Scoring/ScoreModelManager.cs b/osu.Game/Scoring/ScoreModelManager.cs index dd0ac79873..81f80df3ad 100644 --- a/osu.Game/Scoring/ScoreModelManager.cs +++ b/osu.Game/Scoring/ScoreModelManager.cs @@ -11,6 +11,7 @@ using osu.Framework.Platform; 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.Stores; using Realms; @@ -25,10 +26,10 @@ namespace osu.Game.Scoring protected override string[] HashableFileTypes => new[] { ".osr" }; - private readonly RealmRulesetStore rulesets; + private readonly RulesetStore rulesets; private readonly Func beatmaps; - public ScoreModelManager(RealmRulesetStore rulesets, Func beatmaps, Storage storage, RealmContextFactory contextFactory) + public ScoreModelManager(RulesetStore rulesets, Func beatmaps, Storage storage, RealmContextFactory contextFactory) : base(storage, contextFactory) { this.rulesets = rulesets; diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index 948e3a7d88..94c48f5251 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -95,7 +95,7 @@ namespace osu.Game.Screens.Menu // if the user has requested not to play theme music, we should attempt to find a random beatmap from their collection. if (!MenuMusic.Value) { - var sets = beatmaps.GetAllUsableBeatmapSets(IncludedDetails.Minimal); + var sets = beatmaps.GetAllUsableBeatmapSets(); if (sets.Count > 0) { @@ -125,7 +125,7 @@ namespace osu.Game.Screens.Menu bool loadThemedIntro() { - setInfo = beatmaps.QueryBeatmapSets(b => b.Hash == BeatmapHash, IncludedDetails.AllButRuleset).FirstOrDefault(); + setInfo = beatmaps.QueryBeatmapSets(b => b.Hash == BeatmapHash).FirstOrDefault(); if (setInfo == null) return false; diff --git a/osu.Game/Screens/Ranking/SoloResultsScreen.cs b/osu.Game/Screens/Ranking/SoloResultsScreen.cs index afebc728b4..2ec6c38287 100644 --- a/osu.Game/Screens/Ranking/SoloResultsScreen.cs +++ b/osu.Game/Screens/Ranking/SoloResultsScreen.cs @@ -27,7 +27,7 @@ namespace osu.Game.Screens.Ranking protected override APIRequest FetchScores(Action> scoresCallback) { - if (Score.BeatmapInfo.OnlineID == null || Score.BeatmapInfo.Status <= BeatmapOnlineStatus.Pending) + if (Score.BeatmapInfo.OnlineID <= 0 || Score.BeatmapInfo.Status <= BeatmapOnlineStatus.Pending) return null; getScoreRequest = new GetScoresRequest(Score.BeatmapInfo, Score.Ruleset); diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 7e292e2de7..f8d2c3cb9b 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -182,7 +182,7 @@ namespace osu.Game.Screens.Select loadBeatmapSets(GetLoadableBeatmaps()); } - protected virtual IEnumerable GetLoadableBeatmaps() => beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.AllButFiles); + protected virtual IEnumerable GetLoadableBeatmaps() => beatmaps.GetAllUsableBeatmapSetsEnumerable(); public void RemoveBeatmapSet(BeatmapSetInfo beatmapSet) => Schedule(() => { diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 619b1e0fd0..618c5cf5ec 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -61,7 +61,11 @@ namespace osu.Game.Screens.Select.Carousel [BackgroundDependencyLoader(true)] private void load(BeatmapSetOverlay beatmapOverlay) { - restoreHiddenRequested = s => s.Beatmaps.ForEach(manager.Restore); + restoreHiddenRequested = s => + { + foreach (var b in s.Beatmaps) + manager.Restore(b); + }; if (beatmapOverlay != null) viewDetails = beatmapOverlay.FetchAndShowBeatmapSet; @@ -214,8 +218,8 @@ namespace osu.Game.Screens.Select.Carousel if (Item.State.Value == CarouselItemState.NotSelected) items.Add(new OsuMenuItem("Expand", MenuItemType.Highlighted, () => Item.State.Value = CarouselItemState.Selected)); - if (beatmapSet.OnlineID != null && viewDetails != null) - items.Add(new OsuMenuItem("Details...", MenuItemType.Standard, () => viewDetails(beatmapSet.OnlineID.Value))); + if (beatmapSet.OnlineID > 0 && viewDetails != null) + items.Add(new OsuMenuItem("Details...", MenuItemType.Standard, () => viewDetails(beatmapSet.OnlineID))); if (collectionManager != null) { diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index a40e8e2bde..1aab84183a 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -295,7 +295,7 @@ namespace osu.Game.Screens.Select Schedule(() => { // if we have no beatmaps, let's prompt the user to import from over a stable install if he has one. - if (!beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.Minimal).Any() && DisplayStableImportPrompt) + if (!beatmaps.GetAllUsableBeatmapSetsEnumerable().Any() && DisplayStableImportPrompt) { dialogOverlay.Push(new ImportFromStablePopup(() => { diff --git a/osu.Game/Stores/BeatmapImporter.cs b/osu.Game/Stores/BeatmapImporter.cs index de92da394d..2c73bd22eb 100644 --- a/osu.Game/Stores/BeatmapImporter.cs +++ b/osu.Game/Stores/BeatmapImporter.cs @@ -34,7 +34,7 @@ namespace osu.Game.Stores /// Handles the storage and retrieval of Beatmaps/WorkingBeatmaps. /// [ExcludeFromDynamicCompile] - public class BeatmapImporter : RealmArchiveModelImporter, IDisposable + public abstract class BeatmapImporter : RealmArchiveModelManager, IDisposable { public override IEnumerable HandledExtensions => new[] { ".osz" }; @@ -45,7 +45,7 @@ namespace osu.Game.Stores private readonly BeatmapOnlineLookupQueue? onlineLookupQueue; - public BeatmapImporter(RealmContextFactory contextFactory, Storage storage, BeatmapOnlineLookupQueue? onlineLookupQueue = null) + protected BeatmapImporter(RealmContextFactory contextFactory, Storage storage, BeatmapOnlineLookupQueue? onlineLookupQueue = null) : base(storage, contextFactory) { this.onlineLookupQueue = onlineLookupQueue; diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs index 8a31e4576a..d21813d97f 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs @@ -10,8 +10,8 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Textures; using osu.Framework.Platform; -using osu.Game.IO; using osu.Game.Screens.Play; +using osu.Game.Stores; namespace osu.Game.Storyboards.Drawables { @@ -76,7 +76,7 @@ namespace osu.Game.Storyboards.Drawables } [BackgroundDependencyLoader(true)] - private void load(FileStore fileStore, GameplayClock clock, CancellationToken? cancellationToken, GameHost host) + private void load(RealmFileStore fileStore, GameplayClock clock, CancellationToken? cancellationToken, GameHost host) { if (clock != null) Clock = clock; diff --git a/osu.Game/Tests/Beatmaps/TestBeatmap.cs b/osu.Game/Tests/Beatmaps/TestBeatmap.cs index 99944bcf6d..3b4547cb49 100644 --- a/osu.Game/Tests/Beatmaps/TestBeatmap.cs +++ b/osu.Game/Tests/Beatmaps/TestBeatmap.cs @@ -32,14 +32,12 @@ namespace osu.Game.Tests.Beatmaps HitObjects = baseBeatmap.HitObjects; BeatmapInfo.Ruleset = ruleset; - BeatmapInfo.RulesetID = ruleset.ID ?? 0; BeatmapInfo.Length = 75000; BeatmapInfo.OnlineInfo = new APIBeatmap(); BeatmapInfo.OnlineID = Interlocked.Increment(ref onlineBeatmapID); Debug.Assert(BeatmapInfo.BeatmapSet != null); - BeatmapInfo.BeatmapSet.Metadata = BeatmapInfo.Metadata; BeatmapInfo.BeatmapSet.Beatmaps.Add(BeatmapInfo); BeatmapInfo.BeatmapSet.OnlineID = Interlocked.Increment(ref onlineSetID); } diff --git a/osu.Game/Tests/Visual/EditorTestScene.cs b/osu.Game/Tests/Visual/EditorTestScene.cs index a2f567f3b0..570e4c26b3 100644 --- a/osu.Game/Tests/Visual/EditorTestScene.cs +++ b/osu.Game/Tests/Visual/EditorTestScene.cs @@ -122,9 +122,9 @@ namespace osu.Game.Tests.Visual this.testBeatmap = testBeatmap; } - protected override BeatmapModelManager CreateBeatmapModelManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, GameHost host) + protected override BeatmapModelManager CreateBeatmapModelManager(Storage storage, RealmContextFactory contextFactory, RulesetStore rulesets, BeatmapOnlineLookupQueue onlineLookupQueue) { - return new TestBeatmapModelManager(storage, contextFactory, rulesets, api, host); + return new TestBeatmapModelManager(storage, contextFactory, rulesets, onlineLookupQueue); } protected override WorkingBeatmapCache CreateWorkingBeatmapCache(AudioManager audioManager, IResourceStore resources, IResourceStore storage, WorkingBeatmap defaultBeatmap, GameHost host) @@ -148,8 +148,8 @@ namespace osu.Game.Tests.Visual internal class TestBeatmapModelManager : BeatmapModelManager { - public TestBeatmapModelManager(Storage storage, IDatabaseContextFactory databaseContextFactory, RulesetStore rulesetStore, IAPIProvider apiProvider, GameHost gameHost) - : base(storage, databaseContextFactory, rulesetStore, gameHost) + public TestBeatmapModelManager(Storage storage, RealmContextFactory databaseContextFactory, RulesetStore rulesetStore, BeatmapOnlineLookupQueue beatmapOnlineLookupQueue) + : base(databaseContextFactory, storage, beatmapOnlineLookupQueue) { } From d7fe3584cde52011d776d26f1bcb95a651d56896 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 14 Dec 2021 21:00:19 +0900 Subject: [PATCH 199/996] Don't persist `Countdown` to realm for now It's another enum which is a pain to handle, and not actually being consumed anywhere. --- osu.Game/Beatmaps/BeatmapInfo.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 796c2b2e90..fc0d007df4 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -101,6 +101,7 @@ namespace osu.Game.Beatmaps public double TimelineZoom { get; set; } + [Ignored] public CountdownType Countdown { get; set; } = CountdownType.Normal; /// From 8d943b57095cc17f172ba83086b50d05c86bed89 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Dec 2021 00:31:35 +0900 Subject: [PATCH 200/996] Fix many shortcomings and compatibility issues with EF classes post-rename --- osu.Game/Beatmaps/BeatmapSetFileInfo.cs | 4 +++- osu.Game/Beatmaps/EFBeatmapDifficulty.cs | 2 ++ osu.Game/Beatmaps/EFBeatmapInfo.cs | 3 ++- osu.Game/Beatmaps/EFBeatmapMetadata.cs | 1 + osu.Game/Beatmaps/EFBeatmapSetInfo.cs | 2 ++ osu.Game/Database/OsuDbContext.cs | 2 +- osu.Game/Rulesets/EFRulesetInfo.cs | 1 + osu.Game/Scoring/EFScoreInfo.cs | 29 +++++++++++++----------- osu.Game/Scoring/ScoreFileInfo.cs | 4 ++++ osu.Game/Scoring/ScoreInfo.cs | 9 -------- 10 files changed, 32 insertions(+), 25 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapSetFileInfo.cs b/osu.Game/Beatmaps/BeatmapSetFileInfo.cs index 29dcf4d6aa..3d41f59b3d 100644 --- a/osu.Game/Beatmaps/BeatmapSetFileInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetFileInfo.cs @@ -15,6 +15,8 @@ namespace osu.Game.Beatmaps public int BeatmapSetInfoID { get; set; } + public EFBeatmapSetInfo BeatmapSetInfo { get; set; } + public int FileInfoID { get; set; } public FileInfo FileInfo { get; set; } @@ -22,6 +24,6 @@ namespace osu.Game.Beatmaps [Required] public string Filename { get; set; } - public IFileInfo File => FileInfo; + IFileInfo INamedFileUsage.File => FileInfo; } } diff --git a/osu.Game/Beatmaps/EFBeatmapDifficulty.cs b/osu.Game/Beatmaps/EFBeatmapDifficulty.cs index 843c6ac6bf..38371d3b38 100644 --- a/osu.Game/Beatmaps/EFBeatmapDifficulty.cs +++ b/osu.Game/Beatmaps/EFBeatmapDifficulty.cs @@ -2,10 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.ComponentModel.DataAnnotations.Schema; using osu.Game.Database; namespace osu.Game.Beatmaps { + [Table(@"BeatmapDifficulty")] public class EFBeatmapDifficulty : IHasPrimaryKey, IBeatmapDifficultyInfo { /// diff --git a/osu.Game/Beatmaps/EFBeatmapInfo.cs b/osu.Game/Beatmaps/EFBeatmapInfo.cs index 2336eeb456..8257d0cf7d 100644 --- a/osu.Game/Beatmaps/EFBeatmapInfo.cs +++ b/osu.Game/Beatmaps/EFBeatmapInfo.cs @@ -16,6 +16,7 @@ namespace osu.Game.Beatmaps { [ExcludeFromDynamicCompile] [Serializable] + [Table(@"BeatmapInfo")] public class EFBeatmapInfo : IEquatable, IHasPrimaryKey, IBeatmapInfo { public int ID { get; set; } @@ -124,7 +125,7 @@ namespace osu.Game.Beatmaps /// /// Currently only populated for beatmap deletion. Use to query scores. /// - public List Scores { get; set; } + public List Scores { get; set; } [JsonIgnore] public DifficultyRating DifficultyRating => BeatmapDifficultyCache.GetDifficultyRating(StarRating); diff --git a/osu.Game/Beatmaps/EFBeatmapMetadata.cs b/osu.Game/Beatmaps/EFBeatmapMetadata.cs index 6c192088dc..7c27863a7f 100644 --- a/osu.Game/Beatmaps/EFBeatmapMetadata.cs +++ b/osu.Game/Beatmaps/EFBeatmapMetadata.cs @@ -16,6 +16,7 @@ namespace osu.Game.Beatmaps { [ExcludeFromDynamicCompile] [Serializable] + [Table(@"BeatmapMetadata")] public class EFBeatmapMetadata : IEquatable, IHasPrimaryKey, IBeatmapMetadataInfo { public int ID { get; set; } diff --git a/osu.Game/Beatmaps/EFBeatmapSetInfo.cs b/osu.Game/Beatmaps/EFBeatmapSetInfo.cs index 10910d9817..12235abce0 100644 --- a/osu.Game/Beatmaps/EFBeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/EFBeatmapSetInfo.cs @@ -14,6 +14,8 @@ using osu.Game.Extensions; namespace osu.Game.Beatmaps { [ExcludeFromDynamicCompile] + [Serializable] + [Table(@"BeatmapSetInfo")] public class EFBeatmapSetInfo : IHasPrimaryKey, IHasFiles, ISoftDelete, IEquatable, IBeatmapSetInfo { public int ID { get; set; } diff --git a/osu.Game/Database/OsuDbContext.cs b/osu.Game/Database/OsuDbContext.cs index fb83592c01..441b090a6e 100644 --- a/osu.Game/Database/OsuDbContext.cs +++ b/osu.Game/Database/OsuDbContext.cs @@ -147,7 +147,7 @@ namespace osu.Game.Database modelBuilder.Entity().HasOne(b => b.BaseDifficulty); - modelBuilder.Entity().HasIndex(b => b.OnlineID).IsUnique(); + modelBuilder.Entity().HasIndex(b => b.OnlineID).IsUnique(); } private class OsuDbLoggerFactory : ILoggerFactory diff --git a/osu.Game/Rulesets/EFRulesetInfo.cs b/osu.Game/Rulesets/EFRulesetInfo.cs index a7070ad2b6..473b7c657e 100644 --- a/osu.Game/Rulesets/EFRulesetInfo.cs +++ b/osu.Game/Rulesets/EFRulesetInfo.cs @@ -10,6 +10,7 @@ using osu.Framework.Testing; namespace osu.Game.Rulesets { [ExcludeFromDynamicCompile] + [Table(@"RulesetInfo")] public sealed class EFRulesetInfo : IEquatable, IRulesetInfo { public int? ID { get; set; } diff --git a/osu.Game/Scoring/EFScoreInfo.cs b/osu.Game/Scoring/EFScoreInfo.cs index 84b41e40ef..1dd4e3b6b3 100644 --- a/osu.Game/Scoring/EFScoreInfo.cs +++ b/osu.Game/Scoring/EFScoreInfo.cs @@ -19,6 +19,7 @@ using osu.Game.Utils; namespace osu.Game.Scoring { + [Table(@"ScoreInfo")] public class EFScoreInfo : IScoreInfo, IHasFiles, IHasPrimaryKey, ISoftDelete, IEquatable, IDeepCloneable { public int ID { get; set; } @@ -45,7 +46,7 @@ namespace osu.Game.Scoring [NotMapped] public bool Passed { get; set; } = true; - public RulesetInfo Ruleset { get; set; } + public EFRulesetInfo Ruleset { get; set; } private APIMod[] localAPIMods; @@ -135,9 +136,17 @@ namespace osu.Game.Scoring public int BeatmapInfoID { get; set; } [Column("Beatmap")] - public BeatmapInfo BeatmapInfo { get; set; } + public EFBeatmapInfo BeatmapInfo { get; set; } - public long? OnlineScoreID { get; set; } + private long? onlineID; + + [JsonProperty("id")] + [Column("OnlineScoreID")] + public long? OnlineID + { + get => onlineID; + set => onlineID = value > 0 ? value : null; + } public DateTimeOffset Date { get; set; } @@ -232,24 +241,18 @@ namespace osu.Game.Scoring public bool Equals(EFScoreInfo other) { - if (other == null) - return false; + if (ReferenceEquals(this, other)) return true; + if (other == null) return false; if (ID != 0 && other.ID != 0) return ID == other.ID; - if (OnlineScoreID.HasValue && other.OnlineScoreID.HasValue) - return OnlineScoreID == other.OnlineScoreID; - - if (!string.IsNullOrEmpty(Hash) && !string.IsNullOrEmpty(other.Hash)) - return Hash == other.Hash; - - return ReferenceEquals(this, other); + return false; } #region Implementation of IHasOnlineID - public long OnlineID => OnlineScoreID ?? -1; + long IHasOnlineID.OnlineID => OnlineID ?? -1; #endregion diff --git a/osu.Game/Scoring/ScoreFileInfo.cs b/osu.Game/Scoring/ScoreFileInfo.cs index 4c88cfa021..8acc98eff6 100644 --- a/osu.Game/Scoring/ScoreFileInfo.cs +++ b/osu.Game/Scoring/ScoreFileInfo.cs @@ -13,6 +13,10 @@ namespace osu.Game.Scoring public bool IsManaged => ID > 0; + public int ScoreInfoID { get; set; } + + public EFScoreInfo ScoreInfo { get; set; } + public int FileInfoID { get; set; } public FileInfo FileInfo { get; set; } diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 20b6ba7a93..65c31951b8 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using Newtonsoft.Json; using osu.Framework.Localisation; @@ -215,14 +214,6 @@ namespace osu.Game.Scoring } } - // Used for database serialisation/deserialisation. - [Column("Mods")] - public string ModsJson - { - get => JsonConvert.SerializeObject(APIMods); - set => APIMods = JsonConvert.DeserializeObject(value) ?? Array.Empty(); - } - public IEnumerable GetStatisticsForDisplay() { foreach (var r in Ruleset.CreateInstance().GetHitResults()) From 3ecd535f6e7f49fb2484109d0d44377810a7e9bb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Dec 2021 00:33:00 +0900 Subject: [PATCH 201/996] Add back missing `IRulesetStore` cache --- osu.Game/OsuGameBase.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 89e3fe5742..ac04be532a 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -222,6 +222,7 @@ namespace osu.Game var defaultBeatmap = new DummyWorkingBeatmap(Audio, Textures); dependencies.Cache(RulesetStore = new RulesetStore(realmFactory, Storage)); + dependencies.CacheAs(RulesetStore); // ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup() dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, realmFactory, Scheduler, Host, () => difficultyCache, LocalConfig)); From c4a9211179ed93cd287e67aa763e14dfae8ae54a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Dec 2021 14:26:38 +0900 Subject: [PATCH 202/996] Apply NRT to `BeatmapManager` and move `Hide`/`Restore` methods across --- osu.Game/Beatmaps/BeatmapManager.cs | 94 +++++++++++++----------- osu.Game/Beatmaps/BeatmapModelManager.cs | 45 ------------ 2 files changed, 51 insertions(+), 88 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 45ae69e4ba..efe9c65f9f 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -8,7 +8,6 @@ using System.Linq; using System.Linq.Expressions; using System.Threading; using System.Threading.Tasks; -using JetBrains.Annotations; using osu.Framework.Audio; using osu.Framework.Audio.Track; using osu.Framework.Extensions; @@ -25,6 +24,8 @@ using osu.Game.Rulesets; using osu.Game.Skinning; using osu.Game.Stores; +#nullable enable + namespace osu.Game.Beatmaps { /// @@ -38,10 +39,13 @@ namespace osu.Game.Beatmaps private readonly BeatmapModelManager beatmapModelManager; private readonly WorkingBeatmapCache workingBeatmapCache; - private readonly BeatmapOnlineLookupQueue onlineBeatmapLookupQueue; + private readonly BeatmapOnlineLookupQueue? onlineBeatmapLookupQueue; - public BeatmapManager(Storage storage, RealmContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore gameResources, GameHost host = null, WorkingBeatmap defaultBeatmap = null, bool performOnlineLookups = false) + private readonly RealmContextFactory contextFactory; + + public BeatmapManager(Storage storage, RealmContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, AudioManager audioManager, IResourceStore gameResources, GameHost? host = null, WorkingBeatmap? defaultBeatmap = null, bool performOnlineLookups = false) { + this.contextFactory = contextFactory; if (performOnlineLookups) onlineBeatmapLookupQueue = new BeatmapOnlineLookupQueue(api, storage); @@ -56,12 +60,12 @@ namespace osu.Game.Beatmaps beatmapModelManager.WorkingBeatmapCache = workingBeatmapCache; } - protected virtual WorkingBeatmapCache CreateWorkingBeatmapCache(AudioManager audioManager, IResourceStore resources, IResourceStore storage, WorkingBeatmap defaultBeatmap, GameHost host) + protected virtual WorkingBeatmapCache CreateWorkingBeatmapCache(AudioManager audioManager, IResourceStore resources, IResourceStore storage, WorkingBeatmap? defaultBeatmap, GameHost? host) { return new WorkingBeatmapCache(BeatmapTrackStore, audioManager, resources, storage, defaultBeatmap, host); } - protected virtual BeatmapModelManager CreateBeatmapModelManager(Storage storage, RealmContextFactory contextFactory, RulesetStore rulesets, [CanBeNull] BeatmapOnlineLookupQueue onlineLookupQueue) => + protected virtual BeatmapModelManager CreateBeatmapModelManager(Storage storage, RealmContextFactory contextFactory, RulesetStore rulesets, BeatmapOnlineLookupQueue? onlineLookupQueue) => new BeatmapModelManager(contextFactory, storage, onlineLookupQueue); /// @@ -103,19 +107,43 @@ namespace osu.Game.Beatmaps /// /// Fired when a single difficulty has been hidden. /// - public event Action BeatmapHidden - { - add => beatmapModelManager.BeatmapHidden += value; - remove => beatmapModelManager.BeatmapHidden -= value; - } + public event Action? BeatmapHidden; /// /// Fired when a single difficulty has been restored. /// - public event Action BeatmapRestored + public event Action? BeatmapRestored; + + /// + /// Delete a beatmap difficulty. + /// + /// The beatmap difficulty to hide. + public void Hide(BeatmapInfo beatmapInfo) { - add => beatmapModelManager.BeatmapRestored += value; - remove => beatmapModelManager.BeatmapRestored -= value; + using (var realm = contextFactory.CreateContext()) + using (var transaction = realm.BeginWrite()) + { + beatmapInfo.Hidden = true; + transaction.Commit(); + + BeatmapHidden?.Invoke(beatmapInfo); + } + } + + /// + /// Restore a beatmap difficulty. + /// + /// The beatmap difficulty to restore. + public void Restore(BeatmapInfo beatmapInfo) + { + using (var realm = contextFactory.CreateContext()) + using (var transaction = realm.BeginWrite()) + { + beatmapInfo.Hidden = false; + transaction.Commit(); + + BeatmapRestored?.Invoke(beatmapInfo); + } } /// @@ -124,7 +152,7 @@ namespace osu.Game.Beatmaps /// The to save the content against. The file referenced by will be replaced. /// The content to write. /// The beatmap content to write, null if to be omitted. - public virtual void Save(BeatmapInfo info, IBeatmap beatmapContent, ISkin beatmapSkin = null) => + public virtual void Save(BeatmapInfo info, IBeatmap beatmapContent, ISkin? beatmapSkin = null) => beatmapModelManager.Save(info, beatmapContent, beatmapSkin); /// @@ -152,7 +180,7 @@ namespace osu.Game.Beatmaps /// /// The query. /// The first result for the provided query, or null if no results were found. - public BeatmapSetInfo QueryBeatmapSet(Expression> query) => beatmapModelManager.QueryBeatmapSet(query); + public BeatmapSetInfo? QueryBeatmapSet(Expression> query) => beatmapModelManager.QueryBeatmapSet(query); /// /// Perform a lookup query on available s. @@ -166,7 +194,7 @@ namespace osu.Game.Beatmaps /// /// The query. /// The first result for the provided query, or null if no results were found. - public BeatmapInfo QueryBeatmap(Expression> query) => beatmapModelManager.QueryBeatmap(query); + public BeatmapInfo? QueryBeatmap(Expression> query) => beatmapModelManager.QueryBeatmap(query); /// /// A default representation of a WorkingBeatmap to use when no beatmap is available. @@ -181,18 +209,6 @@ namespace osu.Game.Beatmaps set => beatmapModelManager.PostNotification = value; } - /// - /// Delete a beatmap difficulty. - /// - /// The beatmap difficulty to hide. - public void Hide(BeatmapInfo beatmapInfo) => beatmapModelManager.Hide(beatmapInfo); - - /// - /// Restore a beatmap difficulty. - /// - /// The beatmap difficulty to restore. - public void Restore(BeatmapInfo beatmapInfo) => beatmapModelManager.Restore(beatmapInfo); - #endregion #region Implementation of IModelManager @@ -202,17 +218,9 @@ namespace osu.Game.Beatmaps return beatmapModelManager.IsAvailableLocally(model); } - public event Action ItemUpdated - { - add => beatmapModelManager.ItemUpdated += value; - remove => beatmapModelManager.ItemUpdated -= value; - } + public event Action? ItemUpdated; - public event Action ItemRemoved - { - add => beatmapModelManager.ItemRemoved += value; - remove => beatmapModelManager.ItemRemoved -= value; - } + public event Action? ItemRemoved; public void Update(BeatmapSetInfo item) { @@ -258,17 +266,17 @@ namespace osu.Game.Beatmaps return beatmapModelManager.Import(notification, tasks); } - public Task> Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default) + public Task?> Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default) { return beatmapModelManager.Import(task, lowPriority, cancellationToken); } - public Task> Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default) + public Task?> Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default) { return beatmapModelManager.Import(archive, lowPriority, cancellationToken); } - public Task> Import(BeatmapSetInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) + public Task?> Import(BeatmapSetInfo item, ArchiveReader? archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) { return beatmapModelManager.Import(item, archive, lowPriority, cancellationToken); } @@ -279,7 +287,7 @@ namespace osu.Game.Beatmaps #region Implementation of IWorkingBeatmapCache - public WorkingBeatmap GetWorkingBeatmap(BeatmapInfo importedBeatmap) => workingBeatmapCache.GetWorkingBeatmap(importedBeatmap); + public WorkingBeatmap GetWorkingBeatmap(BeatmapInfo? importedBeatmap) => workingBeatmapCache.GetWorkingBeatmap(importedBeatmap); void IWorkingBeatmapCache.Invalidate(BeatmapSetInfo beatmapSetInfo) => workingBeatmapCache.Invalidate(beatmapSetInfo); void IWorkingBeatmapCache.Invalidate(BeatmapInfo beatmapInfo) => workingBeatmapCache.Invalidate(beatmapInfo); @@ -316,7 +324,7 @@ namespace osu.Game.Beatmaps #region Implementation of IPostImports - public Action>> PostImport + public Action>>? PostImport { set => beatmapModelManager.PostImport = value; } diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs index 100189bf09..1d1aa20a5b 100644 --- a/osu.Game/Beatmaps/BeatmapModelManager.cs +++ b/osu.Game/Beatmaps/BeatmapModelManager.cs @@ -21,22 +21,9 @@ using osu.Game.Stores; namespace osu.Game.Beatmaps { - /// - /// Handles ef-core storage of beatmaps. - /// [ExcludeFromDynamicCompile] public class BeatmapModelManager : BeatmapImporter { - /// - /// Fired when a single difficulty has been hidden. - /// - public event Action? BeatmapHidden; - - /// - /// Fired when a single difficulty has been restored. - /// - public event Action? BeatmapRestored; - /// /// The game working beatmap cache, used to invalidate entries on changes. /// @@ -53,38 +40,6 @@ namespace osu.Game.Beatmaps protected override bool ShouldDeleteArchive(string path) => Path.GetExtension(path)?.ToLowerInvariant() == ".osz"; - /// - /// Delete a beatmap difficulty. - /// - /// The beatmap difficulty to hide. - public void Hide(BeatmapInfo beatmapInfo) - { - using (var realm = ContextFactory.CreateContext()) - using (var transaction = realm.BeginWrite()) - { - beatmapInfo.Hidden = true; - transaction.Commit(); - - BeatmapHidden?.Invoke(beatmapInfo); - } - } - - /// - /// Restore a beatmap difficulty. - /// - /// The beatmap difficulty to restore. - public void Restore(BeatmapInfo beatmapInfo) - { - using (var realm = ContextFactory.CreateContext()) - using (var transaction = realm.BeginWrite()) - { - beatmapInfo.Hidden = false; - transaction.Commit(); - - BeatmapRestored?.Invoke(beatmapInfo); - } - } - /// /// Saves an file against a given . /// From abd72c496ba470888ef84b7055fd6078967314a6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 8 Nov 2021 17:59:23 +0900 Subject: [PATCH 203/996] "Update" `MusicController` --- osu.Game/Overlays/MusicController.cs | 46 +++++++++++++++++++--------- 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 310f4946f3..58707c3321 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -12,10 +12,11 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Audio; using osu.Framework.Graphics.Containers; -using osu.Framework.Utils; using osu.Framework.Threading; using osu.Game.Beatmaps; +using osu.Game.Database; using osu.Game.Rulesets.Mods; +using Realms; namespace osu.Game.Overlays { @@ -24,6 +25,8 @@ namespace osu.Game.Overlays /// public class MusicController : CompositeDrawable { + private IDisposable beatmapSubscription; + [Resolved] private BeatmapManager beatmaps { get; set; } @@ -65,13 +68,14 @@ namespace osu.Game.Overlays [NotNull] public DrawableTrack CurrentTrack { get; private set; } = new DrawableTrack(new TrackVirtual(1000)); - [BackgroundDependencyLoader] - private void load() - { - beatmaps.ItemUpdated += beatmapUpdated; - beatmaps.ItemRemoved += beatmapRemoved; + [Resolved] + private RealmContextFactory realmFactory { get; set; } - beatmapSets.AddRange(beatmaps.GetAllUsableBeatmapSets(true).OrderBy(_ => RNG.Next())); + protected override void LoadComplete() + { + base.LoadComplete(); + + beatmapSubscription = realmFactory.Context.All().QueryAsyncWithNotifications(beatmapsChanged); // Todo: These binds really shouldn't be here, but are unlikely to cause any issues for now. // They are placed here for now since some tests rely on setting the beatmap _and_ their hierarchies inside their load(), which runs before the MusicController's load(). @@ -79,6 +83,24 @@ namespace osu.Game.Overlays mods.BindValueChanged(_ => ResetTrackAdjustments(), true); } + private void beatmapsChanged(IRealmCollection sender, ChangeSet changes, Exception error) + { + if (changes == null) + { + beatmapSets.AddRange(sender); + return; + } + + // beatmaps.ItemUpdated += set => Schedule(() => + // { + // beatmapSets.Remove(set); + // beatmapSets.Add(set); + // }); + // beatmaps.ItemRemoved += set => Schedule(() => beatmapSets.RemoveAll(s => s.ID == set.ID)); + // + // beatmapSets.AddRange(beatmaps.GetAllUsableBeatmapSets(IncludedDetails.Minimal, true).OrderBy(_ => RNG.Next())); + } + /// /// Forcefully reload the current 's track from disk. /// @@ -111,7 +133,7 @@ namespace osu.Game.Overlays beatmapSets.Add(set); }); - private void beatmapRemoved(BeatmapSetInfo set) => Schedule(() => beatmapSets.RemoveAll(s => s.Equals(set))); + private void beatmapRemoved(BeatmapSetInfo set) => Schedule(() => beatmapSets.RemoveAll(s => s.ID == set.ID)); private ScheduledDelegate seekDelegate; @@ -259,7 +281,7 @@ namespace osu.Game.Overlays queuedDirection = TrackChangeDirection.Next; - var playable = BeatmapSets.SkipWhile(i => !i.Equals(current.BeatmapSetInfo)).ElementAtOrDefault(1) ?? BeatmapSets.FirstOrDefault(); + var playable = BeatmapSets.SkipWhile(i => i.ID != current.BeatmapSetInfo.ID).ElementAtOrDefault(1) ?? BeatmapSets.FirstOrDefault(); if (playable != null) { @@ -429,11 +451,7 @@ namespace osu.Game.Overlays { base.Dispose(isDisposing); - if (beatmaps != null) - { - beatmaps.ItemUpdated -= beatmapUpdated; - beatmaps.ItemRemoved -= beatmapRemoved; - } + beatmapSubscription?.Dispose(); } } From 8696f82627ea226831f1c245487a5ef1f2b3ef63 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Dec 2021 14:36:11 +0900 Subject: [PATCH 204/996] Fix intro screen Fix things --- osu.Game/Beatmaps/BeatmapManager.cs | 2 +- osu.Game/Screens/Menu/IntroScreen.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index efe9c65f9f..1d39873016 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -173,7 +173,7 @@ namespace osu.Game.Beatmaps /// /// The query. /// Results from the provided query. - public IEnumerable QueryBeatmapSets(Expression> query) => beatmapModelManager.QueryBeatmapSets(query); + public ILive> QueryBeatmapSets(Expression> query) => beatmapModelManager.QueryBeatmapSets(query).ToLive(contextFactory); /// /// Perform a lookup query on available s. diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index 94c48f5251..aa3fe9fd88 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -100,7 +100,7 @@ namespace osu.Game.Screens.Menu if (sets.Count > 0) { setInfo = beatmaps.QueryBeatmapSet(s => s.ID == sets[RNG.Next(0, sets.Count - 1)].ID); - initialBeatmap = beatmaps.GetWorkingBeatmap(setInfo.Beatmaps[0]); + initialBeatmap = beatmaps.GetWorkingBeatmap(setInfo?.Beatmaps[0]); } } @@ -113,7 +113,7 @@ namespace osu.Game.Screens.Menu // this could happen if a user has nuked their files store. for now, reimport to repair this. var import = beatmaps.Import(new ZipArchiveReader(game.Resources.GetStream($"Tracks/{BeatmapFile}"), BeatmapFile)).GetResultSafely(); - import.PerformWrite(b => + import?.PerformWrite(b => { b.Protected = true; beatmaps.Update(b); From 3152d2d8a02411fb690965065c03f12ca3f9f1fa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 8 Nov 2021 17:41:42 +0900 Subject: [PATCH 205/996] "Update" `BeatmapCarousel` --- .../SongSelect/TestSceneBeatmapCarousel.cs | 2 - osu.Game/Screens/Select/BeatmapCarousel.cs | 67 ++++++++++++++----- 2 files changed, 51 insertions(+), 18 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 28f5c3ff60..b1414d5896 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -873,8 +873,6 @@ namespace osu.Game.Tests.Visual.SongSelect } } } - - protected override IEnumerable GetLoadableBeatmaps() => Enumerable.Empty(); } } } diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index f8d2c3cb9b..94e7dc9241 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -18,12 +18,14 @@ using osu.Framework.Threading; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Configuration; +using osu.Game.Database; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; using osu.Game.Input.Bindings; using osu.Game.Screens.Select.Carousel; using osuTK; using osuTK.Input; +using Realms; namespace osu.Game.Screens.Select { @@ -172,17 +174,57 @@ namespace osu.Game.Screens.Select RightClickScrollingEnabled.ValueChanged += enabled => Scroll.RightMouseScrollbar = enabled.NewValue; RightClickScrollingEnabled.TriggerChange(); - - beatmaps.ItemUpdated += beatmapUpdated; - beatmaps.ItemRemoved += beatmapRemoved; - beatmaps.BeatmapHidden += beatmapHidden; - beatmaps.BeatmapRestored += beatmapRestored; - - if (!beatmapSets.Any()) - loadBeatmapSets(GetLoadableBeatmaps()); } - protected virtual IEnumerable GetLoadableBeatmaps() => beatmaps.GetAllUsableBeatmapSetsEnumerable(); + [Resolved] + private RealmContextFactory realmFactory { get; set; } + + protected override void LoadComplete() + { + base.LoadComplete(); + + realmFactory.Context.All().Where(s => !s.DeletePending).QueryAsyncWithNotifications(beatmapSetsChanged); + realmFactory.Context.All().Where(b => !b.Hidden).QueryAsyncWithNotifications(beatmapsChanged); + } + + private void beatmapRemoved(BeatmapSetInfo item) => RemoveBeatmapSet(item); + private void beatmapUpdated(BeatmapSetInfo item) => UpdateBeatmapSet(item); + + private void beatmapSetsChanged(IRealmCollection sender, ChangeSet changes, Exception error) + { + if (changes == null) + { + // initial load + loadBeatmapSets(sender); + return; + } + + foreach (int i in changes.NewModifiedIndices) + UpdateBeatmapSet(sender[i]); + + // moves also appear as deletes / inserts but aren't important to us. + if (!changes.Moves.Any()) + { + foreach (int i in changes.InsertedIndices) + UpdateBeatmapSet(sender[i]); + + // TODO: This can not work, as we recently found out https://github.com/realm/realm-dotnet/discussions/2634#discussioncomment-1605595. + foreach (int i in changes.DeletedIndices) + RemoveBeatmapSet(sender[i]); + } + } + + private void beatmapsChanged(IRealmCollection sender, ChangeSet changes, Exception error) + { + // we only care about actual changes in hidden status. + if (changes == null) + return; + + // TODO: we can probably handle hidden items at a per-panel level (ie. start a realm subscription from there)? + // might be cleaner than handling via reconstruction of the whole set's panels. + foreach (int i in changes.NewModifiedIndices) + UpdateBeatmapSet(sender[i].BeatmapSet); + } public void RemoveBeatmapSet(BeatmapSetInfo beatmapSet) => Schedule(() => { @@ -617,11 +659,6 @@ namespace osu.Game.Screens.Select return (firstIndex, lastIndex); } - private void beatmapRemoved(BeatmapSetInfo item) => RemoveBeatmapSet(item); - private void beatmapUpdated(BeatmapSetInfo item) => UpdateBeatmapSet(item); - private void beatmapRestored(BeatmapInfo b) => UpdateBeatmapSet(beatmaps.QueryBeatmapSet(s => s.ID == b.BeatmapSetInfoID)); - private void beatmapHidden(BeatmapInfo b) => UpdateBeatmapSet(beatmaps.QueryBeatmapSet(s => s.ID == b.BeatmapSetInfoID)); - private CarouselBeatmapSet createCarouselSet(BeatmapSetInfo beatmapSet) { if (beatmapSet.Beatmaps.All(b => b.Hidden)) @@ -888,8 +925,6 @@ namespace osu.Game.Screens.Select { beatmaps.ItemUpdated -= beatmapUpdated; beatmaps.ItemRemoved -= beatmapRemoved; - beatmaps.BeatmapHidden -= beatmapHidden; - beatmaps.BeatmapRestored -= beatmapRestored; } } } From 1d536fd0bc6bed1575503f7a5f7636cbc0079bc7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Dec 2021 15:03:06 +0900 Subject: [PATCH 206/996] Start introducing `ILive` --- osu.Game/Beatmaps/BeatmapManager.cs | 4 +- osu.Game/Beatmaps/BeatmapModelManager.cs | 4 +- osu.Game/OsuGame.cs | 45 ++++++++++--------- osu.Game/Overlays/MusicController.cs | 1 - .../Sections/Maintenance/GeneralSettings.cs | 3 +- osu.Game/Screens/Menu/IntroScreen.cs | 7 +-- 6 files changed, 34 insertions(+), 30 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 1d39873016..7fb0729967 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -173,14 +173,14 @@ namespace osu.Game.Beatmaps /// /// The query. /// Results from the provided query. - public ILive> QueryBeatmapSets(Expression> query) => beatmapModelManager.QueryBeatmapSets(query).ToLive(contextFactory); + public List> QueryBeatmapSets(Expression> query) => beatmapModelManager.QueryBeatmapSets(query).ToLive(); /// /// Perform a lookup query on available s. /// /// The query. /// The first result for the provided query, or null if no results were found. - public BeatmapSetInfo? QueryBeatmapSet(Expression> query) => beatmapModelManager.QueryBeatmapSet(query); + public ILive? QueryBeatmapSet(Expression> query) => beatmapModelManager.QueryBeatmapSet(query); /// /// Perform a lookup query on available s. diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs index 1d1aa20a5b..839d3df159 100644 --- a/osu.Game/Beatmaps/BeatmapModelManager.cs +++ b/osu.Game/Beatmaps/BeatmapModelManager.cs @@ -104,10 +104,10 @@ namespace osu.Game.Beatmaps /// /// The query. /// The first result for the provided query, or null if no results were found. - public BeatmapSetInfo? QueryBeatmapSet(Expression> query) + public ILive? QueryBeatmapSet(Expression> query) { using (var context = ContextFactory.CreateContext()) - return context.All().FirstOrDefault(query); // TODO: ?.ToLive(); + return context.All().FirstOrDefault(query)?.ToLive(); } /// diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 49893a6d36..8d1eccb7c1 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -437,7 +437,7 @@ namespace osu.Game /// public void PresentBeatmap(IBeatmapSetInfo beatmap, Predicate difficultyCriteria = null) { - BeatmapSetInfo databasedSet = null; + ILive databasedSet = null; if (beatmap.OnlineID > 0) databasedSet = BeatmapManager.QueryBeatmapSet(s => s.OnlineID == beatmap.OnlineID); @@ -453,27 +453,30 @@ namespace osu.Game PerformFromScreen(screen => { - // Find beatmaps that match our predicate. - var beatmaps = databasedSet.Beatmaps.Where(b => difficultyCriteria?.Invoke(b) ?? true).ToList(); - - // Use all beatmaps if predicate matched nothing - if (beatmaps.Count == 0) - beatmaps = databasedSet.Beatmaps.ToList(); - - // Prefer recommended beatmap if recommendations are available, else fallback to a sane selection. - var selection = difficultyRecommender.GetRecommendedBeatmap(beatmaps) - ?? beatmaps.FirstOrDefault(b => b.Ruleset.Equals(Ruleset.Value)) - ?? beatmaps.First(); - - if (screen is IHandlePresentBeatmap presentableScreen) + databasedSet.PerformRead(set => { - presentableScreen.PresentBeatmap(BeatmapManager.GetWorkingBeatmap(selection), selection.Ruleset); - } - else - { - Ruleset.Value = selection.Ruleset; - Beatmap.Value = BeatmapManager.GetWorkingBeatmap(selection); - } + // Find beatmaps that match our predicate. + var beatmaps = set.Beatmaps.Where(b => difficultyCriteria?.Invoke(b) ?? true).ToList(); + + // Use all beatmaps if predicate matched nothing + if (beatmaps.Count == 0) + beatmaps = set.Beatmaps.ToList(); + + // Prefer recommended beatmap if recommendations are available, else fallback to a sane selection. + var selection = difficultyRecommender.GetRecommendedBeatmap(beatmaps) + ?? beatmaps.FirstOrDefault(b => b.Ruleset.Equals(Ruleset.Value)) + ?? beatmaps.First(); + + if (screen is IHandlePresentBeatmap presentableScreen) + { + presentableScreen.PresentBeatmap(BeatmapManager.GetWorkingBeatmap(selection), selection.Ruleset); + } + else + { + Ruleset.Value = selection.Ruleset; + Beatmap.Value = BeatmapManager.GetWorkingBeatmap(selection); + } + }); }, validScreens: new[] { typeof(SongSelect), typeof(IHandlePresentBeatmap) }); } diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 58707c3321..8c115c2c94 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -88,7 +88,6 @@ namespace osu.Game.Overlays if (changes == null) { beatmapSets.AddRange(sender); - return; } // beatmaps.ItemUpdated += set => Schedule(() => diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs index 51e4d61d2e..93884812fe 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs @@ -160,7 +160,8 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance Action = () => { undeleteButton.Enabled.Value = false; - Task.Run(() => beatmaps.Undelete(beatmaps.QueryBeatmapSets(b => b.DeletePending).ToList())).ContinueWith(t => Schedule(() => undeleteButton.Enabled.Value = true)); + // TODO: reimplement similar to SkinManager? + // Task.Run(() => beatmaps.Undelete(beatmaps.QueryBeatmapSets(b => b.DeletePending).ToList())).ContinueWith(t => Schedule(() => undeleteButton.Enabled.Value = true)); } }, }); diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index aa3fe9fd88..bb8ddb8cd6 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -15,6 +15,7 @@ using osu.Framework.Screens; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Configuration; +using osu.Game.Database; using osu.Game.IO.Archives; using osu.Game.Overlays; using osu.Game.Screens.Backgrounds; @@ -90,7 +91,7 @@ namespace osu.Game.Screens.Menu MenuMusic = config.GetBindable(OsuSetting.MenuMusic); seeya = audio.Samples.Get(SeeyaSampleName); - BeatmapSetInfo setInfo = null; + ILive setInfo = null; // if the user has requested not to play theme music, we should attempt to find a random beatmap from their collection. if (!MenuMusic.Value) @@ -100,7 +101,7 @@ namespace osu.Game.Screens.Menu if (sets.Count > 0) { setInfo = beatmaps.QueryBeatmapSet(s => s.ID == sets[RNG.Next(0, sets.Count - 1)].ID); - initialBeatmap = beatmaps.GetWorkingBeatmap(setInfo?.Beatmaps[0]); + initialBeatmap = beatmaps.GetWorkingBeatmap(setInfo?.PerformRead(s => s.Beatmaps[0].ToLive())); } } @@ -130,7 +131,7 @@ namespace osu.Game.Screens.Menu if (setInfo == null) return false; - initialBeatmap = beatmaps.GetWorkingBeatmap(setInfo.Beatmaps[0]); + initialBeatmap = beatmaps.GetWorkingBeatmap(setInfo.PerformRead(s => s.Beatmaps[0].ToLive())); return UsingThemedIntro = initialBeatmap != null; } From b91f3098793c19a09554abb5b96e17ae8212a4f4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Dec 2021 15:24:26 +0900 Subject: [PATCH 207/996] Inline query methods from `BeatmapModelManager` to `BeatmapManager` where possible --- osu.Game/Beatmaps/BeatmapManager.cs | 60 ++++++++++++++++++------ osu.Game/Beatmaps/BeatmapModelManager.cs | 58 ----------------------- 2 files changed, 45 insertions(+), 73 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 7fb0729967..22871bd332 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -146,48 +146,60 @@ namespace osu.Game.Beatmaps } } - /// - /// Saves an file against a given . - /// - /// The to save the content against. The file referenced by will be replaced. - /// The content to write. - /// The beatmap content to write, null if to be omitted. - public virtual void Save(BeatmapInfo info, IBeatmap beatmapContent, ISkin? beatmapSkin = null) => - beatmapModelManager.Save(info, beatmapContent, beatmapSkin); - /// /// Returns a list of all usable s. /// /// A list of available . - public List GetAllUsableBeatmapSets(bool includeProtected = false) => beatmapModelManager.GetAllUsableBeatmapSets(includeProtected); + public List GetAllUsableBeatmapSets(bool includeProtected = false) + { + using (var context = contextFactory.CreateContext()) + return context.All().Where(b => !b.DeletePending && (includeProtected || !b.Protected)).ToList(); + } /// - /// Returns a list of all usable s. Note that files are not populated. + /// Returns a list of all usable s. Note that files are not populated. TODO: do we still need this? or rather, should we have the non-enumerable version in the first place? /// /// Whether to include protected (system) beatmaps. These should not be included for gameplay playable use cases. /// A list of available . - public IEnumerable GetAllUsableBeatmapSetsEnumerable(bool includeProtected = false) => beatmapModelManager.GetAllUsableBeatmapSetsEnumerable(includeProtected); + public IEnumerable GetAllUsableBeatmapSetsEnumerable(bool includeProtected = false) => GetAllUsableBeatmapSets(includeProtected); /// /// Perform a lookup query on available s. /// /// The query. /// Results from the provided query. - public List> QueryBeatmapSets(Expression> query) => beatmapModelManager.QueryBeatmapSets(query).ToLive(); + public List> QueryBeatmapSets(Expression> query) + { + using (var context = contextFactory.CreateContext()) + { + return context.All() + .Where(b => !b.DeletePending) + .Where(query) + .ToLive(); + } + } /// /// Perform a lookup query on available s. /// /// The query. /// The first result for the provided query, or null if no results were found. - public ILive? QueryBeatmapSet(Expression> query) => beatmapModelManager.QueryBeatmapSet(query); + public ILive? QueryBeatmapSet(Expression> query) + { + using (var context = contextFactory.CreateContext()) + return context.All().FirstOrDefault(query)?.ToLive(); + } /// /// Perform a lookup query on available s. /// /// The query. /// Results from the provided query. - public IQueryable QueryBeatmaps(Expression> query) => beatmapModelManager.QueryBeatmaps(query); + public IQueryable QueryBeatmaps(Expression> query) + { + using (var context = contextFactory.CreateContext()) + return context.All().Where(query); + } /// /// Perform a lookup query on available s. @@ -196,6 +208,15 @@ namespace osu.Game.Beatmaps /// The first result for the provided query, or null if no results were found. public BeatmapInfo? QueryBeatmap(Expression> query) => beatmapModelManager.QueryBeatmap(query); + /// + /// Saves an file against a given . + /// + /// The to save the content against. The file referenced by will be replaced. + /// The content to write. + /// The beatmap content to write, null if to be omitted. + public virtual void Save(BeatmapInfo info, IBeatmap beatmapContent, ISkin? beatmapSkin = null) => + beatmapModelManager.Save(info, beatmapContent, beatmapSkin); + /// /// A default representation of a WorkingBeatmap to use when no beatmap is available. /// @@ -289,6 +310,15 @@ namespace osu.Game.Beatmaps public WorkingBeatmap GetWorkingBeatmap(BeatmapInfo? importedBeatmap) => workingBeatmapCache.GetWorkingBeatmap(importedBeatmap); + public WorkingBeatmap GetWorkingBeatmap(ILive? importedBeatmap) + { + WorkingBeatmap working = workingBeatmapCache.GetWorkingBeatmap(null); + + importedBeatmap?.PerformRead(b => working = workingBeatmapCache.GetWorkingBeatmap(b)); + + return working; + } + void IWorkingBeatmapCache.Invalidate(BeatmapSetInfo beatmapSetInfo) => workingBeatmapCache.Invalidate(beatmapSetInfo); void IWorkingBeatmapCache.Invalidate(BeatmapInfo beatmapInfo) => workingBeatmapCache.Invalidate(beatmapInfo); diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs index 839d3df159..387521ee1d 100644 --- a/osu.Game/Beatmaps/BeatmapModelManager.cs +++ b/osu.Game/Beatmaps/BeatmapModelManager.cs @@ -99,50 +99,6 @@ namespace osu.Game.Beatmaps WorkingBeatmapCache?.Invalidate(beatmapInfo); } - /// - /// Perform a lookup query on available s. - /// - /// The query. - /// The first result for the provided query, or null if no results were found. - public ILive? QueryBeatmapSet(Expression> query) - { - using (var context = ContextFactory.CreateContext()) - return context.All().FirstOrDefault(query)?.ToLive(); - } - - /// - /// Returns a list of all usable s. - /// - /// A list of available . - public List GetAllUsableBeatmapSets(bool includeProtected = false) => - GetAllUsableBeatmapSetsEnumerable(includeProtected).ToList(); - - /// - /// Returns a list of all usable s. Note that files are not populated. - /// - /// Whether to include protected (system) beatmaps. These should not be included for gameplay playable use cases. - /// A list of available . - public IEnumerable GetAllUsableBeatmapSetsEnumerable(bool includeProtected = false) - { - using (var context = ContextFactory.CreateContext()) - return context.All().Where(b => !b.DeletePending && (includeProtected || !b.Protected)); - } - - /// - /// Perform a lookup query on available s. - /// - /// The query. - /// Results from the provided query. - public IEnumerable QueryBeatmapSets(Expression> query) - { - using (var context = ContextFactory.CreateContext()) - { - return context.All() - .Where(b => !b.DeletePending) - .Where(query); - } - } - /// /// Perform a lookup query on available s. /// @@ -153,19 +109,5 @@ namespace osu.Game.Beatmaps using (var context = ContextFactory.CreateContext()) return context.All().FirstOrDefault(query); // TODO: ?.ToLive(); } - - /// - /// Perform a lookup query on available s. - /// - /// The query. - /// Results from the provided query. - public IQueryable QueryBeatmaps(Expression> query) - { - using (var context = ContextFactory.CreateContext()) - { - return context.All() - .Where(query); - } - } } } From a3276758b8c8c121272b633df3d05b428e455d5a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Dec 2021 15:26:48 +0900 Subject: [PATCH 208/996] Remove unnecessary re-query of beatmap set in editor menu construction --- 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 7955464590..3a420f4994 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -776,7 +776,7 @@ namespace osu.Game.Screens.Edit fileMenuItems.Add(new EditorMenuItemSpacer()); - var beatmapSet = beatmapManager.QueryBeatmapSet(bs => bs.ID == Beatmap.Value.BeatmapSetInfo.ID) ?? playableBeatmap.BeatmapInfo.BeatmapSet; + var beatmapSet = playableBeatmap.BeatmapInfo.BeatmapSet; Debug.Assert(beatmapSet != null); From db05727ec49faf970578237cb1f36b00fa162b0b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 16 Dec 2021 20:11:45 +0900 Subject: [PATCH 209/996] Remove unused `includeProtected` parameter --- osu.Game/Beatmaps/BeatmapManager.cs | 15 ++++----------- osu.Game/Screens/Select/SongSelect.cs | 4 ++-- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 22871bd332..706160bfb5 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -102,8 +102,6 @@ namespace osu.Game.Beatmaps return GetWorkingBeatmap(imported?.Beatmaps.First()); } - #region Delegation to BeatmapModelManager (methods which previously existed locally). - /// /// Fired when a single difficulty has been hidden. /// @@ -150,19 +148,12 @@ namespace osu.Game.Beatmaps /// Returns a list of all usable s. /// /// A list of available . - public List GetAllUsableBeatmapSets(bool includeProtected = false) + public List GetAllUsableBeatmapSets() { using (var context = contextFactory.CreateContext()) - return context.All().Where(b => !b.DeletePending && (includeProtected || !b.Protected)).ToList(); + return context.All().Where(b => !b.DeletePending).ToList(); } - /// - /// Returns a list of all usable s. Note that files are not populated. TODO: do we still need this? or rather, should we have the non-enumerable version in the first place? - /// - /// Whether to include protected (system) beatmaps. These should not be included for gameplay playable use cases. - /// A list of available . - public IEnumerable GetAllUsableBeatmapSetsEnumerable(bool includeProtected = false) => GetAllUsableBeatmapSets(includeProtected); - /// /// Perform a lookup query on available s. /// @@ -201,6 +192,8 @@ namespace osu.Game.Beatmaps return context.All().Where(query); } + #region Delegation to BeatmapModelManager (methods which previously existed locally). + /// /// Perform a lookup query on available s. /// diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 1aab84183a..54a4c3270d 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -295,7 +295,7 @@ namespace osu.Game.Screens.Select Schedule(() => { // if we have no beatmaps, let's prompt the user to import from over a stable install if he has one. - if (!beatmaps.GetAllUsableBeatmapSetsEnumerable().Any() && DisplayStableImportPrompt) + if (!beatmaps.GetAllUsableBeatmapSets().Any() && DisplayStableImportPrompt) { dialogOverlay.Push(new ImportFromStablePopup(() => { @@ -421,7 +421,7 @@ namespace osu.Game.Screens.Select // A selection may not have been possible with filters applied. // There was possibly a ruleset mismatch. This is a case we can help things along by updating the game-wide ruleset to match. - if (e.NewValue.BeatmapInfo.Ruleset != null && !e.NewValue.BeatmapInfo.Ruleset.Equals(decoupledRuleset.Value)) + if (!e.NewValue.BeatmapInfo.Ruleset.Equals(decoupledRuleset.Value)) { Ruleset.Value = e.NewValue.BeatmapInfo.Ruleset; transferRulesetValue(); From 31a3161189f7eb915708fbbeef05a27292f05b0c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 17 Dec 2021 18:26:12 +0900 Subject: [PATCH 210/996] Make tests compile again --- .../Beatmaps/IO/BeatmapImportHelper.cs | 87 +++++++++++++++++++ .../TestSceneBeatmapDifficultyCache.cs | 2 +- .../Database/BeatmapImporterTests.cs | 24 ++--- ...eneOnlinePlayBeatmapAvailabilityTracker.cs | 6 +- osu.Game.Tests/Scores/IO/ImportScoreTest.cs | 2 +- .../Visual/Beatmaps/TestSceneBeatmapCard.cs | 2 +- .../Editing/TestSceneDifficultySwitching.cs | 2 +- .../Editing/TestSceneEditorBeatmapCreation.cs | 4 +- .../Editing/TestSceneEditorTestGameplay.cs | 2 +- .../Visual/Gameplay/TestSceneSpectator.cs | 2 +- .../Visual/Multiplayer/QueueModeTestScene.cs | 2 +- .../TestSceneDrawableRoomPlaylist.cs | 6 +- .../TestSceneMultiSpectatorScreen.cs | 2 +- .../Multiplayer/TestSceneMultiplayer.cs | 4 +- .../TestSceneMultiplayerMatchSubScreen.cs | 2 +- .../TestSceneMultiplayerPlaylist.cs | 2 +- .../TestSceneMultiplayerQueueList.cs | 4 +- .../TestSceneMultiplayerReadyButton.cs | 2 +- .../TestSceneMultiplayerSpectateButton.cs | 2 +- .../Visual/Multiplayer/TestSceneTeamVersus.cs | 2 +- .../TestSceneMouseWheelVolumeAdjust.cs | 2 +- .../Navigation/TestScenePerformFromScreen.cs | 2 +- .../Navigation/TestSceneScreenNavigation.cs | 6 +- .../Online/TestSceneBeatmapDownloadButton.cs | 2 +- .../TestScenePlaylistsRoomCreation.cs | 18 +++- ...tSceneUpdateableBeatmapBackgroundSprite.cs | 2 +- 26 files changed, 146 insertions(+), 47 deletions(-) create mode 100644 osu.Game.Tests/Beatmaps/IO/BeatmapImportHelper.cs diff --git a/osu.Game.Tests/Beatmaps/IO/BeatmapImportHelper.cs b/osu.Game.Tests/Beatmaps/IO/BeatmapImportHelper.cs new file mode 100644 index 0000000000..8e67ecd818 --- /dev/null +++ b/osu.Game.Tests/Beatmaps/IO/BeatmapImportHelper.cs @@ -0,0 +1,87 @@ +// 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.IO; +using System.Threading; +using System.Threading.Tasks; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Game.Beatmaps; +using osu.Game.Database; +using osu.Game.Tests.Database; +using osu.Game.Tests.Resources; + +namespace osu.Game.Tests.Beatmaps.IO +{ + [TestFixture] + public static class BeatmapImportHelper + { + public static async Task LoadQuickOszIntoOsu(OsuGameBase osu) + { + string temp = TestResources.GetQuickTestBeatmapForImport(); + + var manager = osu.Dependencies.Get(); + + var importedSet = await manager.Import(new ImportTask(temp)).ConfigureAwait(false); + + Debug.Assert(importedSet != null); + + ensureLoaded(osu); + + waitForOrAssert(() => !File.Exists(temp), "Temporary file still exists after standard import", 5000); + + return manager.GetAllUsableBeatmapSets().Find(beatmapSet => beatmapSet.ID == importedSet.ID); + } + + public static async Task LoadOszIntoOsu(OsuGameBase osu, string path = null, bool virtualTrack = false) + { + string temp = path ?? TestResources.GetTestBeatmapForImport(virtualTrack); + + var manager = osu.Dependencies.Get(); + + var importedSet = await manager.Import(new ImportTask(temp)).ConfigureAwait(false); + + Debug.Assert(importedSet != null); + + ensureLoaded(osu); + + waitForOrAssert(() => !File.Exists(temp), "Temporary file still exists after standard import", 5000); + + return manager.GetAllUsableBeatmapSets().Find(beatmapSet => beatmapSet.ID == importedSet.ID); + } + + private static void ensureLoaded(OsuGameBase osu, int timeout = 60000) + { + var realmContextFactory = osu.Dependencies.Get(); + + using (var realm = realmContextFactory.CreateContext()) + BeatmapImporterTests.EnsureLoaded(realm, timeout); + + // TODO: add back some extra checks outside of the realm ones? + // var set = queryBeatmapSets().First(); + // foreach (BeatmapInfo b in set.Beatmaps) + // Assert.IsTrue(set.Beatmaps.Any(c => c.OnlineID == b.OnlineID)); + // Assert.IsTrue(set.Beatmaps.Count > 0); + // var beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 0))?.Beatmap; + // Assert.IsTrue(beatmap?.HitObjects.Any() == true); + // beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 1))?.Beatmap; + // Assert.IsTrue(beatmap?.HitObjects.Any() == true); + // beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 2))?.Beatmap; + // Assert.IsTrue(beatmap?.HitObjects.Any() == true); + // beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 3))?.Beatmap; + // Assert.IsTrue(beatmap?.HitObjects.Any() == true); + } + + private static void waitForOrAssert(Func result, string failureMessage, int timeout = 60000) + { + Task task = Task.Run(() => + { + while (!result()) Thread.Sleep(200); + }); + + Assert.IsTrue(task.Wait(timeout), failureMessage); + } + } +} diff --git a/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs b/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs index 298e474fd1..f3456cf8e4 100644 --- a/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs +++ b/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs @@ -35,7 +35,7 @@ namespace osu.Game.Tests.Beatmaps [BackgroundDependencyLoader] private void load(OsuGameBase osu) { - importedSet = ImportBeatmapTest.LoadQuickOszIntoOsu(osu).GetResultSafely(); + importedSet = BeatmapImportHelper.LoadQuickOszIntoOsu(osu).GetResultSafely(); } [SetUpSteps] diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index e144f863b0..5b63db1972 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -123,7 +123,7 @@ namespace osu.Game.Tests.Database using (var stream = File.OpenRead(tempPath)) { importedSet = await importer.Import(new ImportTask(stream, Path.GetFileName(tempPath))); - ensureLoaded(realmFactory.Context); + EnsureLoaded(realmFactory.Context); } Assert.NotNull(importedSet); @@ -191,7 +191,7 @@ namespace osu.Game.Tests.Database var importedSecondTime = await importer.Import(new ImportTask(temp)); - ensureLoaded(realmFactory.Context); + EnsureLoaded(realmFactory.Context); Assert.NotNull(importedSecondTime); Debug.Assert(importedSecondTime != null); @@ -242,7 +242,7 @@ namespace osu.Game.Tests.Database var importedSecondTime = await importer.Import(new ImportTask(temp)); - ensureLoaded(realmFactory.Context); + EnsureLoaded(realmFactory.Context); // check the newly "imported" beatmap is not the original. Assert.NotNull(importedSecondTime); @@ -291,7 +291,7 @@ namespace osu.Game.Tests.Database var importedSecondTime = await importer.Import(new ImportTask(temp)); - ensureLoaded(realmFactory.Context); + EnsureLoaded(realmFactory.Context); Assert.NotNull(importedSecondTime); Debug.Assert(importedSecondTime != null); @@ -339,7 +339,7 @@ namespace osu.Game.Tests.Database var importedSecondTime = await importer.Import(new ImportTask(temp)); - ensureLoaded(realmFactory.Context); + EnsureLoaded(realmFactory.Context); Assert.NotNull(importedSecondTime); Debug.Assert(importedSecondTime != null); @@ -606,7 +606,7 @@ namespace osu.Game.Tests.Database string? temp = TestResources.GetTestBeatmapForImport(); using (File.OpenRead(temp)) await importer.Import(temp); - ensureLoaded(realmFactory.Context); + EnsureLoaded(realmFactory.Context); File.Delete(temp); Assert.IsFalse(File.Exists(temp), "We likely held a read lock on the file when we shouldn't"); }); @@ -639,7 +639,7 @@ namespace osu.Game.Tests.Database await importer.Import(temp); - ensureLoaded(realmFactory.Context); + EnsureLoaded(realmFactory.Context); } finally { @@ -679,7 +679,7 @@ namespace osu.Game.Tests.Database Assert.NotNull(imported); Debug.Assert(imported != null); - ensureLoaded(realmFactory.Context); + EnsureLoaded(realmFactory.Context); Assert.IsFalse(imported.PerformRead(s => s.Files.Any(f => f.Filename.Contains("subfolder"))), "Files contain common subfolder"); } @@ -729,7 +729,7 @@ namespace osu.Game.Tests.Database Assert.NotNull(imported); Debug.Assert(imported != null); - ensureLoaded(realmFactory.Context); + EnsureLoaded(realmFactory.Context); Assert.IsFalse(imported.PerformRead(s => s.Files.Any(f => f.Filename.Contains("__MACOSX"))), "Files contain resource fork folder, which should be ignored"); Assert.IsFalse(imported.PerformRead(s => s.Files.Any(f => f.Filename.Contains("actual_data"))), "Files contain common subfolder"); @@ -772,7 +772,7 @@ namespace osu.Game.Tests.Database Assert.NotNull(importedSet); - ensureLoaded(realm); + EnsureLoaded(realm); waitForOrAssert(() => !File.Exists(temp), "Temporary file still exists after standard import", 5000); @@ -788,7 +788,7 @@ namespace osu.Game.Tests.Database Assert.NotNull(importedSet); Debug.Assert(importedSet != null); - ensureLoaded(realm); + EnsureLoaded(realm); waitForOrAssert(() => !File.Exists(temp), "Temporary file still exists after standard import", 5000); @@ -849,7 +849,7 @@ namespace osu.Game.Tests.Database Assert.AreEqual(expected, singleReferencedCount); } - private static void ensureLoaded(Realm realm, int timeout = 60000) + internal static void EnsureLoaded(Realm realm, int timeout = 60000) { IQueryable? resultSets = null; diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index 5a2349e776..bdc2317ccd 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -62,7 +62,7 @@ namespace osu.Game.Tests.Online var existing = beatmaps.QueryBeatmapSet(s => s.OnlineID == testBeatmapSet.OnlineID); if (existing != null) - beatmaps.Delete(existing); + beatmaps.Delete(existing.Value); selectedItem.Value = new PlaylistItem { @@ -103,10 +103,10 @@ namespace osu.Game.Tests.Online AddStep("import beatmap", () => beatmaps.Import(testBeatmapFile).WaitSafely()); addAvailabilityCheckStep("state locally available", BeatmapAvailability.LocallyAvailable); - AddStep("delete beatmap", () => beatmaps.Delete(beatmaps.QueryBeatmapSet(b => b.OnlineID == testBeatmapSet.OnlineID))); + AddStep("delete beatmap", () => beatmaps.Delete(beatmaps.QueryBeatmapSet(b => b.OnlineID == testBeatmapSet.OnlineID)!.Value)); addAvailabilityCheckStep("state not downloaded", BeatmapAvailability.NotDownloaded); - AddStep("undelete beatmap", () => beatmaps.Undelete(beatmaps.QueryBeatmapSet(b => b.OnlineID == testBeatmapSet.OnlineID))); + AddStep("undelete beatmap", () => beatmaps.Undelete(beatmaps.QueryBeatmapSet(b => b.OnlineID == testBeatmapSet.OnlineID)!.Value)); addAvailabilityCheckStep("state locally available", BeatmapAvailability.LocallyAvailable); } diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs index f00fb97dfa..3e6a877a35 100644 --- a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs +++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs @@ -141,7 +141,7 @@ namespace osu.Game.Tests.Scores.IO var beatmapManager = osu.Dependencies.Get(); var scoreManager = osu.Dependencies.Get(); - beatmapManager.Delete(beatmapManager.QueryBeatmapSet(s => s.Beatmaps.Any(b => b.ID == imported.BeatmapInfo.ID))); + beatmapManager.Delete(beatmapManager.QueryBeatmapSet(s => s.Beatmaps.Any(b => b.ID == imported.BeatmapInfo.ID))!.Value); Assert.That(scoreManager.Query(s => s.Equals(imported)).Value.DeletePending, Is.EqualTo(true)); var secondImport = await LoadScoreIntoOsu(osu, imported); diff --git a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs index 7b5e1f4ec7..94b693363a 100644 --- a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs +++ b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs @@ -204,7 +204,7 @@ namespace osu.Game.Tests.Visual.Beatmaps { var beatmap = beatmaps.QueryBeatmapSet(b => b.OnlineID == online_id); - if (beatmap != null) beatmaps.Delete(beatmap); + if (beatmap != null) beatmaps.Delete(beatmap.Value); }); } diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs b/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs index 516305079b..243bb71e26 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs @@ -33,7 +33,7 @@ namespace osu.Game.Tests.Visual.Editing public override void SetUpSteps() { - AddStep("import test beatmap", () => importedBeatmapSet = ImportBeatmapTest.LoadOszIntoOsu(game, virtualTrack: true).GetResultSafely()); + AddStep("import test beatmap", () => importedBeatmapSet = BeatmapImportHelper.LoadOszIntoOsu(game, virtualTrack: true).GetResultSafely()); base.SetUpSteps(); } diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index db20d3c7ba..4cc6e2ca18 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -50,7 +50,7 @@ namespace osu.Game.Tests.Visual.Editing { AddStep("save beatmap", () => Editor.Save()); AddAssert("new beatmap persisted", () => EditorBeatmap.BeatmapInfo.IsManaged); - AddAssert("new beatmap in database", () => beatmapManager.QueryBeatmapSet(s => s.ID == EditorBeatmap.BeatmapInfo.BeatmapSet.ID)?.DeletePending == false); + AddAssert("new beatmap in database", () => beatmapManager.QueryBeatmapSet(s => s.ID == EditorBeatmap.BeatmapInfo.BeatmapSet.ID)?.Value.DeletePending == false); } [Test] @@ -66,7 +66,7 @@ namespace osu.Game.Tests.Visual.Editing }); AddUntilStep("wait for exit", () => !Editor.IsCurrentScreen()); - AddAssert("new beatmap not persisted", () => beatmapManager.QueryBeatmapSet(s => s.ID == editorBeatmap.BeatmapInfo.BeatmapSet.ID)?.DeletePending == true); + AddAssert("new beatmap not persisted", () => beatmapManager.QueryBeatmapSet(s => s.ID == editorBeatmap.BeatmapInfo.BeatmapSet.ID)?.Value.DeletePending == true); } [Test] diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs index 6d48ef3ba7..bb630e5d5c 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs @@ -39,7 +39,7 @@ namespace osu.Game.Tests.Visual.Editing public override void SetUpSteps() { - AddStep("import test beatmap", () => importedBeatmapSet = ImportBeatmapTest.LoadOszIntoOsu(game).GetResultSafely()); + AddStep("import test beatmap", () => importedBeatmapSet = BeatmapImportHelper.LoadOszIntoOsu(game).GetResultSafely()); base.SetUpSteps(); } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index d8964c531f..8b7e1a1d85 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -61,7 +61,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("import beatmap", () => { - importedBeatmap = ImportBeatmapTest.LoadOszIntoOsu(game, virtualTrack: true).GetResultSafely(); + importedBeatmap = BeatmapImportHelper.LoadOszIntoOsu(game, virtualTrack: true).GetResultSafely(); importedBeatmapId = importedBeatmap.Beatmaps.First(b => b.RulesetID == 0).OnlineID; }); } diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs index a993190a67..dc826701d2 100644 --- a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs +++ b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs @@ -58,7 +58,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("import beatmap", () => { beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); - importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable().First(); + importedSet = beatmaps.GetAllUsableBeatmapSets().First(); InitialBeatmap = importedSet.Beatmaps.First(b => b.RulesetID == 0); OtherBeatmap = importedSet.Beatmaps.Last(b => b.RulesetID == 0); }); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index 1607750d15..d87a0a4a1c 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -154,16 +154,18 @@ namespace osu.Game.Tests.Visual.Multiplayer { var beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo; + Debug.Assert(beatmap.BeatmapSet != null); + AddStep("import beatmap", () => manager.Import(beatmap.BeatmapSet).WaitSafely()); createPlaylistWithBeatmaps(beatmap); assertDownloadButtonVisible(false); - AddStep("delete beatmap set", () => manager.Delete(manager.QueryBeatmapSets(_ => true).Single())); + AddStep("delete beatmap set", () => manager.Delete(manager.QueryBeatmapSets(_ => true).Single().Value)); assertDownloadButtonVisible(true); - AddStep("undelete beatmap set", () => manager.Undelete(manager.QueryBeatmapSets(_ => true).Single())); + AddStep("undelete beatmap set", () => manager.Undelete(manager.QueryBeatmapSets(_ => true).Single().Value)); assertDownloadButtonVisible(false); void assertDownloadButtonVisible(bool visible) => AddUntilStep($"download button {(visible ? "shown" : "hidden")}", diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index c78d1a2f62..9d67742e4d 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -47,7 +47,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load() { - importedSet = ImportBeatmapTest.LoadOszIntoOsu(game, virtualTrack: true).GetResultSafely(); + importedSet = BeatmapImportHelper.LoadOszIntoOsu(game, virtualTrack: true).GetResultSafely(); importedBeatmap = importedSet.Beatmaps.First(b => b.RulesetID == 0); importedBeatmapId = importedBeatmap.OnlineID; } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 18f45086f6..471816edf8 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -72,7 +72,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("import beatmap", () => { beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); - importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable().First(); + importedSet = beatmaps.GetAllUsableBeatmapSets().First(); }); AddStep("load multiplayer", () => LoadScreen(multiplayerComponents = new TestMultiplayerComponents())); @@ -588,7 +588,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("restore beatmap", () => { beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); - importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable().First(); + importedSet = beatmaps.GetAllUsableBeatmapSets().First(); }); AddUntilStep("play started", () => multiplayerComponents.CurrentScreen is SpectatorScreen); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index 6b6a02906e..98b14fbddb 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); - importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable().First(); + importedSet = beatmaps.GetAllUsableBeatmapSets().First(); } [SetUp] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs index 251f407a85..813b62176b 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs @@ -55,7 +55,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("import beatmap", () => { beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); - importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable().First(); + importedSet = beatmaps.GetAllUsableBeatmapSets().First(); importedBeatmap = importedSet.Beatmaps.First(b => b.RulesetID == 0); }); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs index ad47745642..b5d31c7ae9 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs @@ -39,7 +39,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private void load(GameHost host, AudioManager audio) { Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, API, audio, Resources, host, Beatmap.Default)); } public override void SetUpSteps() @@ -60,7 +60,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("import beatmap", () => { beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); - importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable().First(); + importedSet = beatmaps.GetAllUsableBeatmapSets().First(); importedBeatmap = importedSet.Beatmaps.First(b => b.RulesetID == 0); }); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs index 59e5318263..0b2ab1aef3 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs @@ -50,7 +50,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { AvailabilityTracker.SelectedItem.BindTo(selectedItem); - importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable().First(); + importedSet = beatmaps.GetAllUsableBeatmapSets().First(); Beatmap.Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()); selectedItem.Value = new PlaylistItem { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs index 5466a19657..6b1001f6b7 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs @@ -51,7 +51,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { AvailabilityTracker.SelectedItem.BindTo(selectedItem); - importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable().First(); + importedSet = beatmaps.GetAllUsableBeatmapSets().First(); Beatmap.Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()); selectedItem.Value = new PlaylistItem { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs index 2140acb2e3..97d17ada71 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs @@ -53,7 +53,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("import beatmap", () => { beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); - importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable().First(); + importedSet = beatmaps.GetAllUsableBeatmapSets().First(); }); AddStep("load multiplayer", () => LoadScreen(multiplayerComponents = new TestMultiplayerComponents())); diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneMouseWheelVolumeAdjust.cs b/osu.Game.Tests/Visual/Navigation/TestSceneMouseWheelVolumeAdjust.cs index 701ab480f6..22a00a3e5a 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneMouseWheelVolumeAdjust.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneMouseWheelVolumeAdjust.cs @@ -83,7 +83,7 @@ namespace osu.Game.Tests.Visual.Navigation PushAndConfirm(() => songSelect = new TestSceneScreenNavigation.TestPlaySongSelect()); AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded); - AddStep("import beatmap", () => ImportBeatmapTest.LoadOszIntoOsu(Game, virtualTrack: true).WaitSafely()); + AddStep("import beatmap", () => BeatmapImportHelper.LoadOszIntoOsu(Game, virtualTrack: true).WaitSafely()); AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); AddStep("press enter", () => InputManager.Key(Key.Enter)); diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs b/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs index 24f5808961..1ebceed15d 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs @@ -173,7 +173,7 @@ namespace osu.Game.Tests.Visual.Navigation private void importAndWaitForSongSelect() { - AddStep("import beatmap", () => ImportBeatmapTest.LoadQuickOszIntoOsu(Game).WaitSafely()); + AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely()); PushAndConfirm(() => new TestPlaySongSelect()); AddUntilStep("beatmap updated", () => Game.Beatmap.Value.BeatmapSetInfo.OnlineID == 241526); } diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index d094d8b688..0349961f75 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -71,7 +71,7 @@ namespace osu.Game.Tests.Visual.Navigation PushAndConfirm(() => songSelect = new TestPlaySongSelect()); AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded); - AddStep("import beatmap", () => ImportBeatmapTest.LoadQuickOszIntoOsu(Game).WaitSafely()); + AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely()); AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); @@ -105,7 +105,7 @@ namespace osu.Game.Tests.Visual.Navigation PushAndConfirm(() => songSelect = new TestPlaySongSelect()); AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded); - AddStep("import beatmap", () => ImportBeatmapTest.LoadQuickOszIntoOsu(Game).WaitSafely()); + AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely()); AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); @@ -139,7 +139,7 @@ namespace osu.Game.Tests.Visual.Navigation PushAndConfirm(() => songSelect = new TestPlaySongSelect()); AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded); - AddStep("import beatmap", () => ImportBeatmapTest.LoadOszIntoOsu(Game, virtualTrack: true).WaitSafely()); + AddStep("import beatmap", () => BeatmapImportHelper.LoadOszIntoOsu(Game, virtualTrack: true).WaitSafely()); AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapDownloadButton.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapDownloadButton.cs index 21bf8d1c5a..d9f01622da 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapDownloadButton.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapDownloadButton.cs @@ -59,7 +59,7 @@ namespace osu.Game.Tests.Visual.Online { var beatmap = beatmaps.QueryBeatmapSet(b => b.OnlineID == 241526); - if (beatmap != null) beatmaps.Delete(beatmap); + if (beatmap != null) beatmaps.Delete(beatmap.Value); }); } diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs index e59884f4f4..908e93dee2 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.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 NUnit.Framework; using osu.Framework.Allocation; @@ -39,7 +40,7 @@ namespace osu.Game.Tests.Visual.Playlists private void load(GameHost host, AudioManager audio) { Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); - Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, API, audio, Resources, host, Beatmap.Default)); } [SetUpSteps] @@ -122,8 +123,8 @@ namespace osu.Game.Tests.Visual.Playlists AddStep("store real beatmap values", () => { realHash = importedBeatmap.Value.Beatmaps[0].MD5Hash; - realOnlineId = importedBeatmap.Value.Beatmaps[0].OnlineID ?? -1; - realOnlineSetId = importedBeatmap.Value.OnlineID ?? -1; + realOnlineId = importedBeatmap.Value.Beatmaps[0].OnlineID; + realOnlineSetId = importedBeatmap.Value.OnlineID; }); AddStep("import modified beatmap", () => @@ -185,6 +186,8 @@ namespace osu.Game.Tests.Visual.Playlists }, }; + Debug.Assert(originalBeatmap.BeatmapInfo.BeatmapSet != null); + manager.Import(originalBeatmap.BeatmapInfo.BeatmapSet).WaitSafely(); }); @@ -202,7 +205,14 @@ namespace osu.Game.Tests.Visual.Playlists }); } - private void importBeatmap() => AddStep("import beatmap", () => importedBeatmap = manager.Import(CreateBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo.BeatmapSet).GetResultSafely()); + private void importBeatmap() => AddStep("import beatmap", () => + { + var beatmap = CreateBeatmap(new OsuRuleset().RulesetInfo); + + Debug.Assert(beatmap.BeatmapInfo.BeatmapSet != null); + + importedBeatmap = manager.Import(beatmap.BeatmapInfo.BeatmapSet).GetResultSafely(); + }); private class TestPlaylistsRoomSubScreen : PlaylistsRoomSubScreen { diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs index 6fe1ccc037..4f22281818 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs @@ -32,7 +32,7 @@ namespace osu.Game.Tests.Visual.UserInterface { this.api = api; - testBeatmap = ImportBeatmapTest.LoadOszIntoOsu(osu).GetResultSafely(); + testBeatmap = BeatmapImportHelper.LoadOszIntoOsu(osu).GetResultSafely(); } [Test] From 1f9318265e6a7ee5a89b356d8a0e1418463ca066 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 17 Dec 2021 18:45:22 +0900 Subject: [PATCH 211/996] Update `ToLive` usages in line with recent changes --- osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs | 6 +++--- osu.Game/Beatmaps/BeatmapManager.cs | 4 ++-- osu.Game/Scoring/ScoreManager.cs | 2 +- osu.Game/Screens/Menu/IntroScreen.cs | 8 ++++---- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 471816edf8..d55ca0b578 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -62,7 +62,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private void load(GameHost host, AudioManager audio) { Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, API, audio, Resources, host, Beatmap.Default)); } public override void SetUpSteps() @@ -827,7 +827,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("join other user", () => client.AddUser(new APIUser { Id = 1234 })); AddStep("add item as other user", () => client.AddUserPlaylistItem(1234, new MultiplayerPlaylistItem(new PlaylistItem { - BeatmapID = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo.OnlineID ?? -1 + BeatmapID = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo.OnlineID }))); AddUntilStep("item arrived in playlist", () => client.Room?.Playlist.Count == 2); @@ -858,7 +858,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("join other user", () => client.AddUser(new APIUser { Id = 1234 })); AddStep("add item as other user", () => client.AddUserPlaylistItem(1234, new MultiplayerPlaylistItem(new PlaylistItem { - BeatmapID = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo.OnlineID ?? -1 + BeatmapID = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo.OnlineID }))); AddUntilStep("item arrived in playlist", () => client.Room?.Playlist.Count == 2); diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 706160bfb5..462980448a 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -166,7 +166,7 @@ namespace osu.Game.Beatmaps return context.All() .Where(b => !b.DeletePending) .Where(query) - .ToLive(); + .ToLive(contextFactory); } } @@ -178,7 +178,7 @@ namespace osu.Game.Beatmaps public ILive? QueryBeatmapSet(Expression> query) { using (var context = contextFactory.CreateContext()) - return context.All().FirstOrDefault(query)?.ToLive(); + return context.All().FirstOrDefault(query)?.ToLive(contextFactory); } /// diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 0a1b616422..c2a9675474 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -52,7 +52,7 @@ namespace osu.Game.Scoring public ILive Query(Expression> query) { using (var context = contextFactory.CreateContext()) - return context.All().FirstOrDefault(query)?.ToLive(); + return context.All().FirstOrDefault(query)?.ToLive(contextFactory); } /// diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index bb8ddb8cd6..f54228b441 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -82,9 +82,9 @@ namespace osu.Game.Screens.Menu } [BackgroundDependencyLoader] - private void load(OsuConfigManager config, SkinManager skinManager, BeatmapManager beatmaps, Framework.Game game) + private void load(OsuConfigManager config, SkinManager skinManager, BeatmapManager beatmaps, Framework.Game game, RealmContextFactory realmContextFactory) { - // prevent user from changing beatmap while the intro is still runnning. + // prevent user from changing beatmap while the intro is still running. beatmap = Beatmap.BeginLease(false); MenuVoice = config.GetBindable(OsuSetting.MenuVoice); @@ -101,7 +101,7 @@ namespace osu.Game.Screens.Menu if (sets.Count > 0) { setInfo = beatmaps.QueryBeatmapSet(s => s.ID == sets[RNG.Next(0, sets.Count - 1)].ID); - initialBeatmap = beatmaps.GetWorkingBeatmap(setInfo?.PerformRead(s => s.Beatmaps[0].ToLive())); + initialBeatmap = beatmaps.GetWorkingBeatmap(setInfo?.PerformRead(s => s.Beatmaps[0].ToLive(realmContextFactory))); } } @@ -131,7 +131,7 @@ namespace osu.Game.Screens.Menu if (setInfo == null) return false; - initialBeatmap = beatmaps.GetWorkingBeatmap(setInfo.PerformRead(s => s.Beatmaps[0].ToLive())); + initialBeatmap = beatmaps.GetWorkingBeatmap(setInfo.PerformRead(s => s.Beatmaps[0].ToLive(realmContextFactory))); return UsingThemedIntro = initialBeatmap != null; } From 5dc497e949cf813f57bc35f4ad8bfc8d488fb6a9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 17 Dec 2021 18:37:28 +0900 Subject: [PATCH 212/996] Replace `BeatmapLeaderboard` event flow with realm subscriptions --- .../Select/Leaderboards/BeatmapLeaderboard.cs | 38 +++++++++++++++---- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index e47acc5ba7..869036ef68 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -44,6 +44,8 @@ namespace osu.Game.Screens.Select.Leaderboards Scores = null; UpdateScores(); + if (IsLoaded) + refreshRealmSubscription(); } } @@ -78,6 +80,9 @@ namespace osu.Game.Screens.Select.Leaderboards [Resolved] private IAPIProvider api { get; set; } + [Resolved] + private RealmContextFactory realmContextFactory { get; set; } + [BackgroundDependencyLoader] private void load() { @@ -87,9 +92,32 @@ namespace osu.Game.Screens.Select.Leaderboards if (filterMods) UpdateScores(); }; + } - scoreManager.ItemRemoved += scoreStoreChanged; - scoreManager.ItemUpdated += scoreStoreChanged; + protected override void LoadComplete() + { + base.LoadComplete(); + + refreshRealmSubscription(); + } + + private IDisposable scoreSubscription; + + private void refreshRealmSubscription() + { + scoreSubscription?.Dispose(); + scoreSubscription = null; + + if (beatmapInfo == null) + return; + + scoreSubscription = realmContextFactory.Context.All().Where(s => s.BeatmapInfo.ID == beatmapInfo.ID).QueryAsyncWithNotifications((_, changes, ___) => + { + if (changes == null) + return; + + RefreshScores(); + }); } protected override void Reset() @@ -212,11 +240,7 @@ namespace osu.Game.Screens.Select.Leaderboards { base.Dispose(isDisposing); - if (scoreManager != null) - { - scoreManager.ItemRemoved -= scoreStoreChanged; - scoreManager.ItemUpdated -= scoreStoreChanged; - } + scoreSubscription?.Dispose(); } } } From 00e9f0d41ee90143a107b397a6fcd9c328ed2383 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 17 Dec 2021 18:53:46 +0900 Subject: [PATCH 213/996] Replace `BeatmapDownloadTracker` event flow with realm subscriptions --- osu.Game/Online/BeatmapDownloadTracker.cs | 62 ++++++++++----------- osu.Game/Stores/RealmArchiveModelManager.cs | 1 + 2 files changed, 30 insertions(+), 33 deletions(-) diff --git a/osu.Game/Online/BeatmapDownloadTracker.cs b/osu.Game/Online/BeatmapDownloadTracker.cs index 509d5c1b71..7a396ac7aa 100644 --- a/osu.Game/Online/BeatmapDownloadTracker.cs +++ b/osu.Game/Online/BeatmapDownloadTracker.cs @@ -2,8 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using osu.Framework.Allocation; using osu.Game.Beatmaps; +using osu.Game.Database; using osu.Game.Online.API; #nullable enable @@ -12,37 +14,47 @@ namespace osu.Game.Online { public class BeatmapDownloadTracker : DownloadTracker { - [Resolved(CanBeNull = true)] - protected BeatmapManager? Manager { get; private set; } - [Resolved(CanBeNull = true)] protected BeatmapModelDownloader? Downloader { get; private set; } private ArchiveDownloadRequest? attachedRequest; + private IDisposable? realmSubscription; + + [Resolved] + private RealmContextFactory realmContextFactory { get; set; } = null!; + public BeatmapDownloadTracker(IBeatmapSetInfo trackedItem) : base(trackedItem) { } - [BackgroundDependencyLoader(true)] - private void load() + protected override void LoadComplete() { - if (Manager == null || Downloader == null) + base.LoadComplete(); + + if (Downloader == null) return; + Downloader.DownloadBegan += downloadBegan; + Downloader.DownloadFailed += downloadFailed; + // Used to interact with manager classes that don't support interface types. Will eventually be replaced. var beatmapSetInfo = new BeatmapSetInfo { OnlineID = TrackedItem.OnlineID }; - if (Manager.IsAvailableLocally(beatmapSetInfo)) - UpdateState(DownloadState.LocallyAvailable); - else - attachDownload(Downloader.GetExistingDownload(beatmapSetInfo)); - - Downloader.DownloadBegan += downloadBegan; - Downloader.DownloadFailed += downloadFailed; - Manager.ItemUpdated += itemUpdated; - Manager.ItemRemoved += itemRemoved; + realmSubscription = realmContextFactory.Context.All().Where(s => s.OnlineID == TrackedItem.OnlineID).QueryAsyncWithNotifications((items, changes, ___) => + { + if (items.Any()) + Schedule(() => UpdateState(DownloadState.LocallyAvailable)); + else + { + Schedule(() => + { + UpdateState(DownloadState.NotDownloaded); + attachDownload(Downloader.GetExistingDownload(beatmapSetInfo)); + }); + } + }); } private void downloadBegan(ArchiveDownloadRequest request) => Schedule(() => @@ -97,18 +109,6 @@ namespace osu.Game.Online private void onRequestFailure(Exception e) => Schedule(() => attachDownload(null)); - private void itemUpdated(BeatmapSetInfo item) => Schedule(() => - { - if (checkEquality(item, TrackedItem)) - UpdateState(DownloadState.LocallyAvailable); - }); - - private void itemRemoved(BeatmapSetInfo item) => Schedule(() => - { - if (checkEquality(item, TrackedItem)) - UpdateState(DownloadState.NotDownloaded); - }); - private bool checkEquality(IBeatmapSetInfo x, IBeatmapSetInfo y) => x.OnlineID == y.OnlineID; #region Disposal @@ -118,17 +118,13 @@ namespace osu.Game.Online base.Dispose(isDisposing); attachDownload(null); + realmSubscription?.Dispose(); + if (Downloader != null) { Downloader.DownloadBegan -= downloadBegan; Downloader.DownloadFailed -= downloadFailed; } - - if (Manager != null) - { - Manager.ItemUpdated -= itemUpdated; - Manager.ItemRemoved -= itemRemoved; - } } #endregion diff --git a/osu.Game/Stores/RealmArchiveModelManager.cs b/osu.Game/Stores/RealmArchiveModelManager.cs index 7fa8d16882..d6bb01cedf 100644 --- a/osu.Game/Stores/RealmArchiveModelManager.cs +++ b/osu.Game/Stores/RealmArchiveModelManager.cs @@ -187,6 +187,7 @@ namespace osu.Game.Stores item.Realm.Write(r => item.DeletePending = false); } + // TODO: delete or abstract public virtual bool IsAvailableLocally(TModel model) => false; // Not relevant for skins since they can't be downloaded yet. public void Update(TModel skin) From 8c4836e87d68cfeef0b2b51527e7364039b6f01a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 17 Dec 2021 18:58:21 +0900 Subject: [PATCH 214/996] Replace `ScoreDownloadTracker` event flow with realm subscriptions --- osu.Game/Online/ScoreDownloadTracker.cs | 58 ++++++++++++------------- 1 file changed, 27 insertions(+), 31 deletions(-) diff --git a/osu.Game/Online/ScoreDownloadTracker.cs b/osu.Game/Online/ScoreDownloadTracker.cs index 68932cc388..946a751c31 100644 --- a/osu.Game/Online/ScoreDownloadTracker.cs +++ b/osu.Game/Online/ScoreDownloadTracker.cs @@ -2,7 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using osu.Framework.Allocation; +using osu.Game.Database; using osu.Game.Extensions; using osu.Game.Online.API; using osu.Game.Scoring; @@ -13,23 +15,26 @@ namespace osu.Game.Online { public class ScoreDownloadTracker : DownloadTracker { - [Resolved(CanBeNull = true)] - protected ScoreManager? Manager { get; private set; } - [Resolved(CanBeNull = true)] protected ScoreModelDownloader? Downloader { get; private set; } private ArchiveDownloadRequest? attachedRequest; + private IDisposable? realmSubscription; + + [Resolved] + private RealmContextFactory realmContextFactory { get; set; } = null!; + public ScoreDownloadTracker(ScoreInfo trackedItem) : base(trackedItem) { } - [BackgroundDependencyLoader(true)] - private void load() + protected override void LoadComplete() { - if (Manager == null || Downloader == null) + base.LoadComplete(); + + if (Downloader == null) return; // Used to interact with manager classes that don't support interface types. Will eventually be replaced. @@ -39,15 +44,22 @@ namespace osu.Game.Online OnlineID = TrackedItem.OnlineID }; - if (Manager.IsAvailableLocally(scoreInfo)) - UpdateState(DownloadState.LocallyAvailable); - else - attachDownload(Downloader.GetExistingDownload(scoreInfo)); - Downloader.DownloadBegan += downloadBegan; Downloader.DownloadFailed += downloadFailed; - Manager.ItemUpdated += itemUpdated; - Manager.ItemRemoved += itemRemoved; + + realmSubscription = realmContextFactory.Context.All().Where(s => s.OnlineID == TrackedItem.OnlineID).QueryAsyncWithNotifications((items, changes, ___) => + { + if (items.Any()) + Schedule(() => UpdateState(DownloadState.LocallyAvailable)); + else + { + Schedule(() => + { + UpdateState(DownloadState.NotDownloaded); + attachDownload(Downloader.GetExistingDownload(scoreInfo)); + }); + } + }); } private void downloadBegan(ArchiveDownloadRequest request) => Schedule(() => @@ -102,18 +114,6 @@ namespace osu.Game.Online private void onRequestFailure(Exception e) => Schedule(() => attachDownload(null)); - private void itemUpdated(ScoreInfo item) => Schedule(() => - { - if (checkEquality(item, TrackedItem)) - UpdateState(DownloadState.LocallyAvailable); - }); - - private void itemRemoved(ScoreInfo item) => Schedule(() => - { - if (checkEquality(item, TrackedItem)) - UpdateState(DownloadState.NotDownloaded); - }); - private bool checkEquality(IScoreInfo x, IScoreInfo y) => x.MatchesOnlineID(y); #region Disposal @@ -123,17 +123,13 @@ namespace osu.Game.Online base.Dispose(isDisposing); attachDownload(null); + realmSubscription?.Dispose(); + if (Downloader != null) { Downloader.DownloadBegan -= downloadBegan; Downloader.DownloadFailed -= downloadFailed; } - - if (Manager != null) - { - Manager.ItemUpdated -= itemUpdated; - Manager.ItemRemoved -= itemRemoved; - } } #endregion From c9257e9ecc34e484bebe1a70ec663268f729a915 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 17 Dec 2021 19:01:19 +0900 Subject: [PATCH 215/996] Fix missing disposal of realm subscriptions in `BeatmapCarousel` --- osu.Game/Screens/Select/BeatmapCarousel.cs | 23 +++++++++------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 94e7dc9241..78e416c64b 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -144,9 +144,13 @@ namespace osu.Game.Screens.Select private CarouselRoot root; + private IDisposable subscriptionSets; + private IDisposable subscriptionBeatmaps; + private readonly DrawablePool setPool = new DrawablePool(100); public BeatmapCarousel() + { root = new CarouselRoot(this); InternalChild = new OsuContextMenuContainer @@ -163,10 +167,7 @@ namespace osu.Game.Screens.Select }; } - [Resolved] - private BeatmapManager beatmaps { get; set; } - - [BackgroundDependencyLoader(permitNulls: true)] + [BackgroundDependencyLoader] private void load(OsuConfigManager config) { config.BindWith(OsuSetting.RandomSelectAlgorithm, RandomAlgorithm); @@ -183,13 +184,10 @@ namespace osu.Game.Screens.Select { base.LoadComplete(); - realmFactory.Context.All().Where(s => !s.DeletePending).QueryAsyncWithNotifications(beatmapSetsChanged); - realmFactory.Context.All().Where(b => !b.Hidden).QueryAsyncWithNotifications(beatmapsChanged); + subscriptionSets = realmFactory.Context.All().Where(s => !s.DeletePending).QueryAsyncWithNotifications(beatmapSetsChanged); + subscriptionBeatmaps = realmFactory.Context.All().Where(b => !b.Hidden).QueryAsyncWithNotifications(beatmapsChanged); } - private void beatmapRemoved(BeatmapSetInfo item) => RemoveBeatmapSet(item); - private void beatmapUpdated(BeatmapSetInfo item) => UpdateBeatmapSet(item); - private void beatmapSetsChanged(IRealmCollection sender, ChangeSet changes, Exception error) { if (changes == null) @@ -921,11 +919,8 @@ namespace osu.Game.Screens.Select { base.Dispose(isDisposing); - if (beatmaps != null) - { - beatmaps.ItemUpdated -= beatmapUpdated; - beatmaps.ItemRemoved -= beatmapRemoved; - } + subscriptionSets?.Dispose(); + subscriptionBeatmaps?.Dispose(); } } } From fe8a5e867d2cbaa19e95516c61084508ff57af2c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 17 Dec 2021 19:03:02 +0900 Subject: [PATCH 216/996] Remove updated/removed flow method mapping --- osu.Game/Beatmaps/BeatmapManager.cs | 4 ---- osu.Game/Database/IModelManager.cs | 11 ----------- osu.Game/Scoring/ScoreManager.cs | 12 ------------ osu.Game/Stores/RealmArchiveModelManager.cs | 16 ---------------- 4 files changed, 43 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 462980448a..d9484a2880 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -232,10 +232,6 @@ namespace osu.Game.Beatmaps return beatmapModelManager.IsAvailableLocally(model); } - public event Action? ItemUpdated; - - public event Action? ItemRemoved; - public void Update(BeatmapSetInfo item) { beatmapModelManager.Update(item); diff --git a/osu.Game/Database/IModelManager.cs b/osu.Game/Database/IModelManager.cs index 779d0522f7..e7218b621c 100644 --- a/osu.Game/Database/IModelManager.cs +++ b/osu.Game/Database/IModelManager.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Collections.Generic; namespace osu.Game.Database @@ -13,16 +12,6 @@ namespace osu.Game.Database public interface IModelManager where TModel : class { - /// - /// Fired when an item is updated. - /// - event Action ItemUpdated; - - /// - /// Fired when an item is removed. - /// - event Action ItemRemoved; - /// /// Perform an update of the specified item. /// TODO: Support file additions/removals. diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index c2a9675474..159c3c2da0 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -251,18 +251,6 @@ namespace osu.Game.Scoring #region Implementation of IModelManager - public event Action ItemUpdated - { - add => scoreModelManager.ItemUpdated += value; - remove => scoreModelManager.ItemUpdated -= value; - } - - public event Action ItemRemoved - { - add => scoreModelManager.ItemRemoved += value; - remove => scoreModelManager.ItemRemoved -= value; - } - public void Update(ScoreInfo item) { scoreModelManager.Update(item); diff --git a/osu.Game/Stores/RealmArchiveModelManager.cs b/osu.Game/Stores/RealmArchiveModelManager.cs index d6bb01cedf..8698799b4a 100644 --- a/osu.Game/Stores/RealmArchiveModelManager.cs +++ b/osu.Game/Stores/RealmArchiveModelManager.cs @@ -24,22 +24,6 @@ namespace osu.Game.Stores public abstract class RealmArchiveModelManager : RealmArchiveModelImporter, IModelManager, IModelFileManager where TModel : RealmObject, IHasRealmFiles, IHasGuidPrimaryKey, ISoftDelete { - public event Action? ItemUpdated - { - // This may be brought back for beatmaps to ease integration. - // The eventual goal would be not requiring this and using realm subscriptions in its place. - add => throw new NotImplementedException(); - remove => throw new NotImplementedException(); - } - - public event Action? ItemRemoved - { - // This may be brought back for beatmaps to ease integration. - // The eventual goal would be not requiring this and using realm subscriptions in its place. - add => throw new NotImplementedException(); - remove => throw new NotImplementedException(); - } - private readonly RealmFileStore realmFileStore; protected RealmArchiveModelManager(Storage storage, RealmContextFactory contextFactory) From 6d60aa7d9cc45799c40c72f119db2c0936aa606f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 17 Dec 2021 19:09:35 +0900 Subject: [PATCH 217/996] Replace `TopLocalRank` event flow with realm subscriptions --- .../Screens/Select/Carousel/TopLocalRank.cs | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs index a83cb08e1f..2ade213e8b 100644 --- a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs +++ b/osu.Game/Screens/Select/Carousel/TopLocalRank.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 osu.Framework.Allocation; using osu.Framework.Bindables; @@ -19,9 +20,6 @@ namespace osu.Game.Screens.Select.Carousel { private readonly BeatmapInfo beatmapInfo; - [Resolved] - private ScoreManager scores { get; set; } - [Resolved] private IBindable ruleset { get; set; } @@ -40,26 +38,34 @@ namespace osu.Game.Screens.Select.Carousel [BackgroundDependencyLoader] private void load() { - scores.ItemUpdated += scoreChanged; - scores.ItemRemoved += scoreChanged; - ruleset.ValueChanged += _ => fetchAndLoadTopScore(); fetchAndLoadTopScore(); } - private void scoreChanged(ScoreInfo score) + protected override void LoadComplete() { - if (score.BeatmapInfoID == beatmapInfo.ID) - fetchAndLoadTopScore(); + base.LoadComplete(); + + scoreSubscription = realmFactory.Context.All().Where(s => s.BeatmapInfo.ID == beatmapInfo.ID).QueryAsyncWithNotifications((_, changes, ___) => + { + if (changes == null) + return; + + fetchTopScoreRank(); + }); } + private IDisposable scoreSubscription; + private ScheduledDelegate scheduledRankUpdate; private void fetchAndLoadTopScore() { + // TODO: this lookup likely isn't required, we can use the results of the subscription directly. var rank = fetchTopScoreRank(); - scheduledRankUpdate = Schedule(() => + + scheduledRankUpdate = Scheduler.Add(() => { Rank = rank; @@ -89,11 +95,7 @@ namespace osu.Game.Screens.Select.Carousel { base.Dispose(isDisposing); - if (scores != null) - { - scores.ItemUpdated -= scoreChanged; - scores.ItemRemoved -= scoreChanged; - } + scoreSubscription?.Dispose(); } } } From 5c0d31ed2444ed5785584691a07e38caab2a931a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 17 Dec 2021 19:21:41 +0900 Subject: [PATCH 218/996] Replace `OnlinePlayBeatmapAvailabilityTracker` event flow with realm subscriptions --- .../OnlinePlayBeatmapAvailabilityTracker.cs | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs index a32f069470..01999ba766 100644 --- a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs @@ -3,6 +3,7 @@ using System; using System.Diagnostics; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -10,6 +11,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Logging; using osu.Framework.Threading; using osu.Game.Beatmaps; +using osu.Game.Database; namespace osu.Game.Online.Rooms { @@ -29,6 +31,9 @@ namespace osu.Game.Online.Rooms [Resolved] private BeatmapManager beatmapManager { get; set; } + [Resolved] + private RealmContextFactory realmContextFactory { get; set; } = null!; + /// /// The availability state of the currently selected playlist item. /// @@ -45,6 +50,8 @@ namespace osu.Game.Online.Rooms /// private BeatmapInfo matchingHash; + private IDisposable realmSubscription; + protected override void LoadComplete() { base.LoadComplete(); @@ -75,27 +82,24 @@ namespace osu.Game.Online.Rooms if (progressUpdate?.Completed != false) progressUpdate = Scheduler.AddDelayed(updateAvailability, progressUpdate == null ? 0 : 500); }, true); + + // These events are needed for a fringe case where a modified/altered beatmap is imported with matching OnlineIDs. + // During the import process this will cause the existing beatmap set to be silently deleted and replaced with the new one. + // This is not exposed to us via `BeatmapDownloadTracker` so we have to take it into our own hands (as we care about the hash matching). + realmSubscription?.Dispose(); + realmSubscription = realmContextFactory.Context + .All() + .Where(s => s.OnlineID == SelectedItem.Value.BeatmapID || (matchingHash != null && s.ID == matchingHash.ID)) + .QueryAsyncWithNotifications((items, changes, ___) => + { + if (changes == null) + return; + + Schedule(updateAvailability); + }); }, true); - - // These events are needed for a fringe case where a modified/altered beatmap is imported with matching OnlineIDs. - // During the import process this will cause the existing beatmap set to be silently deleted and replaced with the new one. - // This is not exposed to us via `BeatmapDownloadTracker` so we have to take it into our own hands (as we care about the hash matching). - beatmapManager.ItemUpdated += itemUpdated; - beatmapManager.ItemRemoved += itemRemoved; } - private void itemUpdated(BeatmapSetInfo item) => Schedule(() => - { - if (matchingHash?.BeatmapSet.ID == item.ID || SelectedItem.Value?.Beatmap.Value.BeatmapSet?.OnlineID == item.OnlineID) - updateAvailability(); - }); - - private void itemRemoved(BeatmapSetInfo item) => Schedule(() => - { - if (matchingHash?.BeatmapSet.ID == item.ID) - updateAvailability(); - }); - private void updateAvailability() { if (downloadTracker == null) @@ -148,11 +152,7 @@ namespace osu.Game.Online.Rooms { base.Dispose(isDisposing); - if (beatmapManager != null) - { - beatmapManager.ItemUpdated -= itemUpdated; - beatmapManager.ItemRemoved -= itemRemoved; - } + realmSubscription?.Dispose(); } } } From 86ce2256be0a1619c81fca24618c145bb227933f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 17 Dec 2021 19:26:41 +0900 Subject: [PATCH 219/996] Replace `SpectatorScreen` event flow with realm subscriptions --- osu.Game/Screens/Spectate/SpectatorScreen.cs | 21 +++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs index c4e75cc413..dd586bdd37 100644 --- a/osu.Game/Screens/Spectate/SpectatorScreen.cs +++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -54,6 +55,11 @@ namespace osu.Game.Screens.Spectate this.users.AddRange(users); } + [Resolved] + private RealmContextFactory realmContextFactory { get; set; } + + private IDisposable realmSubscription; + protected override void LoadComplete() { base.LoadComplete(); @@ -73,7 +79,17 @@ namespace osu.Game.Screens.Spectate playingUserStates.BindTo(spectatorClient.PlayingUserStates); playingUserStates.BindCollectionChanged(onPlayingUserStatesChanged, true); - beatmaps.ItemUpdated += beatmapUpdated; + realmSubscription = realmContextFactory.Context + .All() + .Where(s => !s.DeletePending) + .QueryAsyncWithNotifications((items, changes, ___) => + { + if (changes?.InsertedIndices == null) + return; + + foreach (int c in changes.InsertedIndices) + beatmapUpdated(items[c]); + }); foreach ((int id, var _) in userMap) spectatorClient.WatchUser(id); @@ -219,8 +235,7 @@ namespace osu.Game.Screens.Spectate spectatorClient.StopWatchingUser(userId); } - if (beatmaps != null) - beatmaps.ItemUpdated -= beatmapUpdated; + realmSubscription?.Dispose(); } } } From 4295815c7d44800959c90b6284f85c419128058f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 17 Dec 2021 19:39:04 +0900 Subject: [PATCH 220/996] Fix invalid equality comparison in `BeatmapLeaderboard` --- osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 869036ef68..0bc93fc36e 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -37,7 +37,7 @@ namespace osu.Game.Screens.Select.Leaderboards get => beatmapInfo; set { - if (beatmapInfo.Equals(value)) + if (beatmapInfo?.Equals(value) == true) return; beatmapInfo = value; From 99e46cd26b30007f1f919840711814a58bbbc51d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Dec 2021 16:09:00 +0900 Subject: [PATCH 221/996] Fix missing `BeatmapMetadata.ToString` This is relied on by a few usages. --- osu.Game/Beatmaps/BeatmapMetadata.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs index dc5517375d..f729b55154 100644 --- a/osu.Game/Beatmaps/BeatmapMetadata.cs +++ b/osu.Game/Beatmaps/BeatmapMetadata.cs @@ -54,5 +54,7 @@ namespace osu.Game.Beatmaps } #endregion + + public override string ToString() => this.GetDisplayTitle(); } } From 167c399e8ada3ce3d47870d14b4e70235099ed81 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Dec 2021 16:09:19 +0900 Subject: [PATCH 222/996] Fix invalid DI resolution of `RealmFileStore` --- osu.Game/Storyboards/Drawables/DrawableStoryboard.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs index d21813d97f..3d6240bc98 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Textures; using osu.Framework.Platform; +using osu.Game.Database; using osu.Game.Screens.Play; using osu.Game.Stores; @@ -76,12 +77,12 @@ namespace osu.Game.Storyboards.Drawables } [BackgroundDependencyLoader(true)] - private void load(RealmFileStore fileStore, GameplayClock clock, CancellationToken? cancellationToken, GameHost host) + private void load(GameplayClock clock, CancellationToken? cancellationToken, GameHost host, RealmContextFactory realmContextFactory) { if (clock != null) Clock = clock; - dependencies.Cache(new TextureStore(host.CreateTextureLoaderStore(fileStore.Store), false, scaleAdjust: 1)); + dependencies.Cache(new TextureStore(host.CreateTextureLoaderStore(new RealmFileStore(realmContextFactory, host.Storage).Store), false, scaleAdjust: 1)); foreach (var layer in Storyboard.Layers) { From 2a980cc474a70993b160da4eb6c4ce80f0c8d634 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Dec 2021 16:09:32 +0900 Subject: [PATCH 223/996] Fix `BeatmapInfo` file lookup not handling the case where no files exist Quite common for test scenes. --- osu.Game/Beatmaps/BeatmapInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index fc0d007df4..16133ae362 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -38,7 +38,7 @@ namespace osu.Game.Beatmaps public BeatmapSetInfo? BeatmapSet { get; set; } [Ignored] - public RealmNamedFileUsage? File => BeatmapSet?.Files.First(f => f.File.Hash == Hash); + public RealmNamedFileUsage? File => BeatmapSet?.Files.FirstOrDefault(f => f.File.Hash == Hash); public BeatmapOnlineStatus Status { From dcd69e852eb0a516fc4d7bdfe27b23503f5d66b9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Dec 2021 16:30:34 +0900 Subject: [PATCH 224/996] Add back settable `RulesetID` for now --- osu.Game/Beatmaps/BeatmapInfo.cs | 15 ++++++++++++++- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 4 +--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 16133ae362..ea2508032e 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -140,8 +140,21 @@ namespace osu.Game.Beatmaps #region Compatibility properties + private int rulesetID; + [Ignored] - public int RulesetID => Ruleset.OnlineID; + public int RulesetID + { + // ReSharper disable once ConstantConditionalAccessQualifier + get => Ruleset?.OnlineID ?? rulesetID; + set + { + if (Ruleset != null) + throw new InvalidOperationException($"Cannot set a {nameof(RulesetID)} when {nameof(Ruleset)} is non-null"); + + rulesetID = value; + } + } [Ignored] public Guid BeatmapSetInfoID => BeatmapSet?.ID ?? Guid.Empty; diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 5c61d302c2..e5db9d045a 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -11,7 +11,6 @@ using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Legacy; using osu.Game.Beatmaps.Timing; using osu.Game.IO; -using osu.Game.Rulesets; using osu.Game.Rulesets.Objects.Legacy; namespace osu.Game.Beatmaps.Formats @@ -142,8 +141,7 @@ namespace osu.Game.Beatmaps.Formats break; case @"Mode": - // TODO: ha ha ha. - beatmap.BeatmapInfo.Ruleset = new RulesetInfo(Parsing.ParseInt(pair.Value), "some ruleset", "wangs", true /* probably not */); + beatmap.BeatmapInfo.RulesetID = Parsing.ParseInt(pair.Value); switch (beatmap.BeatmapInfo.RulesetID) { From 0793b0f0abf827976f0b813170ff933d44ea31fa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Dec 2021 16:37:29 +0900 Subject: [PATCH 225/996] Fix `Max` lookup methods not checking for zero beatmap count --- osu.Game/Beatmaps/BeatmapSetInfo.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index 4aea0c6ce8..ed16e3a965 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -50,11 +50,11 @@ namespace osu.Game.Beatmaps /// public bool Protected { get; set; } - public double MaxStarDifficulty => Beatmaps.Max(b => b.StarRating); + public double MaxStarDifficulty => Beatmaps.Count == 0 ? 0 : Beatmaps.Max(b => b.StarRating); - public double MaxLength => Beatmaps.Max(b => b.Length); + public double MaxLength => Beatmaps.Count == 0 ? 0 : Beatmaps.Max(b => b.Length); - public double MaxBPM => Beatmaps.Max(b => b.BPM); + public double MaxBPM => Beatmaps.Count == 0 ? 0 : Beatmaps.Max(b => b.BPM); /// /// Returns the storage path for the file in this beatmapset with the given filename, if any exists, otherwise null. From 33060990b7741ec2823a58fb590c4a4da554a8c3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 22 Dec 2021 17:05:00 +0900 Subject: [PATCH 226/996] Temporarily disable `WorkingBeatmapCache` and fix multiple invalid data flows --- osu.Game/Beatmaps/WorkingBeatmapCache.cs | 19 ++-------- osu.Game/Overlays/MusicController.cs | 36 ++++++++++--------- osu.Game/Screens/Menu/IntroScreen.cs | 32 +++++++++++++++-- .../Select/Leaderboards/BeatmapLeaderboard.cs | 11 ------ .../Visual/RateAdjustedBeatmapTestScene.cs | 7 ++-- 5 files changed, 57 insertions(+), 48 deletions(-) diff --git a/osu.Game/Beatmaps/WorkingBeatmapCache.cs b/osu.Game/Beatmaps/WorkingBeatmapCache.cs index 95983ae052..888851d1e8 100644 --- a/osu.Game/Beatmaps/WorkingBeatmapCache.cs +++ b/osu.Game/Beatmaps/WorkingBeatmapCache.cs @@ -12,7 +12,6 @@ using osu.Framework.IO.Stores; using osu.Framework.Lists; using osu.Framework.Logging; using osu.Framework.Platform; -using osu.Framework.Statistics; using osu.Framework.Testing; using osu.Game.Beatmaps.Formats; using osu.Game.Database; @@ -86,23 +85,11 @@ namespace osu.Game.Beatmaps if (beatmapInfo?.BeatmapSet == null) return DefaultBeatmap; - lock (workingCache) - { - var working = workingCache.FirstOrDefault(w => beatmapInfo.Equals(w.BeatmapInfo)); + WorkingBeatmap working = null; - if (working != null) - return working; + working = new BeatmapManagerWorkingBeatmap(beatmapInfo, this); - // TODO: is this still required..? - //beatmapInfo.Metadata ??= beatmapInfo.BeatmapSet.Metadata; - - workingCache.Add(working = new BeatmapManagerWorkingBeatmap(beatmapInfo, this)); - - // best effort; may be higher than expected. - GlobalStatistics.Get(nameof(Beatmaps), $"Cached {nameof(WorkingBeatmap)}s").Value = workingCache.Count(); - - return working; - } + return working; } #region IResourceStorageProvider diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 8c115c2c94..2731096a00 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -71,33 +71,37 @@ namespace osu.Game.Overlays [Resolved] private RealmContextFactory realmFactory { get; set; } - protected override void LoadComplete() + [BackgroundDependencyLoader] + private void load() { - base.LoadComplete(); - - beatmapSubscription = realmFactory.Context.All().QueryAsyncWithNotifications(beatmapsChanged); - // Todo: These binds really shouldn't be here, but are unlikely to cause any issues for now. // They are placed here for now since some tests rely on setting the beatmap _and_ their hierarchies inside their load(), which runs before the MusicController's load(). beatmap.BindValueChanged(beatmapChanged, true); mods.BindValueChanged(_ => ResetTrackAdjustments(), true); } + protected override void LoadComplete() + { + base.LoadComplete(); + + // ensure we're ready before completing async load. + // probably not a good way of handling this (as there is a period we aren't watching for changes until the realm subscription finishes up. + foreach (var s in realmFactory.Context.All()) + beatmapSets.Add(s); + + beatmapSubscription = realmFactory.Context.All().QueryAsyncWithNotifications(beatmapsChanged); + } + private void beatmapsChanged(IRealmCollection sender, ChangeSet changes, Exception error) { if (changes == null) - { - beatmapSets.AddRange(sender); - } + return; - // beatmaps.ItemUpdated += set => Schedule(() => - // { - // beatmapSets.Remove(set); - // beatmapSets.Add(set); - // }); - // beatmaps.ItemRemoved += set => Schedule(() => beatmapSets.RemoveAll(s => s.ID == set.ID)); - // - // beatmapSets.AddRange(beatmaps.GetAllUsableBeatmapSets(IncludedDetails.Minimal, true).OrderBy(_ => RNG.Next())); + foreach (int i in changes.InsertedIndices) + beatmapSets.Insert(i, sender[i]); + + foreach (int i in changes.DeletedIndices.OrderByDescending(i => i)) + beatmapSets.RemoveAt(i); } /// diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index f54228b441..e02066560d 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -81,8 +81,11 @@ namespace osu.Game.Screens.Menu this.createNextScreen = createNextScreen; } + [Resolved] + private BeatmapManager beatmaps { get; set; } + [BackgroundDependencyLoader] - private void load(OsuConfigManager config, SkinManager skinManager, BeatmapManager beatmaps, Framework.Game game, RealmContextFactory realmContextFactory) + private void load(OsuConfigManager config, SkinManager skinManager, Framework.Game game, RealmContextFactory realmContextFactory) { // prevent user from changing beatmap while the intro is still running. beatmap = Beatmap.BeginLease(false); @@ -101,7 +104,13 @@ namespace osu.Game.Screens.Menu if (sets.Count > 0) { setInfo = beatmaps.QueryBeatmapSet(s => s.ID == sets[RNG.Next(0, sets.Count - 1)].ID); - initialBeatmap = beatmaps.GetWorkingBeatmap(setInfo?.PerformRead(s => s.Beatmaps[0].ToLive(realmContextFactory))); + setInfo?.PerformRead(s => + { + if (s.Beatmaps.Count == 0) + return; + + initialBeatmap = beatmaps.GetWorkingBeatmap(s.Beatmaps[0].ToLive(realmContextFactory)); + }); } } @@ -131,12 +140,29 @@ namespace osu.Game.Screens.Menu if (setInfo == null) return false; - initialBeatmap = beatmaps.GetWorkingBeatmap(setInfo.PerformRead(s => s.Beatmaps[0].ToLive(realmContextFactory))); + setInfo.PerformRead(s => + { + if (s.Beatmaps.Count == 0) + return; + + initialBeatmap = beatmaps.GetWorkingBeatmap(s.Beatmaps[0].ToLive(realmContextFactory)); + }); return UsingThemedIntro = initialBeatmap != null; } } + protected override void LoadComplete() + { + base.LoadComplete(); + + // TODO: This is temporary to get the setInfo on the update thread, to make things work "better" without using ILive everywhere. + var setInfo = beatmaps.QueryBeatmapSets(b => b.Hash == BeatmapHash).FirstOrDefault(); + + if (setInfo?.Value.Beatmaps.Count > 0) + initialBeatmap = beatmaps.GetWorkingBeatmap(setInfo.Value.Beatmaps.First()); + } + public override void OnResuming(IScreen last) { this.FadeIn(300); diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 0bc93fc36e..c2ab930550 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -126,17 +126,6 @@ namespace osu.Game.Screens.Select.Leaderboards TopScore = null; } - private void scoreStoreChanged(ScoreInfo score) - { - if (Scope != BeatmapLeaderboardScope.Local) - return; - - if (BeatmapInfo?.ID != score.BeatmapInfoID) - return; - - RefreshScores(); - } - protected override bool IsOnlineScope => Scope != BeatmapLeaderboardScope.Local; private CancellationTokenSource loadCancellationSource; diff --git a/osu.Game/Tests/Visual/RateAdjustedBeatmapTestScene.cs b/osu.Game/Tests/Visual/RateAdjustedBeatmapTestScene.cs index ad24ffc7b8..5bac2635f5 100644 --- a/osu.Game/Tests/Visual/RateAdjustedBeatmapTestScene.cs +++ b/osu.Game/Tests/Visual/RateAdjustedBeatmapTestScene.cs @@ -12,8 +12,11 @@ namespace osu.Game.Tests.Visual { base.Update(); - // note that this will override any mod rate application - Beatmap.Value.Track.Tempo.Value = Clock.Rate; + if (Beatmap.Value.TrackLoaded && Beatmap.Value.Track != null) // null check... wasn't required until now? + { + // note that this will override any mod rate application + Beatmap.Value.Track.Tempo.Value = Clock.Rate; + } } } } From 667cdb2475279378beee6012ec59e7bc4018d636 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 Dec 2021 17:09:24 +0900 Subject: [PATCH 227/996] Fix skin lookup when there's no beatmap file available --- osu.Game/Skinning/LegacySkin.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index e677e2c01b..81c695b8da 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -63,7 +63,7 @@ namespace osu.Game.Skinning /// Access to raw game resources. /// The user-facing filename of the configuration file to be parsed. Can accept an .osu or skin.ini file. protected LegacySkin(SkinInfo skin, [CanBeNull] IResourceStore storage, [CanBeNull] IStorageResourceProvider resources, string configurationFilename) - : this(skin, storage, resources, storage?.GetStream(configurationFilename)) + : this(skin, storage, resources, string.IsNullOrEmpty(configurationFilename) ? null : storage?.GetStream(configurationFilename)) { } From 3811bd85208e3667b3e6da0aa40c2616673a6588 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 4 Jan 2022 14:35:18 +0900 Subject: [PATCH 228/996] Fix some null inspections --- .../Visual/SongSelect/TestScenePlaySongSelect.cs | 4 ++-- osu.Game/Beatmaps/WorkingBeatmapCache.cs | 6 +----- osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs | 2 +- osu.Game/Rulesets/Edit/Checks/CheckAudioInVideo.cs | 2 +- osu.Game/Screens/Edit/Verify/IssueList.cs | 2 +- osu.Game/Screens/Select/BeatmapDeleteDialog.cs | 2 +- osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs | 8 ++++---- .../Screens/Select/Carousel/DrawableCarouselBeatmap.cs | 2 +- osu.Game/Screens/Select/Carousel/SetPanelContent.cs | 4 ++-- 9 files changed, 14 insertions(+), 18 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 586cd273be..7b81f330d0 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -744,7 +744,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddUntilStep("Check ruleset changed to mania", () => Ruleset.Value.OnlineID == 3); - AddAssert("Selected beatmap still same set", () => songSelect.Carousel.SelectedBeatmapInfo.BeatmapSet.OnlineID == previousSetID); + AddAssert("Selected beatmap still same set", () => songSelect.Carousel.SelectedBeatmapInfo.BeatmapSet?.OnlineID == previousSetID); AddAssert("Selected beatmap is mania", () => Beatmap.Value.BeatmapInfo.Ruleset.OnlineID == 3); } @@ -760,7 +760,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("import huge difficulty count map", () => { var usableRulesets = rulesets.AvailableRulesets.Where(r => r.OnlineID != 2).ToArray(); - imported = manager.Import(TestResources.CreateTestBeatmapSetInfo(50, usableRulesets)).GetResultSafely().Value; + imported = manager.Import(TestResources.CreateTestBeatmapSetInfo(50, usableRulesets)).GetResultSafely()?.Value; }); AddStep("select the first beatmap of import", () => Beatmap.Value = manager.GetWorkingBeatmap(imported.Beatmaps.First())); diff --git a/osu.Game/Beatmaps/WorkingBeatmapCache.cs b/osu.Game/Beatmaps/WorkingBeatmapCache.cs index 888851d1e8..beec48becd 100644 --- a/osu.Game/Beatmaps/WorkingBeatmapCache.cs +++ b/osu.Game/Beatmaps/WorkingBeatmapCache.cs @@ -85,11 +85,7 @@ namespace osu.Game.Beatmaps if (beatmapInfo?.BeatmapSet == null) return DefaultBeatmap; - WorkingBeatmap working = null; - - working = new BeatmapManagerWorkingBeatmap(beatmapInfo, this); - - return working; + return new BeatmapManagerWorkingBeatmap(beatmapInfo, this); } #region IResourceStorageProvider diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index 2c78fa264e..5ef434c427 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -173,7 +173,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { Text = score.MaxCombo.ToLocalisableString(@"0\x"), Font = OsuFont.GetFont(size: text_size), - Colour = score.MaxCombo == score.BeatmapInfo?.MaxCombo ? highAccuracyColour : Color4.White + Colour = score.MaxCombo == score.BeatmapInfo.MaxCombo ? highAccuracyColour : Color4.White } }; diff --git a/osu.Game/Rulesets/Edit/Checks/CheckAudioInVideo.cs b/osu.Game/Rulesets/Edit/Checks/CheckAudioInVideo.cs index 255671c807..a285979fd2 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckAudioInVideo.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckAudioInVideo.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Edit.Checks foreach (string filename in videoPaths) { - string storagePath = beatmapSet.GetPathForFile(filename); + string storagePath = beatmapSet?.GetPathForFile(filename); if (storagePath == null) { diff --git a/osu.Game/Screens/Edit/Verify/IssueList.cs b/osu.Game/Screens/Edit/Verify/IssueList.cs index cadcdebc6e..5fe43199cc 100644 --- a/osu.Game/Screens/Edit/Verify/IssueList.cs +++ b/osu.Game/Screens/Edit/Verify/IssueList.cs @@ -40,7 +40,7 @@ namespace osu.Game.Screens.Edit.Verify private void load(OverlayColourProvider colours) { generalVerifier = new BeatmapVerifier(); - rulesetVerifier = beatmap.BeatmapInfo.Ruleset?.CreateInstance().CreateBeatmapVerifier(); + rulesetVerifier = beatmap.BeatmapInfo.Ruleset.CreateInstance().CreateBeatmapVerifier(); context = new BeatmapVerifierContext(beatmap, workingBeatmap.Value, verify.InterpretedDifficulty.Value); verify.InterpretedDifficulty.BindValueChanged(difficulty => context.InterpretedDifficulty = difficulty.NewValue); diff --git a/osu.Game/Screens/Select/BeatmapDeleteDialog.cs b/osu.Game/Screens/Select/BeatmapDeleteDialog.cs index 307c2352e3..1ac278d045 100644 --- a/osu.Game/Screens/Select/BeatmapDeleteDialog.cs +++ b/osu.Game/Screens/Select/BeatmapDeleteDialog.cs @@ -20,7 +20,7 @@ namespace osu.Game.Screens.Select public BeatmapDeleteDialog(BeatmapSetInfo beatmap) { - BodyText = $@"{beatmap.Metadata?.Artist} - {beatmap.Metadata?.Title}"; + BodyText = $@"{beatmap.Metadata.Artist} - {beatmap.Metadata.Title}"; Icon = FontAwesome.Regular.TrashAlt; HeaderText = @"Confirm deletion of"; diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index a1a8de04ae..f2700e9053 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -63,16 +63,16 @@ namespace osu.Game.Screens.Select.Carousel { default: case SortMode.Artist: - return string.Compare(BeatmapSet.Metadata?.Artist, otherSet.BeatmapSet.Metadata?.Artist, StringComparison.OrdinalIgnoreCase); + return string.Compare(BeatmapSet.Metadata.Artist, otherSet.BeatmapSet.Metadata.Artist, StringComparison.OrdinalIgnoreCase); case SortMode.Title: - return string.Compare(BeatmapSet.Metadata?.Title, otherSet.BeatmapSet.Metadata?.Title, StringComparison.OrdinalIgnoreCase); + return string.Compare(BeatmapSet.Metadata.Title, otherSet.BeatmapSet.Metadata.Title, StringComparison.OrdinalIgnoreCase); case SortMode.Author: - return string.Compare(BeatmapSet.Metadata?.Author.Username, otherSet.BeatmapSet.Metadata?.Author.Username, StringComparison.OrdinalIgnoreCase); + return string.Compare(BeatmapSet.Metadata.Author.Username, otherSet.BeatmapSet.Metadata.Author.Username, StringComparison.OrdinalIgnoreCase); case SortMode.Source: - return string.Compare(BeatmapSet.Metadata?.Source, otherSet.BeatmapSet.Metadata?.Source, StringComparison.OrdinalIgnoreCase); + return string.Compare(BeatmapSet.Metadata.Source, otherSet.BeatmapSet.Metadata.Source, StringComparison.OrdinalIgnoreCase); case SortMode.DateAdded: return otherSet.BeatmapSet.DateAdded.CompareTo(BeatmapSet.DateAdded); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 2eb226df70..a5fabd9237 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -142,7 +142,7 @@ namespace osu.Game.Screens.Select.Carousel }, new OsuSpriteText { - Text = $"{(beatmapInfo.Metadata ?? beatmapInfo.BeatmapSet.Metadata).Author.Username}", + Text = $"{beatmapInfo.Metadata.Author.Username}", Font = OsuFont.GetFont(italics: true), Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft diff --git a/osu.Game/Screens/Select/Carousel/SetPanelContent.cs b/osu.Game/Screens/Select/Carousel/SetPanelContent.cs index 9aa85c872e..f2054677b0 100644 --- a/osu.Game/Screens/Select/Carousel/SetPanelContent.cs +++ b/osu.Game/Screens/Select/Carousel/SetPanelContent.cs @@ -41,13 +41,13 @@ namespace osu.Game.Screens.Select.Carousel { new OsuSpriteText { - Text = new RomanisableString(beatmapSet.Metadata?.TitleUnicode, beatmapSet.Metadata?.Title), + Text = new RomanisableString(beatmapSet.Metadata.TitleUnicode, beatmapSet.Metadata.Title), Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 22, italics: true), Shadow = true, }, new OsuSpriteText { - Text = new RomanisableString(beatmapSet.Metadata?.ArtistUnicode, beatmapSet.Metadata?.Artist), + Text = new RomanisableString(beatmapSet.Metadata.ArtistUnicode, beatmapSet.Metadata.Artist), Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 17, italics: true), Shadow = true, }, From de076678fe3120b32079f6356b87e4311e149256 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 4 Jan 2022 15:59:41 +0900 Subject: [PATCH 229/996] Fix some remaining test failures --- osu.Game.Tests/Online/TestAPIModJsonSerialization.cs | 12 ++++++++++-- .../Visual/Ranking/TestSceneAccuracyCircle.cs | 1 + osu.Game/Online/Solo/SubmittableScore.cs | 3 +-- osu.Game/Screens/Ranking/ScorePanelList.cs | 4 ++-- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs b/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs index 4b160e1d67..1b7a7656b5 100644 --- a/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs +++ b/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs @@ -9,10 +9,12 @@ using osu.Framework.Bindables; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Online.API; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Solo; using osu.Game.Rulesets; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.UI; using osu.Game.Scoring; @@ -93,7 +95,11 @@ namespace osu.Game.Tests.Online [Test] public void TestDeserialiseSubmittableScoreWithEmptyMods() { - var score = new SubmittableScore(new ScoreInfo()); + var score = new SubmittableScore(new ScoreInfo + { + User = new APIUser(), + Ruleset = new OsuRuleset().RulesetInfo, + }); var deserialised = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(score)); @@ -105,7 +111,9 @@ namespace osu.Game.Tests.Online { var score = new SubmittableScore(new ScoreInfo { - Mods = new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 2 } } } + Mods = new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 2 } } }, + User = new APIUser(), + Ruleset = new OsuRuleset().RulesetInfo, }); var deserialised = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(score)); diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs index 944941723e..ac736086fd 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs @@ -72,6 +72,7 @@ namespace osu.Game.Tests.Visual.Ranking Username = "peppy", }, BeatmapInfo = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo, + Ruleset = new OsuRuleset().RulesetInfo, Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() }, TotalScore = 2845370, Accuracy = accuracy, diff --git a/osu.Game/Online/Solo/SubmittableScore.cs b/osu.Game/Online/Solo/SubmittableScore.cs index 327806f390..4e4dae5157 100644 --- a/osu.Game/Online/Solo/SubmittableScore.cs +++ b/osu.Game/Online/Solo/SubmittableScore.cs @@ -10,7 +10,6 @@ using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; -using osu.Game.Users; namespace osu.Game.Online.Solo { @@ -48,7 +47,7 @@ namespace osu.Game.Online.Solo public APIMod[] Mods { get; set; } [JsonProperty("user")] - public IUser User { get; set; } + public APIUser User { get; set; } [JsonProperty("statistics")] public Dictionary Statistics { get; set; } diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index 7f270b3a2a..c2ef5529e8 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -158,7 +158,7 @@ namespace osu.Game.Screens.Ranking trackingContainer.Show(); - if (SelectedScore.Value.Equals(score)) + if (SelectedScore.Value?.Equals(score) == true) { SelectedScore.TriggerChange(); } @@ -185,7 +185,7 @@ namespace osu.Game.Screens.Ranking private void selectedScoreChanged(ValueChangedEvent score) { // avoid contracting panels unnecessarily when TriggerChange is fired manually. - if (!score.OldValue.Equals(score.NewValue)) + if (score.OldValue != null && !score.OldValue.Equals(score.NewValue)) { // Contract the old panel. foreach (var t in flow.Where(t => t.Panel.Score.Equals(score.OldValue))) From 8461eaab46922fb44c937ef62b1469acd7315045 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 7 Jan 2022 14:17:22 +0900 Subject: [PATCH 230/996] `BeatmapSetInfo` detach support --- .../Database/BeatmapImporterTests.cs | 45 +++++++++++++++++++ osu.Game/Beatmaps/BeatmapInfo.cs | 4 ++ osu.Game/Beatmaps/BeatmapManager.cs | 4 +- osu.Game/Beatmaps/BeatmapModelManager.cs | 2 +- osu.Game/Beatmaps/BeatmapSetInfo.cs | 4 ++ osu.Game/Beatmaps/WorkingBeatmapCache.cs | 4 ++ osu.Game/Collections/CollectionManager.cs | 2 +- osu.Game/Database/RealmLive.cs | 5 ++- osu.Game/Database/RealmObjectExtensions.cs | 26 +++++++++-- osu.Game/Screens/Select/BeatmapCarousel.cs | 4 ++ 10 files changed, 90 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index 5b63db1972..78b8628669 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -35,6 +35,51 @@ namespace osu.Game.Tests.Database [TestFixture] public class BeatmapImporterTests : RealmTest { + [Test] + public void TestDetach() + { + RunTestWithRealmAsync(async (realmFactory, storage) => + { + using (var importer = new BeatmapModelManager(realmFactory, storage)) + using (new RulesetStore(realmFactory, storage)) + { + ILive? imported; + + using (var reader = new ZipArchiveReader(TestResources.GetTestBeatmapStream())) + imported = await importer.Import(reader); + + Assert.NotNull(imported); + Debug.Assert(imported != null); + + BeatmapSetInfo? detached = null; + + imported.PerformRead(live => + { + var timer = new Stopwatch(); + timer.Start(); + detached = live.Detach(); + Logger.Log($"Detach took {timer.ElapsedMilliseconds} ms"); + + Logger.Log($"NamedFiles: {live.Files.Count} {detached.Files.Count}"); + Logger.Log($"Files: {live.Files.Select(f => f.File).Count()} {detached.Files.Select(f => f.File).Count()}"); + Logger.Log($"Difficulties: {live.Beatmaps.Count} {detached.Beatmaps.Count}"); + Logger.Log($"BeatmapDifficulties: {live.Beatmaps.Select(f => f.Difficulty).Count()} {detached.Beatmaps.Select(f => f.Difficulty).Count()}"); + Logger.Log($"Metadata: {live.Metadata} {detached.Metadata}"); + }); + + Logger.Log("Testing detached-ness"); + + Debug.Assert(detached != null); + + Logger.Log($"NamedFiles: {detached.Files.Count}"); + Logger.Log($"Files: {detached.Files.Select(f => f.File).Count()}"); + Logger.Log($"Difficulties: {detached.Beatmaps.Count}"); + Logger.Log($"BeatmapDifficulties: {detached.Beatmaps.Select(f => f.Difficulty).Count()}"); + Logger.Log($"Metadata: {detached.Metadata}"); + } + }); + } + [Test] public void TestImportBeatmapThenCleanup() { diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index ea2508032e..153f40e39d 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -3,6 +3,7 @@ using System; using System.Linq; +using AutoMapper; using JetBrains.Annotations; using Newtonsoft.Json; using osu.Framework.Testing; @@ -143,6 +144,7 @@ namespace osu.Game.Beatmaps private int rulesetID; [Ignored] + [IgnoreMap] public int RulesetID { // ReSharper disable once ConstantConditionalAccessQualifier @@ -182,6 +184,8 @@ namespace osu.Game.Beatmaps public BeatmapInfo Clone() => this.Detach(); + public override string ToString() => Metadata?.ToString() ?? base.ToString(); + #endregion } } diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index d9484a2880..2479390700 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -151,7 +151,7 @@ namespace osu.Game.Beatmaps public List GetAllUsableBeatmapSets() { using (var context = contextFactory.CreateContext()) - return context.All().Where(b => !b.DeletePending).ToList(); + return context.All().Where(b => !b.DeletePending).Detach(); } /// @@ -199,7 +199,7 @@ namespace osu.Game.Beatmaps /// /// The query. /// The first result for the provided query, or null if no results were found. - public BeatmapInfo? QueryBeatmap(Expression> query) => beatmapModelManager.QueryBeatmap(query); + public BeatmapInfo? QueryBeatmap(Expression> query) => beatmapModelManager.QueryBeatmap(query)?.Detach(); /// /// Saves an file against a given . diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs index 387521ee1d..254babc3a1 100644 --- a/osu.Game/Beatmaps/BeatmapModelManager.cs +++ b/osu.Game/Beatmaps/BeatmapModelManager.cs @@ -107,7 +107,7 @@ namespace osu.Game.Beatmaps public BeatmapInfo? QueryBeatmap(Expression> query) { using (var context = ContextFactory.CreateContext()) - return context.All().FirstOrDefault(query); // TODO: ?.ToLive(); + return context.All().FirstOrDefault(query)?.Detach(); } } } diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index ed16e3a965..1cc6a96f40 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using AutoMapper; using osu.Framework.Testing; using osu.Game.Database; using osu.Game.Extensions; @@ -75,7 +76,10 @@ namespace osu.Game.Beatmaps public bool Equals(IBeatmapSetInfo? other) => other is BeatmapSetInfo b && Equals(b); + [IgnoreMap] IEnumerable IBeatmapSetInfo.Beatmaps => Beatmaps; + + [IgnoreMap] IEnumerable IHasNamedFiles.Files => Files; } } diff --git a/osu.Game/Beatmaps/WorkingBeatmapCache.cs b/osu.Game/Beatmaps/WorkingBeatmapCache.cs index beec48becd..6be69b5477 100644 --- a/osu.Game/Beatmaps/WorkingBeatmapCache.cs +++ b/osu.Game/Beatmaps/WorkingBeatmapCache.cs @@ -82,6 +82,10 @@ namespace osu.Game.Beatmaps beatmapInfo = BeatmapManager.QueryBeatmap(b => b.ID == lookupId); } + // TODO: FUCK THE WORLD :D + if (beatmapInfo?.IsManaged == true) + beatmapInfo = beatmapInfo.Detach(); + if (beatmapInfo?.BeatmapSet == null) return DefaultBeatmap; diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs index c4f991094c..d230e649f7 100644 --- a/osu.Game/Collections/CollectionManager.cs +++ b/osu.Game/Collections/CollectionManager.cs @@ -209,7 +209,7 @@ namespace osu.Game.Collections string checksum = sr.ReadString(); - var beatmap = beatmaps.QueryBeatmap(b => b.MD5Hash == checksum); + var beatmap = beatmaps.QueryBeatmap(b => b.MD5Hash == checksum)?.Detach(); if (beatmap != null) collection.Beatmaps.Add(beatmap); } diff --git a/osu.Game/Database/RealmLive.cs b/osu.Game/Database/RealmLive.cs index 90b8814c24..45bc0e4200 100644 --- a/osu.Game/Database/RealmLive.cs +++ b/osu.Game/Database/RealmLive.cs @@ -61,8 +61,9 @@ namespace osu.Game.Database /// The action to perform. public TReturn PerformRead(Func perform) { - if (typeof(RealmObjectBase).IsAssignableFrom(typeof(TReturn))) - throw new InvalidOperationException(@$"Realm live objects should not exit the scope of {nameof(PerformRead)}."); + // TODO: this is weird and kinda wrong... unmanaged objects should be allowed? + // if (typeof(RealmObjectBase).IsAssignableFrom(typeof(TReturn))) + // throw new InvalidOperationException(@$"Realm live objects should not exit the scope of {nameof(PerformRead)}."); if (!IsManaged) return perform(data); diff --git a/osu.Game/Database/RealmObjectExtensions.cs b/osu.Game/Database/RealmObjectExtensions.cs index e09f046421..9d7d08e106 100644 --- a/osu.Game/Database/RealmObjectExtensions.cs +++ b/osu.Game/Database/RealmObjectExtensions.cs @@ -6,7 +6,10 @@ using System.Collections.Generic; using System.Linq; using AutoMapper; using osu.Framework.Development; +using osu.Game.Beatmaps; using osu.Game.Input.Bindings; +using osu.Game.Models; +using osu.Game.Rulesets; using Realms; #nullable enable @@ -18,9 +21,23 @@ namespace osu.Game.Database private static readonly IMapper mapper = new MapperConfiguration(c => { c.ShouldMapField = fi => false; - c.ShouldMapProperty = pi => pi.SetMethod != null && pi.SetMethod.IsPublic; + c.ShouldMapProperty = pi => true; c.CreateMap(); + c.CreateMap(); + c.CreateMap(); + c.CreateMap(); + c.CreateMap(); + c.CreateMap(); + c.CreateMap(); + c.CreateMap(); + c.CreateMap(); + + c.ForAllMaps((a, b) => + { + b.PreserveReferences(); + b.MaxDepth(2); + }); }).CreateMapper(); /// @@ -32,7 +49,7 @@ namespace osu.Game.Database /// A list of managed s to detach. /// The type of object. /// A list containing non-managed copies of provided items. - public static List Detach(this IEnumerable items) where T : RealmObject + public static List Detach(this IEnumerable items) where T : RealmObjectBase { var list = new List(); @@ -51,7 +68,7 @@ namespace osu.Game.Database /// The managed to detach. /// The type of object. /// A non-managed copy of provided item. Will return the provided item if already detached. - public static T Detach(this T item) where T : RealmObject + public static T Detach(this T item) where T : RealmObjectBase { if (!item.IsManaged) return item; @@ -65,7 +82,8 @@ namespace osu.Game.Database return realmList.Select(l => new RealmLiveUnmanaged(l)).Cast>().ToList(); } - public static ILive ToLiveUnmanaged(this T realmObject) + public static ILive ToLiveUnmanaged(this T realmObject + ) where T : RealmObject, IHasGuidPrimaryKey { return new RealmLiveUnmanaged(realmObject); diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 78e416c64b..226d2df619 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -662,6 +662,10 @@ namespace osu.Game.Screens.Select if (beatmapSet.Beatmaps.All(b => b.Hidden)) return null; + // TODO: FUCK THE WORLD :D + if (beatmapSet?.IsManaged == true) + beatmapSet = beatmapSet.Detach(); + // todo: probably not required any more. // foreach (var b in beatmapSet.Beatmaps) // b.Metadata ??= beatmapSet.Metadata; From de3a338d02f2767a6051298790efdd7927262538 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 7 Jan 2022 14:47:03 +0900 Subject: [PATCH 231/996] Update realm queries to use `Filter` to allow for indirect property filtering --- .../Screens/Select/Carousel/TopLocalRank.cs | 20 ++++++++++++------- .../Select/Leaderboards/BeatmapLeaderboard.cs | 18 ++++++++--------- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs index 2ade213e8b..9a0085c62a 100644 --- a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs +++ b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs @@ -13,6 +13,7 @@ using osu.Game.Online.API; using osu.Game.Online.Leaderboards; using osu.Game.Rulesets; using osu.Game.Scoring; +using Realms; namespace osu.Game.Screens.Select.Carousel { @@ -47,13 +48,15 @@ namespace osu.Game.Screens.Select.Carousel { base.LoadComplete(); - scoreSubscription = realmFactory.Context.All().Where(s => s.BeatmapInfo.ID == beatmapInfo.ID).QueryAsyncWithNotifications((_, changes, ___) => - { - if (changes == null) - return; + scoreSubscription = realmFactory.Context.All() + .Filter($"{nameof(ScoreInfo.Beatmap)}.{nameof(BeatmapInfo.ID)} = $0", beatmapInfo.ID) + .QueryAsyncWithNotifications((_, changes, ___) => + { + if (changes == null) + return; - fetchTopScoreRank(); - }); + fetchTopScoreRank(); + }); } private IDisposable scoreSubscription; @@ -84,7 +87,10 @@ namespace osu.Game.Screens.Select.Carousel using (var realm = realmFactory.CreateContext()) { - return realm.All().Where(s => s.UserID == api.LocalUser.Value.Id && s.BeatmapInfoID == beatmapInfo.ID && s.RulesetID == ruleset.Value.ID && !s.DeletePending) + return realm.All() + .AsEnumerable() + // TODO: update to use a realm filter directly (or at least figure out the beatmap part to reduce scope). + .Where(s => s.UserID == api.LocalUser.Value.Id && s.BeatmapInfoID == beatmapInfo.ID && s.RulesetID == ruleset.Value.ID && !s.DeletePending) .OrderByDescending(s => s.TotalScore) .FirstOrDefault() ?.Rank; diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index c2ab930550..f2654cbdee 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -17,6 +17,7 @@ using osu.Game.Online.Leaderboards; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Scoring; +using Realms; namespace osu.Game.Screens.Select.Leaderboards { @@ -80,9 +81,6 @@ namespace osu.Game.Screens.Select.Leaderboards [Resolved] private IAPIProvider api { get; set; } - [Resolved] - private RealmContextFactory realmContextFactory { get; set; } - [BackgroundDependencyLoader] private void load() { @@ -111,13 +109,15 @@ namespace osu.Game.Screens.Select.Leaderboards if (beatmapInfo == null) return; - scoreSubscription = realmContextFactory.Context.All().Where(s => s.BeatmapInfo.ID == beatmapInfo.ID).QueryAsyncWithNotifications((_, changes, ___) => - { - if (changes == null) - return; + scoreSubscription = realmFactory.Context.All() + .Filter($"{nameof(ScoreInfo.Beatmap)}.{nameof(BeatmapInfo.ID)} = $0", beatmapInfo.ID) + .QueryAsyncWithNotifications((_, changes, ___) => + { + if (changes == null) + return; - RefreshScores(); - }); + RefreshScores(); + }); } protected override void Reset() From e1f77b87def512748cf8cde40c2b82c606545654 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 7 Jan 2022 16:48:49 +0900 Subject: [PATCH 232/996] "Fix" OnlinePlayBeatmapAvailabilityTracker --- .../Rooms/OnlinePlayBeatmapAvailabilityTracker.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs index 01999ba766..d344846e5f 100644 --- a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs @@ -83,13 +83,10 @@ namespace osu.Game.Online.Rooms progressUpdate = Scheduler.AddDelayed(updateAvailability, progressUpdate == null ? 0 : 500); }, true); - // These events are needed for a fringe case where a modified/altered beatmap is imported with matching OnlineIDs. - // During the import process this will cause the existing beatmap set to be silently deleted and replaced with the new one. - // This is not exposed to us via `BeatmapDownloadTracker` so we have to take it into our own hands (as we care about the hash matching). realmSubscription?.Dispose(); realmSubscription = realmContextFactory.Context .All() - .Where(s => s.OnlineID == SelectedItem.Value.BeatmapID || (matchingHash != null && s.ID == matchingHash.ID)) + .Where(s => s.OnlineID == SelectedItem.Value.BeatmapID) // TODO: figure out if we need this hash match in the subscription || s.ID == matchingHash?.ID) .QueryAsyncWithNotifications((items, changes, ___) => { if (changes == null) @@ -145,7 +142,13 @@ namespace osu.Game.Online.Rooms int onlineId = SelectedItem.Value.Beatmap.Value.OnlineID; string checksum = SelectedItem.Value.Beatmap.Value.MD5Hash; - return beatmapManager.QueryBeatmap(b => b.OnlineID == onlineId && b.MD5Hash == checksum && !b.BeatmapSet.DeletePending); + var foundBeatmap = beatmapManager.QueryBeatmap(b => b.OnlineID == onlineId && b.MD5Hash == checksum); + + // can't be included in the above query due to realm limitations. + if (foundBeatmap?.BeatmapSet?.DeletePending == true) + return null; + + return foundBeatmap; } protected override void Dispose(bool isDisposing) From 2d2faa72a96ca601888da4d55d28381ff382f2fc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 7 Jan 2022 16:49:03 +0900 Subject: [PATCH 233/996] Fix rulesets being out of order --- osu.Game/Rulesets/RulesetStore.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index 6ec89f9dc5..c675fbbf63 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -141,7 +141,7 @@ namespace osu.Game.Rulesets List detachedRulesets = new List(); // perform a consistency check and detach final rulesets from realm for cross-thread runtime usage. - foreach (var r in rulesets) + foreach (var r in rulesets.OrderBy(r => r.OnlineID)) { try { From 60d2de8a3b486d3b9b85fa726784740732d5d4c5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 7 Jan 2022 16:49:13 +0900 Subject: [PATCH 234/996] Fix potential nullref when song select filters to no results --- osu.Game/Screens/Select/SongSelect.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 54a4c3270d..9ba5e77bbb 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -482,7 +482,7 @@ namespace osu.Game.Screens.Select else selectionChangedDebounce = Scheduler.AddDelayed(run, 200); - if (!beatmap.Equals(beatmapInfoPrevious)) + if (beatmap?.Equals(beatmapInfoPrevious) != true) { if (beatmap != null && beatmapInfoPrevious != null && Time.Current - audioFeedbackLastPlaybackTime >= 50) { From 2b8706b6ce2762bf6d1130840308a4bb3f6dd905 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 7 Jan 2022 17:27:48 +0900 Subject: [PATCH 235/996] Detach and reattach scores to make work --- osu.Game/Database/RealmObjectExtensions.cs | 2 ++ osu.Game/OsuGame.cs | 2 ++ .../Scoring/Legacy/DatabasedLegacyScoreDecoder.cs | 4 ++-- osu.Game/Scoring/ScoreInfo.cs | 8 ++++++++ osu.Game/Scoring/ScoreModelManager.cs | 11 ++++++++++- osu.Game/Screens/Play/Player.cs | 12 +++++------- .../Select/Leaderboards/BeatmapLeaderboard.cs | 7 ++++++- 7 files changed, 35 insertions(+), 11 deletions(-) diff --git a/osu.Game/Database/RealmObjectExtensions.cs b/osu.Game/Database/RealmObjectExtensions.cs index 9d7d08e106..dfa3076448 100644 --- a/osu.Game/Database/RealmObjectExtensions.cs +++ b/osu.Game/Database/RealmObjectExtensions.cs @@ -10,6 +10,7 @@ using osu.Game.Beatmaps; using osu.Game.Input.Bindings; using osu.Game.Models; using osu.Game.Rulesets; +using osu.Game.Scoring; using Realms; #nullable enable @@ -27,6 +28,7 @@ namespace osu.Game.Database c.CreateMap(); c.CreateMap(); c.CreateMap(); + c.CreateMap(); c.CreateMap(); c.CreateMap(); c.CreateMap(); diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 8d1eccb7c1..149de9bc76 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -502,6 +502,8 @@ namespace osu.Game return; } + databasedScoreInfo = databasedScoreInfo.Detach(); + var databasedScore = ScoreManager.GetScore(databasedScoreInfo); if (databasedScore.Replay == null) diff --git a/osu.Game/Scoring/Legacy/DatabasedLegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/DatabasedLegacyScoreDecoder.cs index a49eb89ecc..03e13455f0 100644 --- a/osu.Game/Scoring/Legacy/DatabasedLegacyScoreDecoder.cs +++ b/osu.Game/Scoring/Legacy/DatabasedLegacyScoreDecoder.cs @@ -21,7 +21,7 @@ namespace osu.Game.Scoring.Legacy this.beatmaps = beatmaps; } - protected override Ruleset GetRuleset(int rulesetId) => rulesets.GetRuleset(rulesetId).CreateInstance(); - protected override WorkingBeatmap GetBeatmap(string md5Hash) => beatmaps.GetWorkingBeatmap(beatmaps.QueryBeatmap(b => !b.BeatmapSet.DeletePending && b.MD5Hash == md5Hash)); + protected override Ruleset GetRuleset(int rulesetId) => rulesets.GetRuleset(rulesetId)?.CreateInstance(); + protected override WorkingBeatmap GetBeatmap(string md5Hash) => beatmaps.GetWorkingBeatmap(beatmaps.QueryBeatmap(b => b.MD5Hash == md5Hash)); } } diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 65c31951b8..c07f4a4f87 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using AutoMapper; using Newtonsoft.Json; using osu.Framework.Localisation; using osu.Framework.Testing; @@ -112,9 +113,16 @@ namespace osu.Game.Scoring [MapTo(nameof(Rank))] public int RankInt { get; set; } + [IgnoreMap] IRulesetInfo IScoreInfo.Ruleset => Ruleset; + + [IgnoreMap] IBeatmapInfo IScoreInfo.Beatmap => Beatmap; + + [IgnoreMap] IUser IScoreInfo.User => User; + + [IgnoreMap] IEnumerable IHasNamedFiles.Files => Files; #region Properties required to make things work with existing usages diff --git a/osu.Game/Scoring/ScoreModelManager.cs b/osu.Game/Scoring/ScoreModelManager.cs index 81f80df3ad..8f04e50f63 100644 --- a/osu.Game/Scoring/ScoreModelManager.cs +++ b/osu.Game/Scoring/ScoreModelManager.cs @@ -55,6 +55,15 @@ namespace osu.Game.Scoring public Score GetScore(ScoreInfo score) => new LegacyDatabasedScore(score, rulesets, beatmaps(), Files.Store); protected override Task Populate(ScoreInfo model, ArchiveReader? archive, Realm realm, CancellationToken cancellationToken = default) - => Task.CompletedTask; + { + // Ensure the beatmap is not detached. + if (!model.Beatmap.IsManaged) + model.Beatmap = realm.Find(model.Beatmap.ID); + + if (!model.Ruleset.IsManaged) + model.Ruleset = realm.Find(model.Ruleset.ShortName); + + return Task.CompletedTask; + } } } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 5a443ec48e..9fc0d70cc0 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -1038,19 +1038,17 @@ namespace osu.Game.Screens.Play 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. + var importableScore = score.ScoreInfo.DeepClone(); + // For the time being, online ID responses are not really useful for anything. // In addition, the IDs provided via new (lazer) endpoints are based on a different autoincrement from legacy (stable) scores. // // Until we better define the server-side logic behind this, let's not store the online ID to avoid potential unique constraint // conflicts across various systems (ie. solo and multiplayer). - long onlineScoreId = score.ScoreInfo.OnlineID; + importableScore.OnlineID = -1; - score.ScoreInfo.OnlineID = -1; - - await scoreManager.Import(score.ScoreInfo, replayReader).ConfigureAwait(false); - - // ... And restore the online ID for other processes to handle correctly (e.g. de-duplication for the results screen). - score.ScoreInfo.OnlineID = onlineScoreId; + await scoreManager.Import(importableScore, replayReader).ConfigureAwait(false); } /// diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index f2654cbdee..caa994a98b 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -149,7 +149,10 @@ namespace osu.Game.Screens.Select.Leaderboards { using (var realm = realmFactory.CreateContext()) { - var scores = realm.All().Where(s => !s.DeletePending && s.BeatmapInfo.ID == fetchBeatmapInfo.ID && s.Ruleset.OnlineID == ruleset.Value.ID); + var scores = realm.All() + .AsEnumerable() + // TODO: update to use a realm filter directly (or at least figure out the beatmap part to reduce scope). + .Where(s => !s.DeletePending && s.BeatmapInfo.ID == fetchBeatmapInfo.ID && s.Ruleset.OnlineID == ruleset.Value.ID); if (filterMods && !mods.Value.Any()) { @@ -164,6 +167,8 @@ namespace osu.Game.Screens.Select.Leaderboards scores = scores.Where(s => s.Mods.Any(m => selectedMods.Contains(m.Acronym))); } + scores = scores.Detach(); + scoreManager.OrderByTotalScoreAsync(scores.ToArray(), cancellationToken) .ContinueWith(ordered => scoresCallback?.Invoke(ordered.Result), TaskContinuationOptions.OnlyOnRanToCompletion); From aaefd72c6917a2f497b8883b81116406cd29fc5d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 7 Jan 2022 22:57:12 +0900 Subject: [PATCH 236/996] Handle ignored mappings locally in `Detach` configuration --- osu.Game/Beatmaps/BeatmapSetInfo.cs | 3 --- osu.Game/Database/RealmObjectExtensions.cs | 4 +++- osu.Game/Scoring/ScoreInfo.cs | 8 -------- 3 files changed, 3 insertions(+), 12 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index 1cc6a96f40..f6ca184ea3 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using AutoMapper; using osu.Framework.Testing; using osu.Game.Database; using osu.Game.Extensions; @@ -76,10 +75,8 @@ namespace osu.Game.Beatmaps public bool Equals(IBeatmapSetInfo? other) => other is BeatmapSetInfo b && Equals(b); - [IgnoreMap] IEnumerable IBeatmapSetInfo.Beatmaps => Beatmaps; - [IgnoreMap] IEnumerable IHasNamedFiles.Files => Files; } } diff --git a/osu.Game/Database/RealmObjectExtensions.cs b/osu.Game/Database/RealmObjectExtensions.cs index dfa3076448..9827ca6edd 100644 --- a/osu.Game/Database/RealmObjectExtensions.cs +++ b/osu.Game/Database/RealmObjectExtensions.cs @@ -22,7 +22,9 @@ namespace osu.Game.Database private static readonly IMapper mapper = new MapperConfiguration(c => { c.ShouldMapField = fi => false; - c.ShouldMapProperty = pi => true; + // If we want to limit this further, we can avoid mapping properties with no setter that are not IList<>. + // Takes a bit of effort to determine whether this is the case though, see https://stackoverflow.com/questions/951536/how-do-i-tell-whether-a-type-implements-ilist + c.ShouldMapProperty = pi => pi.GetMethod?.IsPublic == true; c.CreateMap(); c.CreateMap(); diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index c07f4a4f87..65c31951b8 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using AutoMapper; using Newtonsoft.Json; using osu.Framework.Localisation; using osu.Framework.Testing; @@ -113,16 +112,9 @@ namespace osu.Game.Scoring [MapTo(nameof(Rank))] public int RankInt { get; set; } - [IgnoreMap] IRulesetInfo IScoreInfo.Ruleset => Ruleset; - - [IgnoreMap] IBeatmapInfo IScoreInfo.Beatmap => Beatmap; - - [IgnoreMap] IUser IScoreInfo.User => User; - - [IgnoreMap] IEnumerable IHasNamedFiles.Files => Files; #region Properties required to make things work with existing usages From 6919df18faf5d11a9ff2d05ea3d96c773b43f446 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 7 Jan 2022 22:57:22 +0900 Subject: [PATCH 237/996] Fix incorrect ordering and grouping of difficulties at song select --- osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs | 2 ++ osu.Game/Screens/Select/Carousel/SetPanelContent.cs | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index f2700e9053..b2b3b5411c 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -39,6 +39,8 @@ namespace osu.Game.Screens.Select.Carousel beatmapSet.Beatmaps .Where(b => !b.Hidden) + .OrderBy(b => b.RulesetID) + .ThenBy(b => b.StarRating) .Select(b => new CarouselBeatmap(b)) .ForEach(AddChild); } diff --git a/osu.Game/Screens/Select/Carousel/SetPanelContent.cs b/osu.Game/Screens/Select/Carousel/SetPanelContent.cs index f2054677b0..619806f96e 100644 --- a/osu.Game/Screens/Select/Carousel/SetPanelContent.cs +++ b/osu.Game/Screens/Select/Carousel/SetPanelContent.cs @@ -87,7 +87,8 @@ namespace osu.Game.Screens.Select.Carousel var beatmaps = carouselSet.Beatmaps.ToList(); return beatmaps.Count > maximum_difficulty_icons - ? (IEnumerable)beatmaps.GroupBy(b => b.BeatmapInfo.Ruleset).Select(group => new FilterableGroupedDifficultyIcon(group.ToList(), group.Key)) + ? (IEnumerable)beatmaps.GroupBy(b => b.BeatmapInfo.RulesetID) + .Select(group => new FilterableGroupedDifficultyIcon(group.ToList(), group.Last().BeatmapInfo.Ruleset)) : beatmaps.Select(b => new FilterableDifficultyIcon(b)); } } From 52ca6491594f7179c873ab782ea77802a9b5ae9d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 7 Jan 2022 23:55:53 +0900 Subject: [PATCH 238/996] Fix results screen test failures due to relation query --- .../Visual/Ranking/TestSceneResultsScreen.cs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index 666cbf02b5..14ceb8f4d4 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs @@ -13,8 +13,10 @@ using osu.Framework.Screens; using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Beatmaps; +using osu.Game.Database; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; +using osu.Game.Rulesets; using osu.Game.Scoring; using osu.Game.Screens; using osu.Game.Screens.Play; @@ -23,6 +25,7 @@ using osu.Game.Screens.Ranking.Statistics; using osu.Game.Tests.Resources; using osuTK; using osuTK.Input; +using Realms; namespace osu.Game.Tests.Visual.Ranking { @@ -32,13 +35,22 @@ namespace osu.Game.Tests.Visual.Ranking [Resolved] private BeatmapManager beatmaps { get; set; } + [Resolved] + private RealmContextFactory realmContextFactory { get; set; } + protected override void LoadComplete() { base.LoadComplete(); - var beatmapInfo = beatmaps.QueryBeatmap(b => b.RulesetID == 0); - if (beatmapInfo != null) - Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmapInfo); + using (var realm = realmContextFactory.CreateContext()) + { + var beatmapInfo = realm.All() + .Filter($"{nameof(BeatmapInfo.Ruleset)}.{nameof(RulesetInfo.OnlineID)} = $0", 0) + .FirstOrDefault(); + + if (beatmapInfo != null) + Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmapInfo); + } } [Test] From 76670a8faa9c964920e4a6277f4f2dd0bad4281b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 7 Jan 2022 23:56:04 +0900 Subject: [PATCH 239/996] Fix `BeatmapDifficultyCache` not working with detached beatmaps --- osu.Game/Beatmaps/BeatmapDifficultyCache.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs index f760c25170..981120c5a8 100644 --- a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs +++ b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs @@ -136,7 +136,7 @@ namespace osu.Game.Beatmaps var localRulesetInfo = rulesetInfo as RulesetInfo; // Difficulty can only be computed if the beatmap and ruleset are locally available. - if (localBeatmapInfo?.IsManaged != true || localRulesetInfo == null) + if (localBeatmapInfo?.Ruleset == null || localRulesetInfo == null) { // If not, fall back to the existing star difficulty (e.g. from an online source). return Task.FromResult(new StarDifficulty(beatmapInfo.StarRating, (beatmapInfo as IBeatmapOnlineInfo)?.MaxCombo ?? 0)); From 13401a884667779bfb9da2b37e12163a551b6179 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 8 Jan 2022 00:39:39 +0900 Subject: [PATCH 240/996] Better handle Statistics to avoid losing data --- osu.Game/Scoring/ScoreInfo.cs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 65c31951b8..6a9805ba6a 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -83,21 +83,22 @@ namespace osu.Game.Scoring public RulesetInfo Ruleset { get; set; } = null!; + private Dictionary? statistics; + [Ignored] public Dictionary Statistics { - // TODO: this is dangerous. a get operation may then modify the dictionary, which would be a fresh copy that is not persisted with the model. - // this is already the case in multiple locations. get { - if (string.IsNullOrEmpty(StatisticsJson)) - return new Dictionary(); + if (statistics != null) + return statistics; - return JsonConvert.DeserializeObject>(StatisticsJson) ?? new Dictionary(); + if (!string.IsNullOrEmpty(StatisticsJson)) + statistics = JsonConvert.DeserializeObject>(StatisticsJson); + + return statistics ??= new Dictionary(); } - // .. todo - // ReSharper disable once ValueParameterNotUsed - set => JsonConvert.SerializeObject(StatisticsJson); + set => statistics = value; } [MapTo("Statistics")] From e74a5022c99eb377e6c5010f3d7c7c0385a28c7a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 8 Jan 2022 00:40:14 +0900 Subject: [PATCH 241/996] Fix multiple tests via null checks and changing `ToLive` to `Detach` flow --- osu.Game.Tests/Scores/IO/ImportScoreTest.cs | 5 +++-- .../Skins/TestSceneBeatmapSkinResources.cs | 2 +- .../Visual/Navigation/TestScenePresentBeatmap.cs | 7 +++---- .../Visual/SongSelect/TestSceneBeatmapCarousel.cs | 2 +- osu.Game/Beatmaps/BeatmapManager.cs | 1 + osu.Game/OsuGame.cs | 4 ++-- osu.Game/Scoring/ScoreManager.cs | 4 ++-- osu.Game/Scoring/ScoreModelManager.cs | 4 ++++ osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- osu.Game/Stores/BeatmapImporter.cs | 7 +++++++ .../Visual/Multiplayer/TestMultiplayerClient.cs | 12 ++++++------ 11 files changed, 31 insertions(+), 19 deletions(-) diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs index 3e6a877a35..fc7f3229df 100644 --- a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs +++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs @@ -10,6 +10,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Platform; using osu.Game.Beatmaps; +using osu.Game.Database; using osu.Game.IO.Archives; using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets.Mods; @@ -142,7 +143,7 @@ namespace osu.Game.Tests.Scores.IO var scoreManager = osu.Dependencies.Get(); beatmapManager.Delete(beatmapManager.QueryBeatmapSet(s => s.Beatmaps.Any(b => b.ID == imported.BeatmapInfo.ID))!.Value); - Assert.That(scoreManager.Query(s => s.Equals(imported)).Value.DeletePending, Is.EqualTo(true)); + Assert.That(scoreManager.Query(s => s.Equals(imported)).DeletePending, Is.EqualTo(true)); var secondImport = await LoadScoreIntoOsu(osu, imported); Assert.That(secondImport, Is.Null); @@ -187,7 +188,7 @@ namespace osu.Game.Tests.Scores.IO var scoreManager = osu.Dependencies.Get(); await scoreManager.Import(score, archive); - return scoreManager.Query(_ => true).Value; + return scoreManager.Query(_ => true).Detach(); } internal class TestArchiveReader : ArchiveReader diff --git a/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs b/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs index c20ab84a68..98732bfb58 100644 --- a/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs +++ b/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs @@ -26,7 +26,7 @@ namespace osu.Game.Tests.Skins private void load() { var imported = beatmaps.Import(new ZipArchiveReader(TestResources.OpenResource("Archives/ogg-beatmap.osz"))).GetResultSafely(); - beatmap = beatmaps.GetWorkingBeatmap(imported.Value.Beatmaps[0]); + beatmap = beatmaps.GetWorkingBeatmap(imported?.Value.Beatmaps[0]); beatmap.LoadTrack(); } diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs b/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs index 81aacf61cd..69b30ec6a0 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs @@ -97,7 +97,6 @@ namespace osu.Game.Tests.Visual.Navigation BeatmapSetInfo imported = null; AddStep($"import beatmap {i}", () => { - var difficulty = new BeatmapDifficulty(); var metadata = new BeatmapMetadata { Artist = "SomeArtist", @@ -115,18 +114,18 @@ namespace osu.Game.Tests.Visual.Navigation { OnlineID = i * 1024, Metadata = metadata, - BaseDifficulty = difficulty, + BaseDifficulty = new BeatmapDifficulty(), Ruleset = ruleset ?? new OsuRuleset().RulesetInfo }, new BeatmapInfo { OnlineID = i * 2048, Metadata = metadata, - BaseDifficulty = difficulty, + BaseDifficulty = new BeatmapDifficulty(), Ruleset = ruleset ?? new OsuRuleset().RulesetInfo }, } - }).GetResultSafely().Value; + }).GetResultSafely()?.Value; }); AddAssert($"import {i} succeeded", () => imported != null); diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index b1414d5896..cbcfc07202 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -76,7 +76,7 @@ namespace osu.Game.Tests.Visual.SongSelect { AddStep("store selection", () => selection = carousel.SelectedBeatmapInfo); if (isIterating) - AddUntilStep("selection changed", () => !carousel.SelectedBeatmapInfo.Equals(selection)); + AddUntilStep("selection changed", () => !carousel.SelectedBeatmapInfo?.Equals(selection) == true); else AddUntilStep("selection not changed", () => carousel.SelectedBeatmapInfo.Equals(selection)); } diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 2479390700..4f6b74ba0a 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -200,6 +200,7 @@ namespace osu.Game.Beatmaps /// The query. /// The first result for the provided query, or null if no results were found. public BeatmapInfo? QueryBeatmap(Expression> query) => beatmapModelManager.QueryBeatmap(query)?.Detach(); + // TODO: move detach to usages? /// /// Saves an file against a given . diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 149de9bc76..b9e0aac6ef 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -491,10 +491,10 @@ namespace osu.Game ScoreInfo databasedScoreInfo = null; if (score.OnlineID > 0) - databasedScoreInfo = ScoreManager.Query(s => s.OnlineID == score.OnlineID)?.Value; + databasedScoreInfo = ScoreManager.Query(s => s.OnlineID == score.OnlineID); if (score is ScoreInfo scoreInfo) - databasedScoreInfo ??= ScoreManager.Query(s => s.Hash == scoreInfo.Hash)?.Value; + databasedScoreInfo ??= ScoreManager.Query(s => s.Hash == scoreInfo.Hash); if (databasedScoreInfo == null) { diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 159c3c2da0..62e61d3a94 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -49,10 +49,10 @@ namespace osu.Game.Scoring /// /// The query. /// The first result for the provided query, or null if no results were found. - public ILive Query(Expression> query) + public ScoreInfo Query(Expression> query) { using (var context = contextFactory.CreateContext()) - return context.All().FirstOrDefault(query)?.ToLive(contextFactory); + return context.All().FirstOrDefault(query)?.Detach(); } /// diff --git a/osu.Game/Scoring/ScoreModelManager.cs b/osu.Game/Scoring/ScoreModelManager.cs index 8f04e50f63..27b02fc2f0 100644 --- a/osu.Game/Scoring/ScoreModelManager.cs +++ b/osu.Game/Scoring/ScoreModelManager.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Newtonsoft.Json; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Beatmaps; @@ -63,6 +64,9 @@ namespace osu.Game.Scoring if (!model.Ruleset.IsManaged) model.Ruleset = realm.Find(model.Ruleset.ShortName); + if (string.IsNullOrEmpty(model.StatisticsJson)) + model.StatisticsJson = JsonConvert.SerializeObject(model.Statistics); + return Task.CompletedTask; } } diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 226d2df619..ffb5c6a2a5 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -111,7 +111,7 @@ namespace osu.Game.Screens.Select newRoot.AddChildren(beatmapSets.Select(createCarouselSet).Where(g => g != null)); root = newRoot; - if (selectedBeatmapSet != null && !beatmapSets.Contains(selectedBeatmapSet.BeatmapSet)) + if (selectedBeatmapSet != null && (!selectedBeatmapSet.BeatmapSet.IsManaged || !beatmapSets.Contains(selectedBeatmapSet.BeatmapSet))) selectedBeatmapSet = null; Scroll.Clear(false); diff --git a/osu.Game/Stores/BeatmapImporter.cs b/osu.Game/Stores/BeatmapImporter.cs index 2c73bd22eb..e0eee84456 100644 --- a/osu.Game/Stores/BeatmapImporter.cs +++ b/osu.Game/Stores/BeatmapImporter.cs @@ -59,8 +59,15 @@ namespace osu.Game.Stores beatmapSet.Beatmaps.AddRange(createBeatmapDifficulties(beatmapSet.Files, realm)); foreach (BeatmapInfo b in beatmapSet.Beatmaps) + { b.BeatmapSet = beatmapSet; + // ensure we aren't trying to add a new ruleset to the database + // this can happen in tests, mostly + if (!b.Ruleset.IsManaged) + b.Ruleset = realm.Find(b.Ruleset.ShortName); + } + validateOnlineIds(beatmapSet, realm); bool hadOnlineIDs = beatmapSet.Beatmaps.Any(b => b.OnlineID > 0); diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 76acac3f8b..15ede6cc26 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -409,18 +409,18 @@ namespace osu.Game.Tests.Visual.Multiplayer public override Task GetAPIBeatmap(int beatmapId, CancellationToken cancellationToken = default) { - IBeatmapSetInfo? set = roomManager.ServerSideRooms.SelectMany(r => r.Playlist) - .FirstOrDefault(p => p.BeatmapID == beatmapId)?.Beatmap.Value.BeatmapSet - ?? beatmaps.QueryBeatmap(b => b.OnlineID == beatmapId)?.BeatmapSet; + IBeatmapInfo? beatmap = roomManager.ServerSideRooms.SelectMany(r => r.Playlist) + .FirstOrDefault(p => p.BeatmapID == beatmapId)?.Beatmap.Value + ?? beatmaps.QueryBeatmap(b => b.OnlineID == beatmapId); - if (set == null) + if (beatmap == null) throw new InvalidOperationException("Beatmap not found."); return Task.FromResult(new APIBeatmap { - BeatmapSet = new APIBeatmapSet { OnlineID = set.OnlineID }, + BeatmapSet = new APIBeatmapSet { OnlineID = beatmap.BeatmapSet?.OnlineID ?? -1 }, OnlineID = beatmapId, - Checksum = set.Beatmaps.First(b => b.OnlineID == beatmapId).MD5Hash + Checksum = beatmap.MD5Hash }); } From b619ff126434b0e37356b8608afb82b5cc5a478a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 8 Jan 2022 14:03:04 +0900 Subject: [PATCH 242/996] Reattach detached items on delete/undelete --- osu.Game/Stores/RealmArchiveModelManager.cs | 26 +++++++++++++++------ 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/osu.Game/Stores/RealmArchiveModelManager.cs b/osu.Game/Stores/RealmArchiveModelManager.cs index 8698799b4a..c53348fd92 100644 --- a/osu.Game/Stores/RealmArchiveModelManager.cs +++ b/osu.Game/Stores/RealmArchiveModelManager.cs @@ -156,19 +156,31 @@ namespace osu.Game.Stores public bool Delete(TModel item) { - if (item.DeletePending) - return false; + using (var realm = ContextFactory.CreateContext()) + { + if (!item.IsManaged) + item = realm.Find(item.ID); - item.Realm.Write(r => item.DeletePending = true); - return true; + if (item?.DeletePending != false) + return false; + + realm.Write(r => item.DeletePending = true); + return true; + } } public void Undelete(TModel item) { - if (!item.DeletePending) - return; + using (var realm = ContextFactory.CreateContext()) + { + if (!item.IsManaged) + item = realm.Find(item.ID); - item.Realm.Write(r => item.DeletePending = false); + if (item?.DeletePending != true) + return; + + realm.Write(r => item.DeletePending = false); + } } // TODO: delete or abstract From a3c70ccdfc15b02e4db479ae06857d2be3133e8a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 8 Jan 2022 14:03:23 +0900 Subject: [PATCH 243/996] Fix OnlineAvailabilityTracker referencing a value in query that could potentially be null --- osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs index d344846e5f..1832c116bc 100644 --- a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs @@ -86,7 +86,7 @@ namespace osu.Game.Online.Rooms realmSubscription?.Dispose(); realmSubscription = realmContextFactory.Context .All() - .Where(s => s.OnlineID == SelectedItem.Value.BeatmapID) // TODO: figure out if we need this hash match in the subscription || s.ID == matchingHash?.ID) + .Where(s => s.OnlineID == item.NewValue.BeatmapID) // TODO: figure out if we need this hash match in the subscription || s.ID == matchingHash?.ID) .QueryAsyncWithNotifications((items, changes, ___) => { if (changes == null) From ba4ef0926fa21ebce350cc5689772be459d0584f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 8 Jan 2022 15:26:44 +0900 Subject: [PATCH 244/996] Remove incorrect test fixture specification --- osu.Game.Tests/Beatmaps/IO/BeatmapImportHelper.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Beatmaps/IO/BeatmapImportHelper.cs b/osu.Game.Tests/Beatmaps/IO/BeatmapImportHelper.cs index 8e67ecd818..44f6943871 100644 --- a/osu.Game.Tests/Beatmaps/IO/BeatmapImportHelper.cs +++ b/osu.Game.Tests/Beatmaps/IO/BeatmapImportHelper.cs @@ -15,7 +15,6 @@ using osu.Game.Tests.Resources; namespace osu.Game.Tests.Beatmaps.IO { - [TestFixture] public static class BeatmapImportHelper { public static async Task LoadQuickOszIntoOsu(OsuGameBase osu) From f2f1adb792c8931b30298b62199c3e439b8b5128 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 Jan 2022 12:28:09 +0900 Subject: [PATCH 245/996] Update `FilterMatchingTest` and filter code to use ruleset's `OnlineID` --- osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs index 8ba3d1a6c7..3d78043c73 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs @@ -17,7 +17,6 @@ namespace osu.Game.Tests.NonVisual.Filtering private BeatmapInfo getExampleBeatmap() => new BeatmapInfo { Ruleset = new RulesetInfo { OnlineID = 0 }, - RulesetID = 0, StarRating = 4.0d, BaseDifficulty = new BeatmapDifficulty { From e5af673b01866d6b197bb71817ef2eb1234d8af1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 Jan 2022 12:36:11 +0900 Subject: [PATCH 246/996] Fix incorrect `BeatmapInfo.ToString` implementation --- osu.Game/Beatmaps/BeatmapInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 153f40e39d..8bfcd947ac 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -184,7 +184,7 @@ namespace osu.Game.Beatmaps public BeatmapInfo Clone() => this.Detach(); - public override string ToString() => Metadata?.ToString() ?? base.ToString(); + public override string ToString() => this.GetDisplayTitle(); #endregion } From 7e7784b78a616d5128f9e03e03e8c7473d74e155 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 Jan 2022 12:39:49 +0900 Subject: [PATCH 247/996] Fix incorrect access to `ILive` in `BeatmapSkinResources` tests --- osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs b/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs index 98732bfb58..fe0423dcfc 100644 --- a/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs +++ b/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs @@ -26,8 +26,12 @@ namespace osu.Game.Tests.Skins private void load() { var imported = beatmaps.Import(new ZipArchiveReader(TestResources.OpenResource("Archives/ogg-beatmap.osz"))).GetResultSafely(); - beatmap = beatmaps.GetWorkingBeatmap(imported?.Value.Beatmaps[0]); - beatmap.LoadTrack(); + + imported?.PerformRead(s => + { + beatmap = beatmaps.GetWorkingBeatmap(s.Beatmaps[0]); + beatmap.LoadTrack(); + }); } [Test] From 62517137961b606739e46477bce55393105b1d1d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 Jan 2022 12:56:25 +0900 Subject: [PATCH 248/996] Add missing `Ruleset` in `ReplayRecorder` tests --- osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs index e6361a15d7..4eab1a21da 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs @@ -64,7 +64,11 @@ namespace osu.Game.Tests.Visual.Gameplay Recorder = recorder = new TestReplayRecorder(new Score { Replay = replay, - ScoreInfo = { BeatmapInfo = gameplayState.Beatmap.BeatmapInfo } + ScoreInfo = + { + BeatmapInfo = gameplayState.Beatmap.BeatmapInfo, + Ruleset = new OsuRuleset().RulesetInfo, + } }) { ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos), From fa7dddcf3c1e3af3d280a58d751b9c50e2ce3ff7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 Jan 2022 13:03:25 +0900 Subject: [PATCH 249/996] Fix `TestScenePresentScore` sharing metadata/difficulty across multiple beatmaps --- .../Navigation/TestScenePresentScore.cs | 32 +++++++++++-------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs index dbff5964df..f9649db135 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs @@ -8,6 +8,7 @@ using osu.Framework.Extensions; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; +using osu.Game.Online.API; using osu.Game.Rulesets; using osu.Game.Rulesets.Mania; using osu.Game.Rulesets.Osu; @@ -28,14 +29,6 @@ namespace osu.Game.Tests.Visual.Navigation { AddStep("import beatmap", () => { - var difficulty = new BeatmapDifficulty(); - var metadata = new BeatmapMetadata - { - Artist = "SomeArtist", - AuthorString = "SomeAuthor", - Title = "import" - }; - beatmap = Game.BeatmapManager.Import(new BeatmapSetInfo { Hash = Guid.NewGuid().ToString(), @@ -45,19 +38,29 @@ namespace osu.Game.Tests.Visual.Navigation new BeatmapInfo { OnlineID = 1 * 1024, - Metadata = metadata, - BaseDifficulty = difficulty, + Metadata = new BeatmapMetadata + { + Artist = "SomeArtist", + AuthorString = "SomeAuthor", + Title = "import" + }, + BaseDifficulty = new BeatmapDifficulty(), Ruleset = new OsuRuleset().RulesetInfo }, new BeatmapInfo { OnlineID = 1 * 2048, - Metadata = metadata, - BaseDifficulty = difficulty, + Metadata = new BeatmapMetadata + { + Artist = "SomeArtist", + AuthorString = "SomeAuthor", + Title = "import" + }, + BaseDifficulty = new BeatmapDifficulty(), Ruleset = new OsuRuleset().RulesetInfo }, } - }).GetResultSafely().Value; + }).GetResultSafely()?.Value; }); } @@ -130,7 +133,8 @@ namespace osu.Game.Tests.Visual.Navigation Hash = Guid.NewGuid().ToString(), OnlineID = i, BeatmapInfo = beatmap.Beatmaps.First(), - Ruleset = ruleset ?? new OsuRuleset().RulesetInfo + Ruleset = ruleset ?? new OsuRuleset().RulesetInfo, + User = new GuestUser(), }).GetResultSafely().Value; }); From 82259ee0720df42422ee42e21c2bd553d41c9038 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 Jan 2022 13:08:44 +0900 Subject: [PATCH 250/996] Improve legibility of `RulesetInfo.Equals` --- osu.Game/Rulesets/RulesetInfo.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/RulesetInfo.cs b/osu.Game/Rulesets/RulesetInfo.cs index 99b4f5915f..5d5cbe6328 100644 --- a/osu.Game/Rulesets/RulesetInfo.cs +++ b/osu.Game/Rulesets/RulesetInfo.cs @@ -47,7 +47,11 @@ namespace osu.Game.Rulesets public bool Available { get; set; } - public bool Equals(RulesetInfo? other) => other != null && OnlineID == other.OnlineID && Available == other.Available && Name == other.Name && InstantiationInfo == other.InstantiationInfo; + public bool Equals(RulesetInfo? other) => other != null + && OnlineID == other.OnlineID + && Available == other.Available + && Name == other.Name + && InstantiationInfo == other.InstantiationInfo; public bool Equals(IRulesetInfo? other) => other is RulesetInfo b && Equals(b); From 5dd0bb12183dd065e01be932aca007e0faf503b4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 Jan 2022 13:08:57 +0900 Subject: [PATCH 251/996] Ensure `Score` created by `GameplayState` has a valid ruleset --- osu.Game/Screens/Play/GameplayState.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/GameplayState.cs b/osu.Game/Screens/Play/GameplayState.cs index 44f72022f7..83881f739d 100644 --- a/osu.Game/Screens/Play/GameplayState.cs +++ b/osu.Game/Screens/Play/GameplayState.cs @@ -50,7 +50,13 @@ namespace osu.Game.Screens.Play { Beatmap = beatmap; Ruleset = ruleset; - Score = score ?? new Score(); + Score = score ?? new Score + { + ScoreInfo = + { + Ruleset = ruleset.RulesetInfo + } + }; Mods = mods ?? ArraySegment.Empty; } From c831e9107a09472bce2f6f5e90b63901fd107df5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 Jan 2022 13:16:39 +0900 Subject: [PATCH 252/996] Fix `BeatmapInfo.Clone` potentially not cloning if already detached --- osu.Game/Beatmaps/BeatmapInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 8bfcd947ac..cea5f4531d 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -182,7 +182,7 @@ namespace osu.Game.Beatmaps public int BeatmapVersion; - public BeatmapInfo Clone() => this.Detach(); + public BeatmapInfo Clone() => (BeatmapInfo)this.Detach().MemberwiseClone(); public override string ToString() => this.GetDisplayTitle(); From 83ccbc1d132aff271899922e2c69d80be97d24ec Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 Jan 2022 13:25:23 +0900 Subject: [PATCH 253/996] Mention safety failures of Beatmap/Score constructors --- osu.Game/Beatmaps/BeatmapInfo.cs | 2 +- osu.Game/Scoring/ScoreInfo.cs | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index cea5f4531d..32eff68d5c 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -74,7 +74,7 @@ namespace osu.Game.Beatmaps } [UsedImplicitly] - public BeatmapInfo() + public BeatmapInfo() // TODO: this bypasses null safeties. needs to be hidden from user api (only should be used by realm). { } diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 6a9805ba6a..cb81901d0a 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; using Newtonsoft.Json; using osu.Framework.Localisation; using osu.Framework.Testing; @@ -44,6 +45,18 @@ namespace osu.Game.Scoring [MapTo("User")] public RealmUser RealmUser { get; set; } = null!; + public ScoreInfo(BeatmapInfo beatmap, RulesetInfo ruleset, RealmUser user) + { + Ruleset = ruleset; + Beatmap = beatmap; + RealmUser = user; + } + + [UsedImplicitly] + public ScoreInfo() // TODO: this bypasses null safeties. needs to be hidden from user api (only should be used by realm). + { + } + // TODO: this is a bit temporary to account for the fact that this class is used to ferry API user data to certain UI components. // Eventually we should either persist enough information to realm to not require the API lookups, or perform the API lookups locally. private APIUser? user; From d19a9a0ba32b6a0cf42455b6abbf227615aeaaf8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 Jan 2022 13:37:53 +0900 Subject: [PATCH 254/996] Remove assertion of `ScoreInfo.Combo` being database persisted --- osu.Game.Tests/Scores/IO/ImportScoreTest.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs index fc7f3229df..a09acbc8a7 100644 --- a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs +++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs @@ -50,7 +50,6 @@ namespace osu.Game.Tests.Scores.IO Assert.AreEqual(toImport.TotalScore, imported.TotalScore); Assert.AreEqual(toImport.Accuracy, imported.Accuracy); Assert.AreEqual(toImport.MaxCombo, imported.MaxCombo); - Assert.AreEqual(toImport.Combo, imported.Combo); Assert.AreEqual(toImport.User.Username, imported.User.Username); Assert.AreEqual(toImport.Date, imported.Date); Assert.AreEqual(toImport.OnlineID, imported.OnlineID); From 463a185605be7dd03488ea3e844f7c1bbaacb5c6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 Jan 2022 13:38:12 +0900 Subject: [PATCH 255/996] Fix many instances of `User` being null in score import tests --- osu.Game.Tests/Scores/IO/ImportScoreTest.cs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs index a09acbc8a7..1fa2a363b9 100644 --- a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs +++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs @@ -72,6 +72,7 @@ namespace osu.Game.Tests.Scores.IO var toImport = new ScoreInfo { + User = new APIUser { Username = "Test user" }, Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() }, }; @@ -98,6 +99,7 @@ namespace osu.Game.Tests.Scores.IO var toImport = new ScoreInfo { + User = new APIUser { Username = "Test user" }, Statistics = new Dictionary { { HitResult.Perfect, 100 }, @@ -128,6 +130,7 @@ namespace osu.Game.Tests.Scores.IO var toImport = new ScoreInfo { + User = new APIUser { Username = "Test user" }, Hash = Guid.NewGuid().ToString(), Statistics = new Dictionary { @@ -163,12 +166,20 @@ namespace osu.Game.Tests.Scores.IO { var osu = LoadOsuIntoHost(host, true); - await LoadScoreIntoOsu(osu, new ScoreInfo { OnlineID = 2 }, new TestArchiveReader()); + await LoadScoreIntoOsu(osu, new ScoreInfo + { + User = new APIUser { Username = "Test user" }, + OnlineID = 2 + }, new TestArchiveReader()); var scoreManager = osu.Dependencies.Get(); // Note: A new score reference is used here since the import process mutates the original object to set an ID - Assert.That(scoreManager.IsAvailableLocally(new ScoreInfo { OnlineID = 2 })); + Assert.That(scoreManager.IsAvailableLocally(new ScoreInfo + { + User = new APIUser { Username = "Test user" }, + OnlineID = 2 + })); } finally { From 33b5fa347375e3285e2b9752aeae685e9ee98a50 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 Jan 2022 13:38:23 +0900 Subject: [PATCH 256/996] Detach score during import tests to ensure original object doesn't get managed --- osu.Game.Tests/Scores/IO/ImportScoreTest.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs index 1fa2a363b9..e14159da8e 100644 --- a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs +++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs @@ -192,6 +192,8 @@ namespace osu.Game.Tests.Scores.IO { var beatmapManager = osu.Dependencies.Get(); + // clone to avoid attaching the input score to realm. + score = score.DeepClone(); score.BeatmapInfo ??= beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps.First(); score.Ruleset ??= new OsuRuleset().RulesetInfo; From c8641389afc681a65b2c1e93e0baf1b1538579b4 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 12 Jan 2022 11:08:12 +0300 Subject: [PATCH 257/996] Update macOS minimum supported version in line with .NET 5 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f18c5e76f9..b1dfcab416 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ If you are looking to install or test osu! without setting up a development envi **Latest build:** -| [Windows 8.1+ (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | [macOS 10.12+](https://github.com/ppy/osu/releases/latest/download/osu.app.zip) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS 10+](https://osu.ppy.sh/home/testflight) | [Android 5+](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk) +| [Windows 8.1+ (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | [macOS 10.15+](https://github.com/ppy/osu/releases/latest/download/osu.app.zip) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS 10+](https://osu.ppy.sh/home/testflight) | [Android 5+](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk) | ------------- | ------------- | ------------- | ------------- | ------------- | - The iOS testflight link may fill up (Apple has a hard limit of 10,000 users). We reset it occasionally when this happens. Please do not ask about this. Check back regularly for link resets or follow [peppy](https://twitter.com/ppy) on twitter for announcements of link resets. From ba62d2c756168940154faa9c7a2beb68ba7ac98f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 Jan 2022 13:38:37 +0900 Subject: [PATCH 258/996] Fix `ScoreInfo` oversights causing automapper to fail Parameter in ctor *has* to be named `realmUser` else automapper will try to map to the `User` property. --- osu.Game/Scoring/ScoreInfo.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index cb81901d0a..28a6d67b6a 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using AutoMapper; using JetBrains.Annotations; using Newtonsoft.Json; using osu.Framework.Localisation; @@ -45,11 +46,11 @@ namespace osu.Game.Scoring [MapTo("User")] public RealmUser RealmUser { get; set; } = null!; - public ScoreInfo(BeatmapInfo beatmap, RulesetInfo ruleset, RealmUser user) + public ScoreInfo(BeatmapInfo beatmap, RulesetInfo ruleset, RealmUser realmUser) { Ruleset = ruleset; Beatmap = beatmap; - RealmUser = user; + RealmUser = realmUser; } [UsedImplicitly] @@ -61,6 +62,7 @@ namespace osu.Game.Scoring // Eventually we should either persist enough information to realm to not require the API lookups, or perform the API lookups locally. private APIUser? user; + [IgnoreMap] public APIUser User { get => user ??= new APIUser @@ -164,7 +166,6 @@ namespace osu.Game.Scoring [Ignored] public bool Passed { get; set; } = true; - [Ignored] public int Combo { get; set; } /// From 43c7b0d2c8a31df449c1042bf721f8e79c0880e1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 Jan 2022 13:53:38 +0900 Subject: [PATCH 259/996] Fix unsupported realm operations in multiple tests --- osu.Game.Tests/Scores/IO/ImportScoreTest.cs | 4 ++-- .../Visual/SongSelect/TestScenePlaySongSelect.cs | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs index e14159da8e..a1d47351dd 100644 --- a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs +++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs @@ -144,8 +144,8 @@ namespace osu.Game.Tests.Scores.IO var beatmapManager = osu.Dependencies.Get(); var scoreManager = osu.Dependencies.Get(); - beatmapManager.Delete(beatmapManager.QueryBeatmapSet(s => s.Beatmaps.Any(b => b.ID == imported.BeatmapInfo.ID))!.Value); - Assert.That(scoreManager.Query(s => s.Equals(imported)).DeletePending, Is.EqualTo(true)); + beatmapManager.Delete(beatmapManager.GetAllUsableBeatmapSets().First(s => s.Beatmaps.Any(b => b.ID == imported.BeatmapInfo.ID))); + Assert.That(scoreManager.Query(s => s.ID == imported.ID).DeletePending, Is.EqualTo(true)); var secondImport = await LoadScoreIntoOsu(osu, imported); Assert.That(secondImport, Is.Null); diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 7b81f330d0..396bbc3dc5 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -796,8 +796,8 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestChangeRulesetWhilePresentingScore() { - BeatmapInfo getPresentBeatmap() => manager.QueryBeatmap(b => !b.BeatmapSet.DeletePending && b.RulesetID == 0); - BeatmapInfo getSwitchBeatmap() => manager.QueryBeatmap(b => !b.BeatmapSet.DeletePending && b.RulesetID == 1); + BeatmapInfo getPresentBeatmap() => manager.GetAllUsableBeatmapSets().Where(s => !s.DeletePending).SelectMany(s => s.Beatmaps).First(b => b.RulesetID == 0); + BeatmapInfo getSwitchBeatmap() => manager.GetAllUsableBeatmapSets().Where(s => !s.DeletePending).SelectMany(s => s.Beatmaps).First(b => b.RulesetID == 1); changeRuleset(0); @@ -828,8 +828,8 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestChangeBeatmapWhilePresentingScore() { - BeatmapInfo getPresentBeatmap() => manager.QueryBeatmap(b => !b.BeatmapSet.DeletePending && b.RulesetID == 0); - BeatmapInfo getSwitchBeatmap() => manager.QueryBeatmap(b => !b.BeatmapSet.DeletePending && b.RulesetID == 1); + BeatmapInfo getPresentBeatmap() => manager.GetAllUsableBeatmapSets().Where(s => !s.DeletePending).SelectMany(s => s.Beatmaps).First(b => b.RulesetID == 0); + BeatmapInfo getSwitchBeatmap() => manager.GetAllUsableBeatmapSets().Where(s => !s.DeletePending).SelectMany(s => s.Beatmaps).First(b => b.RulesetID == 1); changeRuleset(0); From 47390d7ec32e8d58a4b32985172010ba2a74dad1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 Jan 2022 13:59:33 +0900 Subject: [PATCH 260/996] Update handling of ruleset nullability when handling a game-wide change --- osu.Game/OsuGameBase.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index ac04be532a..d18ae6d2d8 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -424,9 +424,21 @@ namespace osu.Game private void onRulesetChanged(ValueChangedEvent r) { - Ruleset instance; + Ruleset instance = null; - if (r.NewValue?.Available != true || (instance = r.NewValue.CreateInstance()) == null) + try + { + if (r.NewValue?.Available == true) + { + instance = r.NewValue.CreateInstance(); + } + } + catch (Exception e) + { + Logger.Error(e, "Ruleset load failed and has been rolled back"); + } + + if (instance == null) { // reject the change if the ruleset is not available. Ruleset.Value = r.OldValue?.Available == true ? r.OldValue : RulesetStore.AvailableRulesets.First(); From d8e75a9de49168249a0bd478043e2f3228d26cc8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 Jan 2022 13:59:46 +0900 Subject: [PATCH 261/996] Reimplmeent `IsAvailableLocally` as an `abstract` method --- osu.Game/Scoring/ScoreModelManager.cs | 6 ++++++ osu.Game/Skinning/SkinModelManager.cs | 2 ++ osu.Game/Stores/BeatmapImporter.cs | 6 ++++++ osu.Game/Stores/RealmArchiveModelManager.cs | 3 +-- 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/osu.Game/Scoring/ScoreModelManager.cs b/osu.Game/Scoring/ScoreModelManager.cs index 27b02fc2f0..1068aba231 100644 --- a/osu.Game/Scoring/ScoreModelManager.cs +++ b/osu.Game/Scoring/ScoreModelManager.cs @@ -69,5 +69,11 @@ namespace osu.Game.Scoring return Task.CompletedTask; } + + public override bool IsAvailableLocally(ScoreInfo model) + { + using (var context = ContextFactory.CreateContext()) + return context.All().Any(b => b.OnlineID == model.OnlineID); + } } } diff --git a/osu.Game/Skinning/SkinModelManager.cs b/osu.Game/Skinning/SkinModelManager.cs index 822cb8efa0..66a1808dc1 100644 --- a/osu.Game/Skinning/SkinModelManager.cs +++ b/osu.Game/Skinning/SkinModelManager.cs @@ -262,5 +262,7 @@ namespace osu.Game.Skinning s.Hash = ComputeHash(s); }); } + + public override bool IsAvailableLocally(SkinInfo model) => false; } } diff --git a/osu.Game/Stores/BeatmapImporter.cs b/osu.Game/Stores/BeatmapImporter.cs index e0eee84456..429d3951cf 100644 --- a/osu.Game/Stores/BeatmapImporter.cs +++ b/osu.Game/Stores/BeatmapImporter.cs @@ -170,6 +170,12 @@ namespace osu.Game.Stores return existing.OnlineID == import.OnlineID && existingIds.SequenceEqual(importIds); } + public override bool IsAvailableLocally(BeatmapSetInfo model) + { + using (var context = ContextFactory.CreateContext()) + return context.All().Any(b => b.OnlineID == model.OnlineID); + } + public override string HumanisedModelName => "beatmap"; protected override BeatmapSetInfo? CreateModel(ArchiveReader reader) diff --git a/osu.Game/Stores/RealmArchiveModelManager.cs b/osu.Game/Stores/RealmArchiveModelManager.cs index c53348fd92..0b78071d16 100644 --- a/osu.Game/Stores/RealmArchiveModelManager.cs +++ b/osu.Game/Stores/RealmArchiveModelManager.cs @@ -183,8 +183,7 @@ namespace osu.Game.Stores } } - // TODO: delete or abstract - public virtual bool IsAvailableLocally(TModel model) => false; // Not relevant for skins since they can't be downloaded yet. + public abstract bool IsAvailableLocally(TModel model); public void Update(TModel skin) { From af5d3af664a84e20967b80ca6fb1d446261b0730 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 Jan 2022 14:08:00 +0900 Subject: [PATCH 262/996] Remove test coverage of scores being deleted when beatmaps are This is not supported in realm for now. Probably best suited to a separate pass, similar to files, using backlink count. --- osu.Game.Tests/Scores/IO/ImportScoreTest.cs | 38 --------------------- 1 file changed, 38 deletions(-) diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs index a1d47351dd..22b792cee4 100644 --- a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs +++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs @@ -119,44 +119,6 @@ namespace osu.Game.Tests.Scores.IO } } - [Test] - public async Task TestImportWithDeletedBeatmapSet() - { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) - { - try - { - var osu = LoadOsuIntoHost(host, true); - - var toImport = new ScoreInfo - { - User = new APIUser { Username = "Test user" }, - Hash = Guid.NewGuid().ToString(), - Statistics = new Dictionary - { - { HitResult.Perfect, 100 }, - { HitResult.Miss, 50 } - } - }; - - var imported = await LoadScoreIntoOsu(osu, toImport); - - var beatmapManager = osu.Dependencies.Get(); - var scoreManager = osu.Dependencies.Get(); - - beatmapManager.Delete(beatmapManager.GetAllUsableBeatmapSets().First(s => s.Beatmaps.Any(b => b.ID == imported.BeatmapInfo.ID))); - Assert.That(scoreManager.Query(s => s.ID == imported.ID).DeletePending, Is.EqualTo(true)); - - var secondImport = await LoadScoreIntoOsu(osu, imported); - Assert.That(secondImport, Is.Null); - } - finally - { - host.Exit(); - } - } - } - [Test] public async Task TestOnlineScoreIsAvailableLocally() { From ae8f522c20682cb08ef04d8fdf5fc2e47b135082 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 Jan 2022 14:18:34 +0900 Subject: [PATCH 263/996] Add support for persisting score's mods to realm --- osu.Game/Scoring/ScoreInfo.cs | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 28a6d67b6a..eb4a0d21d7 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -183,6 +183,10 @@ namespace osu.Game.Scoring [Ignored] public bool IsLegacyScore => Mods.OfType().Any(); + // Used for database serialisation/deserialisation. + [MapTo("Mods")] + public string ModsJson { get; set; } = string.Empty; + [Ignored] public Mod[] Mods { @@ -203,6 +207,7 @@ namespace osu.Game.Scoring { localAPIMods = null; mods = value; + ModsJson = JsonConvert.SerializeObject(APIMods); } } @@ -212,13 +217,18 @@ namespace osu.Game.Scoring { get { - if (localAPIMods != null) - return localAPIMods; + if (localAPIMods == null) + { + // prioritise reading from realm backing + if (!string.IsNullOrEmpty(ModsJson)) + localAPIMods = JsonConvert.DeserializeObject(ModsJson); - if (mods == null) - return Array.Empty(); + // then check mods set via Mods property. + if (mods != null) + localAPIMods = mods.Select(m => new APIMod(m)).ToArray(); + } - return localAPIMods = mods.Select(m => new APIMod(m)).ToArray(); + return localAPIMods ?? Array.Empty(); } set { @@ -226,6 +236,7 @@ namespace osu.Game.Scoring // We potentially can't update this yet due to Ruleset being late-bound, so instead update on read as necessary. mods = null; + ModsJson = JsonConvert.SerializeObject(APIMods); } } From f4a1fa85a12c5267f5443b7c780a153891276277 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 Jan 2022 14:36:05 +0900 Subject: [PATCH 264/996] Fix incorrect conditional for deciding whether scores can be deleted from UI --- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 10 +++++----- .../Match/Components/MatchLeaderboardScore.cs | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 3eb9a02a5c..247a509aa1 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -46,7 +46,7 @@ namespace osu.Game.Online.Leaderboards protected Container RankContainer { get; private set; } private readonly int? rank; - private readonly bool allowHighlight; + private readonly bool isOnlineScope; private Box background; private Container content; @@ -68,12 +68,12 @@ namespace osu.Game.Online.Leaderboards [Resolved] private Storage storage { get; set; } - public LeaderboardScore(ScoreInfo score, int? rank, bool allowHighlight = true) + public LeaderboardScore(ScoreInfo score, int? rank, bool isOnlineScope = true) { Score = score; this.rank = rank; - this.allowHighlight = allowHighlight; + this.isOnlineScope = isOnlineScope; RelativeSizeAxes = Axes.X; Height = HEIGHT; @@ -111,7 +111,7 @@ namespace osu.Game.Online.Leaderboards background = new Box { RelativeSizeAxes = Axes.Both, - Colour = user.OnlineID == api.LocalUser.Value.Id && allowHighlight ? colour.Green : Color4.Black, + Colour = user.OnlineID == api.LocalUser.Value.Id && isOnlineScope ? colour.Green : Color4.Black, Alpha = background_alpha, }, }, @@ -399,7 +399,7 @@ namespace osu.Game.Online.Leaderboards if (Score.Files.Count > 0) items.Add(new OsuMenuItem("Export", MenuItemType.Standard, () => new LegacyScoreExporter(storage).Export(Score))); - if (Score.IsManaged) + if (!isOnlineScope) items.Add(new OsuMenuItem("Delete", MenuItemType.Destructive, () => dialogOverlay?.Push(new LocalScoreDeleteDialog(Score)))); return items.ToArray(); diff --git a/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboardScore.cs b/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboardScore.cs index e8f5b1e826..799c44cc28 100644 --- a/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboardScore.cs +++ b/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboardScore.cs @@ -14,8 +14,8 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components { private readonly APIUserScoreAggregate score; - public MatchLeaderboardScore(APIUserScoreAggregate score, int? rank, bool allowHighlight = true) - : base(score.CreateScoreInfo(), rank, allowHighlight) + public MatchLeaderboardScore(APIUserScoreAggregate score, int? rank, bool isOnlineScope = true) + : base(score.CreateScoreInfo(), rank, isOnlineScope) { this.score = score; } From 8ecfb9172ef5d87703f2ee36bb91a5ac121927ca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 Jan 2022 14:36:21 +0900 Subject: [PATCH 265/996] Fix multiple tests with incorrect access to beatmap imports --- .../Menus/TestSceneMusicActionHandling.cs | 5 ++- .../TestSceneDeleteLocalScore.cs | 35 +++++++++++-------- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs b/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs index ee9363fa12..3ebc64cd0b 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs @@ -39,7 +39,10 @@ namespace osu.Game.Tests.Visual.Menus AddStep("import beatmap with track", () => { var setWithTrack = Game.BeatmapManager.Import(new ImportTask(TestResources.GetTestBeatmapForImport())).GetResultSafely(); - Beatmap.Value = Game.BeatmapManager.GetWorkingBeatmap(setWithTrack.Value.Beatmaps.First()); + setWithTrack?.PerformRead(s => + { + Beatmap.Value = Game.BeatmapManager.GetWorkingBeatmap(s.Beatmaps.First()); + }); }); AddStep("bind to track change", () => diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index 49200f6e1a..17168bf07f 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -22,6 +22,7 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Leaderboards; using osu.Game.Overlays; using osu.Game.Rulesets; +using osu.Game.Rulesets.Osu; using osu.Game.Scoring; using osu.Game.Screens.Select.Leaderboards; using osu.Game.Tests.Resources; @@ -90,23 +91,29 @@ namespace osu.Game.Tests.Visual.UserInterface dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesetStore, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default)); dependencies.Cache(scoreManager = new ScoreManager(dependencies.Get(), () => beatmapManager, LocalStorage, dependencies.Get(), Scheduler)); - beatmapInfo = beatmapManager.Import(new ImportTask(TestResources.GetQuickTestBeatmapForImport())).GetResultSafely().Value.Beatmaps[0]; + var imported = beatmapManager.Import(new ImportTask(TestResources.GetQuickTestBeatmapForImport())).GetResultSafely(); - for (int i = 0; i < 50; i++) + imported?.PerformRead(s => { - var score = new ScoreInfo - { - OnlineID = i, - BeatmapInfo = beatmapInfo, - Accuracy = RNG.NextDouble(), - TotalScore = RNG.Next(1, 1000000), - MaxCombo = RNG.Next(1, 1000), - Rank = ScoreRank.XH, - User = new APIUser { Username = "TestUser" }, - }; + beatmapInfo = s.Beatmaps[0].Detach(); - importedScores.Add(scoreManager.Import(score).GetResultSafely().Value); - } + for (int i = 0; i < 50; i++) + { + var score = new ScoreInfo + { + OnlineID = i, + BeatmapInfo = beatmapInfo, + Accuracy = RNG.NextDouble(), + TotalScore = RNG.Next(1, 1000000), + MaxCombo = RNG.Next(1, 1000), + Rank = ScoreRank.XH, + User = new APIUser { Username = "TestUser" }, + Ruleset = new OsuRuleset().RulesetInfo, + }; + + importedScores.Add(scoreManager.Import(score).GetResultSafely().Value); + } + }); return dependencies; } From a0f8debafe909bb8aa8bbb6ab965d7eaa3b33e4f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 Jan 2022 14:40:41 +0900 Subject: [PATCH 266/996] Add note about `BeatmapMetadata.Author` being weird --- osu.Game/Beatmaps/BeatmapMetadata.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs index f729b55154..6349476550 100644 --- a/osu.Game/Beatmaps/BeatmapMetadata.cs +++ b/osu.Game/Beatmaps/BeatmapMetadata.cs @@ -27,7 +27,7 @@ namespace osu.Game.Beatmaps [JsonProperty("artist_unicode")] public string ArtistUnicode { get; set; } = string.Empty; - public RealmUser Author { get; set; } = new RealmUser(); + public RealmUser Author { get; set; } = new RealmUser(); // TODO: not sure we want to initialise this only to have it overwritten by retrieval. public string Source { get; set; } = string.Empty; From 41d90cd0b5a2f1689d03263f9c44fd64693088ea Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 Jan 2022 14:52:59 +0900 Subject: [PATCH 267/996] Fix beatmap carousel test failures --- .../Visual/SongSelect/TestSceneBeatmapCarousel.cs | 3 +-- osu.Game/Screens/Select/BeatmapCarousel.cs | 12 +++++++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index cbcfc07202..d7e3951ac2 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -698,10 +698,9 @@ namespace osu.Game.Tests.Visual.SongSelect for (int i = 1; i <= 15; i++) { - set.Beatmaps.Add(new BeatmapInfo + set.Beatmaps.Add(new BeatmapInfo(new OsuRuleset().RulesetInfo, new BeatmapDifficulty(), new BeatmapMetadata()) { DifficultyName = $"Stars: {i}", - Ruleset = new OsuRuleset().RulesetInfo, StarRating = i, }); } diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index ffb5c6a2a5..25f48ae455 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -98,10 +98,16 @@ namespace osu.Game.Screens.Select private IEnumerable beatmapSets => root.Children.OfType(); // todo: only used for testing, maybe remove. + private bool loadedTestBeatmaps; + public IEnumerable BeatmapSets { get => beatmapSets.Select(g => g.BeatmapSet); - set => loadBeatmapSets(value); + set + { + loadedTestBeatmaps = true; + loadBeatmapSets(value); + } } private void loadBeatmapSets(IEnumerable beatmapSets) @@ -190,6 +196,10 @@ namespace osu.Game.Screens.Select private void beatmapSetsChanged(IRealmCollection sender, ChangeSet changes, Exception error) { + // If loading test beatmaps, avoid overwriting with realm subscription callbacks. + if (loadedTestBeatmaps) + return; + if (changes == null) { // initial load From 4b690703b3142b1db78c6296d8e60b4598fef294 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 Jan 2022 14:59:15 +0900 Subject: [PATCH 268/996] Remove unnecessary DI dependencies from cache test --- osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs b/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs index 99904af42d..e17e4a88da 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs @@ -24,7 +24,6 @@ using osu.Game.Rulesets.Mods; using osu.Game.Scoring; using osu.Game.Screens.Menu; using osu.Game.Skinning; -using osu.Game.Stores; using osu.Game.Utils; namespace osu.Game.Tests.Visual.Navigation @@ -69,7 +68,6 @@ namespace osu.Game.Tests.Visual.Navigation typeof(ISkinSource), typeof(IAPIProvider), typeof(RulesetStore), - typeof(RealmFileStore), typeof(ScoreManager), typeof(BeatmapManager), typeof(IRulesetConfigCache), From 02d0ca274189e02a8e6da0eb277cbe860025fba7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 Jan 2022 14:59:25 +0900 Subject: [PATCH 269/996] Fix protected beatmaps showing up in the song select carousel --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 25f48ae455..f1ad7db9b5 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -190,7 +190,7 @@ namespace osu.Game.Screens.Select { base.LoadComplete(); - subscriptionSets = realmFactory.Context.All().Where(s => !s.DeletePending).QueryAsyncWithNotifications(beatmapSetsChanged); + subscriptionSets = realmFactory.Context.All().Where(s => !s.DeletePending && !s.Protected).QueryAsyncWithNotifications(beatmapSetsChanged); subscriptionBeatmaps = realmFactory.Context.All().Where(b => !b.Hidden).QueryAsyncWithNotifications(beatmapsChanged); } From 605898ec53c217e682450fb32e99a44d94d0aa6f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 Jan 2022 15:06:41 +0900 Subject: [PATCH 270/996] Add missing "non-null" elements missing from some tests --- .../Gameplay/TestSceneBeatmapSkinFallbacks.cs | 2 +- .../Gameplay/TestSceneSpectatorPlayback.cs | 9 +++++++- .../SongSelect/TestSceneBeatmapLeaderboard.cs | 21 +++++++++++++++++++ 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs index c5f56cae9e..b6a08dd2ab 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs @@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestEmptyLegacyBeatmapSkinFallsBack() { - CreateSkinTest(DefaultSkin.CreateInfo(), () => new LegacyBeatmapSkin(new BeatmapInfo(), null, null)); + CreateSkinTest(DefaultSkin.CreateInfo(), () => new LegacyBeatmapSkin(new BeatmapInfo { Metadata = new BeatmapMetadata() }, null, null)); AddUntilStep("wait for hud load", () => Player.ChildrenOfType().All(c => c.ComponentsLoaded)); AddAssert("hud from default skin", () => AssertComponentsFromExpectedSource(SkinnableTarget.MainHUDComponents, skinManager.CurrentSkin.Value)); } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs index f7e9a1fe16..4790bd44db 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs @@ -372,7 +372,14 @@ namespace osu.Game.Tests.Visual.Gameplay internal class TestReplayRecorder : ReplayRecorder { public TestReplayRecorder() - : base(new Score { ScoreInfo = { BeatmapInfo = new BeatmapInfo() } }) + : base(new Score + { + ScoreInfo = + { + BeatmapInfo = new BeatmapInfo(), + Ruleset = new OsuRuleset().RulesetInfo, + } + }) { } diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index a5f42545c1..78df4d8d98 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -17,6 +17,7 @@ using osu.Game.Online.Leaderboards; using osu.Game.Overlays; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Scoring; using osu.Game.Screens.Select.Leaderboards; @@ -136,6 +137,7 @@ namespace osu.Game.Tests.Visual.SongSelect MaxCombo = 244, TotalScore = 1707827, Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock() }, + Ruleset = new OsuRuleset().RulesetInfo, User = new APIUser { Id = 6602580, @@ -158,6 +160,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 1, MaxCombo = 244, TotalScore = 1707827, + Ruleset = new OsuRuleset().RulesetInfo, Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, User = new APIUser { @@ -200,6 +203,7 @@ namespace osu.Game.Tests.Visual.SongSelect MaxCombo = 244, TotalScore = 1707827, //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + Ruleset = new OsuRuleset().RulesetInfo, BeatmapInfo = beatmapInfo, User = new APIUser { @@ -220,6 +224,7 @@ namespace osu.Game.Tests.Visual.SongSelect TotalScore = 1707827, //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, + Ruleset = new OsuRuleset().RulesetInfo, User = new APIUser { Id = 4608074, @@ -239,6 +244,8 @@ namespace osu.Game.Tests.Visual.SongSelect TotalScore = 1707827, //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, + Ruleset = new OsuRuleset().RulesetInfo, + User = new APIUser { Id = 1014222, @@ -258,6 +265,8 @@ namespace osu.Game.Tests.Visual.SongSelect TotalScore = 1707827, //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, + Ruleset = new OsuRuleset().RulesetInfo, + User = new APIUser { Id = 1541390, @@ -277,6 +286,8 @@ namespace osu.Game.Tests.Visual.SongSelect TotalScore = 1707827, //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, + Ruleset = new OsuRuleset().RulesetInfo, + User = new APIUser { Id = 2243452, @@ -296,6 +307,8 @@ namespace osu.Game.Tests.Visual.SongSelect TotalScore = 1707827, //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, + Ruleset = new OsuRuleset().RulesetInfo, + User = new APIUser { Id = 2705430, @@ -315,6 +328,8 @@ namespace osu.Game.Tests.Visual.SongSelect TotalScore = 1707827, //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, + Ruleset = new OsuRuleset().RulesetInfo, + User = new APIUser { Id = 7151382, @@ -334,6 +349,8 @@ namespace osu.Game.Tests.Visual.SongSelect TotalScore = 1707827, //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, + Ruleset = new OsuRuleset().RulesetInfo, + User = new APIUser { Id = 2051389, @@ -353,6 +370,8 @@ namespace osu.Game.Tests.Visual.SongSelect TotalScore = 1707827, //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, + Ruleset = new OsuRuleset().RulesetInfo, + User = new APIUser { Id = 6169483, @@ -372,6 +391,8 @@ namespace osu.Game.Tests.Visual.SongSelect TotalScore = 1707827, //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, + Ruleset = new OsuRuleset().RulesetInfo, + User = new APIUser { Id = 6702666, From c33e1631789ff4b04d334a2654279c2b3cdbff0c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 Jan 2022 16:31:28 +0900 Subject: [PATCH 271/996] Bind ruleset to toolbar later for safety --- osu.Game/Overlays/Toolbar/Toolbar.cs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Toolbar/Toolbar.cs b/osu.Game/Overlays/Toolbar/Toolbar.cs index dc0b06b255..776f7ad7b7 100644 --- a/osu.Game/Overlays/Toolbar/Toolbar.cs +++ b/osu.Game/Overlays/Toolbar/Toolbar.cs @@ -58,8 +58,11 @@ namespace osu.Game.Overlays.Toolbar AlwaysPresent = false; } + [Resolved] + private Bindable ruleset { get; set; } + [BackgroundDependencyLoader(true)] - private void load(OsuGame osuGame, Bindable parentRuleset) + private void load(OsuGame osuGame) { Children = new Drawable[] { @@ -106,13 +109,17 @@ namespace osu.Game.Overlays.Toolbar } }; - // Bound after the selector is added to the hierarchy to give it a chance to load the available rulesets - rulesetSelector.Current.BindTo(parentRuleset); - if (osuGame != null) OverlayActivationMode.BindTo(osuGame.OverlayActivationMode); } + protected override void LoadComplete() + { + base.LoadComplete(); + + rulesetSelector.Current.BindTo(ruleset); + } + public class ToolbarBackground : Container { private readonly Box gradientBackground; From 5cbd73186480ac7a9cb19d5c34cf20431dc201a3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 Jan 2022 16:31:50 +0900 Subject: [PATCH 272/996] Add `RulesetInfo` hashcode implementation and tidy up equality --- .../TestScenePlayerScoreSubmission.cs | 10 ++++++++- osu.Game/Rulesets/RulesetInfo.cs | 21 ++++++++++++++----- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs index 9f34931f54..a4a4f351ec 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs @@ -255,7 +255,15 @@ namespace osu.Game.Tests.Visual.Gameplay { prepareTestAPI(true); - createPlayerTest(false, createRuleset: () => new OsuRuleset { RulesetInfo = { OnlineID = rulesetId ?? -1 } }); + createPlayerTest(false, createRuleset: () => new OsuRuleset + { + RulesetInfo = + { + Name = "custom", + ShortName = $"custom{rulesetId}", + OnlineID = rulesetId ?? -1 + } + }); AddUntilStep("wait for token request", () => Player.TokenCreationRequested); diff --git a/osu.Game/Rulesets/RulesetInfo.cs b/osu.Game/Rulesets/RulesetInfo.cs index 5d5cbe6328..2e2ec5c024 100644 --- a/osu.Game/Rulesets/RulesetInfo.cs +++ b/osu.Game/Rulesets/RulesetInfo.cs @@ -47,14 +47,25 @@ namespace osu.Game.Rulesets public bool Available { get; set; } - public bool Equals(RulesetInfo? other) => other != null - && OnlineID == other.OnlineID - && Available == other.Available - && Name == other.Name - && InstantiationInfo == other.InstantiationInfo; + public bool Equals(RulesetInfo? other) + { + if (ReferenceEquals(this, other)) return true; + if (other == null) return false; + + return ShortName == other.ShortName; + } public bool Equals(IRulesetInfo? other) => other is RulesetInfo b && Equals(b); + public override int GetHashCode() + { + // Importantly, ignore the underlying realm hash code, as it will usually not match. + var hashCode = new HashCode(); + // ReSharper disable once NonReadonlyMemberInGetHashCode + hashCode.Add(ShortName); + return hashCode.ToHashCode(); + } + public override string ToString() => Name; public RulesetInfo Clone() => new RulesetInfo From 8e79898e267c9b4870f2e4b8874dfa4834593f53 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 Jan 2022 16:34:16 +0900 Subject: [PATCH 273/996] Fix a couple of minor issues with `TestSceneBeatmapRecommendations` --- .../Visual/SongSelect/TestSceneBeatmapRecommendations.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs index 08b5802713..b7bc0c37e1 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs @@ -113,6 +113,8 @@ namespace osu.Game.Tests.Visual.SongSelect // Switch to catch presentAndConfirm(() => catchSet, 1); + AddAssert("game-wide ruleset changed", () => Game.Ruleset.Value.Equals(catchSet.Beatmaps.First().Ruleset)); + // Present mixed difficulty set, expect current ruleset to be selected presentAndConfirm(() => mixedSet, 2); } @@ -182,7 +184,7 @@ namespace osu.Game.Tests.Visual.SongSelect beatmap.DifficultyName = $"SR{i + 1}"; } - return Game.BeatmapManager.Import(beatmapSet).GetResultSafely().Value; + return Game.BeatmapManager.Import(beatmapSet).GetResultSafely()?.Value; } private bool ensureAllBeatmapSetsImported(IEnumerable beatmapSets) => beatmapSets.All(set => set != null); From 902dc0eaec32ff49aa1893d611ccc68ee4fecb12 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 Jan 2022 16:34:32 +0900 Subject: [PATCH 274/996] Detach rather than consume live when presenting a beatmap --- osu.Game/OsuGame.cs | 45 ++++++++++++++++++++++----------------------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index b9e0aac6ef..ad045d08d2 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -451,32 +451,31 @@ namespace osu.Game return; } + var detachedSet = databasedSet.PerformRead(s => s.Detach()); + PerformFromScreen(screen => { - databasedSet.PerformRead(set => + // Find beatmaps that match our predicate. + var beatmaps = detachedSet.Beatmaps.Where(b => difficultyCriteria?.Invoke(b) ?? true).ToList(); + + // Use all beatmaps if predicate matched nothing + if (beatmaps.Count == 0) + beatmaps = detachedSet.Beatmaps.ToList(); + + // Prefer recommended beatmap if recommendations are available, else fallback to a sane selection. + var selection = difficultyRecommender.GetRecommendedBeatmap(beatmaps) + ?? beatmaps.FirstOrDefault(b => b.Ruleset.Equals(Ruleset.Value)) + ?? beatmaps.First(); + + if (screen is IHandlePresentBeatmap presentableScreen) { - // Find beatmaps that match our predicate. - var beatmaps = set.Beatmaps.Where(b => difficultyCriteria?.Invoke(b) ?? true).ToList(); - - // Use all beatmaps if predicate matched nothing - if (beatmaps.Count == 0) - beatmaps = set.Beatmaps.ToList(); - - // Prefer recommended beatmap if recommendations are available, else fallback to a sane selection. - var selection = difficultyRecommender.GetRecommendedBeatmap(beatmaps) - ?? beatmaps.FirstOrDefault(b => b.Ruleset.Equals(Ruleset.Value)) - ?? beatmaps.First(); - - if (screen is IHandlePresentBeatmap presentableScreen) - { - presentableScreen.PresentBeatmap(BeatmapManager.GetWorkingBeatmap(selection), selection.Ruleset); - } - else - { - Ruleset.Value = selection.Ruleset; - Beatmap.Value = BeatmapManager.GetWorkingBeatmap(selection); - } - }); + presentableScreen.PresentBeatmap(BeatmapManager.GetWorkingBeatmap(selection), selection.Ruleset); + } + else + { + Ruleset.Value = selection.Ruleset; + Beatmap.Value = BeatmapManager.GetWorkingBeatmap(selection); + } }, validScreens: new[] { typeof(SongSelect), typeof(IHandlePresentBeatmap) }); } From b531cd020750e220a8c1fd3914c2efc15af96e81 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 Jan 2022 16:54:07 +0900 Subject: [PATCH 275/996] Fix donwload trackers not considering deleted scores --- osu.Game/Online/BeatmapDownloadTracker.cs | 2 +- osu.Game/Online/ScoreDownloadTracker.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/BeatmapDownloadTracker.cs b/osu.Game/Online/BeatmapDownloadTracker.cs index 7a396ac7aa..be5bdea6f1 100644 --- a/osu.Game/Online/BeatmapDownloadTracker.cs +++ b/osu.Game/Online/BeatmapDownloadTracker.cs @@ -42,7 +42,7 @@ namespace osu.Game.Online // Used to interact with manager classes that don't support interface types. Will eventually be replaced. var beatmapSetInfo = new BeatmapSetInfo { OnlineID = TrackedItem.OnlineID }; - realmSubscription = realmContextFactory.Context.All().Where(s => s.OnlineID == TrackedItem.OnlineID).QueryAsyncWithNotifications((items, changes, ___) => + realmSubscription = realmContextFactory.Context.All().Where(s => s.OnlineID == TrackedItem.OnlineID && !s.DeletePending).QueryAsyncWithNotifications((items, changes, ___) => { if (items.Any()) Schedule(() => UpdateState(DownloadState.LocallyAvailable)); diff --git a/osu.Game/Online/ScoreDownloadTracker.cs b/osu.Game/Online/ScoreDownloadTracker.cs index 946a751c31..08362448a3 100644 --- a/osu.Game/Online/ScoreDownloadTracker.cs +++ b/osu.Game/Online/ScoreDownloadTracker.cs @@ -47,7 +47,7 @@ namespace osu.Game.Online Downloader.DownloadBegan += downloadBegan; Downloader.DownloadFailed += downloadFailed; - realmSubscription = realmContextFactory.Context.All().Where(s => s.OnlineID == TrackedItem.OnlineID).QueryAsyncWithNotifications((items, changes, ___) => + realmSubscription = realmContextFactory.Context.All().Where(s => s.OnlineID == TrackedItem.OnlineID && !s.DeletePending).QueryAsyncWithNotifications((items, changes, ___) => { if (items.Any()) Schedule(() => UpdateState(DownloadState.LocallyAvailable)); From c15efaeff2256871327360bdb66767455571b99b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 Jan 2022 17:07:40 +0900 Subject: [PATCH 276/996] Fix `OnlinePlayBeatmapAvailabilityTracker` not correctly tracking beatmap import changes --- .../Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs index 1832c116bc..caec944104 100644 --- a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs @@ -71,7 +71,7 @@ namespace osu.Game.Online.Rooms AddInternal(downloadTracker); - downloadTracker.State.BindValueChanged(_ => updateAvailability(), true); + downloadTracker.State.BindValueChanged(_ => Scheduler.AddOnce(updateAvailability), true); downloadTracker.Progress.BindValueChanged(_ => { if (downloadTracker.State.Value != DownloadState.Downloading) @@ -85,14 +85,14 @@ namespace osu.Game.Online.Rooms realmSubscription?.Dispose(); realmSubscription = realmContextFactory.Context - .All() - .Where(s => s.OnlineID == item.NewValue.BeatmapID) // TODO: figure out if we need this hash match in the subscription || s.ID == matchingHash?.ID) + .All() + .Where(b => b.OnlineID == item.NewValue.BeatmapID && b.MD5Hash == item.NewValue.Beatmap.Value.MD5Hash) .QueryAsyncWithNotifications((items, changes, ___) => { if (changes == null) return; - Schedule(updateAvailability); + Scheduler.AddOnce(updateAvailability); }); }, true); } From ac3b7aa89364e1efca967118e189a0a7c9f1f6a7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 Jan 2022 17:21:29 +0900 Subject: [PATCH 277/996] Fix more incorrect test access to `ILive` --- .../TestScenePlaylistsRoomCreation.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs index 908e93dee2..eacc3d5e31 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs @@ -34,7 +34,7 @@ namespace osu.Game.Tests.Visual.Playlists private TestPlaylistsRoomSubScreen match; - private ILive importedBeatmap; + private BeatmapSetInfo importedBeatmap; [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) @@ -65,7 +65,7 @@ namespace osu.Game.Tests.Visual.Playlists room.EndDate.Value = DateTimeOffset.Now.AddMinutes(5); room.Playlist.Add(new PlaylistItem { - Beatmap = { Value = importedBeatmap.Value.Beatmaps.First() }, + Beatmap = { Value = importedBeatmap.Beatmaps.First() }, Ruleset = { Value = new OsuRuleset().RulesetInfo } }); }); @@ -88,7 +88,7 @@ namespace osu.Game.Tests.Visual.Playlists room.EndDate.Value = DateTimeOffset.Now.AddMinutes(5); room.Playlist.Add(new PlaylistItem { - Beatmap = { Value = importedBeatmap.Value.Beatmaps.First() }, + Beatmap = { Value = importedBeatmap.Beatmaps.First() }, Ruleset = { Value = new OsuRuleset().RulesetInfo } }); }); @@ -105,7 +105,7 @@ namespace osu.Game.Tests.Visual.Playlists room.Host.Value = API.LocalUser.Value; room.Playlist.Add(new PlaylistItem { - Beatmap = { Value = importedBeatmap.Value.Beatmaps.First() }, + Beatmap = { Value = importedBeatmap.Beatmaps.First() }, Ruleset = { Value = new OsuRuleset().RulesetInfo } }); }); @@ -122,9 +122,9 @@ namespace osu.Game.Tests.Visual.Playlists AddStep("store real beatmap values", () => { - realHash = importedBeatmap.Value.Beatmaps[0].MD5Hash; - realOnlineId = importedBeatmap.Value.Beatmaps[0].OnlineID; - realOnlineSetId = importedBeatmap.Value.OnlineID; + realHash = importedBeatmap.Beatmaps[0].MD5Hash; + realOnlineId = importedBeatmap.Beatmaps[0].OnlineID; + realOnlineSetId = importedBeatmap.OnlineID; }); AddStep("import modified beatmap", () => @@ -134,6 +134,7 @@ namespace osu.Game.Tests.Visual.Playlists BeatmapInfo = { OnlineID = realOnlineId, + Metadata = new BeatmapMetadata(), BeatmapSet = { OnlineID = realOnlineSetId @@ -160,6 +161,7 @@ namespace osu.Game.Tests.Visual.Playlists { MD5Hash = realHash, OnlineID = realOnlineId, + Metadata = new BeatmapMetadata(), BeatmapSet = new BeatmapSetInfo { OnlineID = realOnlineSetId, @@ -211,7 +213,7 @@ namespace osu.Game.Tests.Visual.Playlists Debug.Assert(beatmap.BeatmapInfo.BeatmapSet != null); - importedBeatmap = manager.Import(beatmap.BeatmapInfo.BeatmapSet).GetResultSafely(); + importedBeatmap = manager.Import(beatmap.BeatmapInfo.BeatmapSet).GetResultSafely()?.Value.Detach(); }); private class TestPlaylistsRoomSubScreen : PlaylistsRoomSubScreen From ca7e11057c871f3e108c172259ff1cbaa2970ec6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 Jan 2022 18:19:47 +0900 Subject: [PATCH 278/996] Use better method to ensure online availability tracker is in a clean state --- .../Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index bdc2317ccd..8c24b2eef8 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -60,9 +60,8 @@ namespace osu.Game.Tests.Online testBeatmapInfo = getTestBeatmapInfo(testBeatmapFile); testBeatmapSet = testBeatmapInfo.BeatmapSet; - var existing = beatmaps.QueryBeatmapSet(s => s.OnlineID == testBeatmapSet.OnlineID); - if (existing != null) - beatmaps.Delete(existing.Value); + ContextFactory.Context.Write(r => r.RemoveAll()); + ContextFactory.Context.Write(r => r.RemoveAll()); selectedItem.Value = new PlaylistItem { From a7958b1d317228af103d0648c7de320e1384ab32 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 Jan 2022 18:26:49 +0900 Subject: [PATCH 279/996] Fix edge cases in online availability tracker and combine query code --- .../OnlinePlayBeatmapAvailabilityTracker.cs | 44 ++++++------------- 1 file changed, 13 insertions(+), 31 deletions(-) diff --git a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs index caec944104..1fa5d15eb3 100644 --- a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs @@ -12,6 +12,7 @@ using osu.Framework.Logging; using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Database; +using Realms; namespace osu.Game.Online.Rooms { @@ -28,9 +29,6 @@ namespace osu.Game.Online.Rooms // Required to allow child components to update. Can potentially be replaced with a `CompositeComponent` class if or when we make one. protected override bool RequiresChildrenUpdate => true; - [Resolved] - private BeatmapManager beatmapManager { get; set; } - [Resolved] private RealmContextFactory realmContextFactory { get; set; } = null!; @@ -45,11 +43,6 @@ namespace osu.Game.Online.Rooms private BeatmapDownloadTracker downloadTracker; - /// - /// The beatmap matching the required hash (and providing a final state). - /// - private BeatmapInfo matchingHash; - private IDisposable realmSubscription; protected override void LoadComplete() @@ -83,17 +76,15 @@ namespace osu.Game.Online.Rooms progressUpdate = Scheduler.AddDelayed(updateAvailability, progressUpdate == null ? 0 : 500); }, true); + // handles changes to hash that didn't occur from the import process (ie. a user editing the beatmap in the editor, somehow). realmSubscription?.Dispose(); - realmSubscription = realmContextFactory.Context - .All() - .Where(b => b.OnlineID == item.NewValue.BeatmapID && b.MD5Hash == item.NewValue.Beatmap.Value.MD5Hash) - .QueryAsyncWithNotifications((items, changes, ___) => - { - if (changes == null) - return; + realmSubscription = filteredBeatmaps().QueryAsyncWithNotifications((items, changes, ___) => + { + if (changes == null) + return; - Scheduler.AddOnce(updateAvailability); - }); + Scheduler.AddOnce(updateAvailability); + }); }, true); } @@ -102,9 +93,6 @@ namespace osu.Game.Online.Rooms if (downloadTracker == null) return; - // will be repopulated below if still valid. - matchingHash = null; - switch (downloadTracker.State.Value) { case DownloadState.NotDownloaded: @@ -120,9 +108,7 @@ namespace osu.Game.Online.Rooms break; case DownloadState.LocallyAvailable: - matchingHash = findMatchingHash(); - - bool hashMatches = matchingHash != null; + bool hashMatches = filteredBeatmaps().Any(); availability.Value = hashMatches ? BeatmapAvailability.LocallyAvailable() : BeatmapAvailability.NotDownloaded(); @@ -137,18 +123,14 @@ namespace osu.Game.Online.Rooms } } - private BeatmapInfo findMatchingHash() + private IQueryable filteredBeatmaps() { int onlineId = SelectedItem.Value.Beatmap.Value.OnlineID; string checksum = SelectedItem.Value.Beatmap.Value.MD5Hash; - var foundBeatmap = beatmapManager.QueryBeatmap(b => b.OnlineID == onlineId && b.MD5Hash == checksum); - - // can't be included in the above query due to realm limitations. - if (foundBeatmap?.BeatmapSet?.DeletePending == true) - return null; - - return foundBeatmap; + return realmContextFactory.Context + .All() + .Filter("OnlineID == $0 && MD5Hash == $1 && BeatmapSet.DeletePending == false", onlineId, checksum); } protected override void Dispose(bool isDisposing) From 6033a825ede119066ea24101d76dc6d86250757f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 Jan 2022 19:23:43 +0900 Subject: [PATCH 280/996] Ensure `BeatmapInfo` `Difficulty` and `Metadata` is non-null --- osu.Game/Beatmaps/BeatmapInfo.cs | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 32eff68d5c..00d40dc513 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -32,9 +32,21 @@ namespace osu.Game.Beatmaps public RulesetInfo Ruleset { get; set; } = null!; - public BeatmapDifficulty Difficulty { get; set; } = null!; + public BeatmapDifficulty Difficulty { get; set; } = new BeatmapDifficulty(); - public BeatmapMetadata Metadata { get; set; } = null!; + public BeatmapMetadata Metadata { get; set; } = new BeatmapMetadata(); + + public BeatmapInfo(RulesetInfo ruleset, BeatmapDifficulty difficulty, BeatmapMetadata metadata) + { + Ruleset = ruleset; + Difficulty = difficulty; + Metadata = metadata; + } + + [UsedImplicitly] + public BeatmapInfo() // TODO: this bypasses null safeties. needs to be hidden from user api (only should be used by realm). + { + } public BeatmapSetInfo? BeatmapSet { get; set; } @@ -66,18 +78,6 @@ namespace osu.Game.Beatmaps [JsonIgnore] public bool Hidden { get; set; } - public BeatmapInfo(RulesetInfo ruleset, BeatmapDifficulty difficulty, BeatmapMetadata metadata) - { - Ruleset = ruleset; - Difficulty = difficulty; - Metadata = metadata; - } - - [UsedImplicitly] - public BeatmapInfo() // TODO: this bypasses null safeties. needs to be hidden from user api (only should be used by realm). - { - } - #region Properties we may not want persisted (but also maybe no harm?) public double AudioLeadIn { get; set; } From f4515602036311f0413e5ff499ddc0eea65e42f9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 Jan 2022 19:27:13 +0900 Subject: [PATCH 281/996] Update null allowances across beatmaps and scores --- osu.Game/Beatmaps/BeatmapInfo.cs | 2 +- osu.Game/Scoring/ScoreInfo.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 00d40dc513..53aba67275 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -44,7 +44,7 @@ namespace osu.Game.Beatmaps } [UsedImplicitly] - public BeatmapInfo() // TODO: this bypasses null safeties. needs to be hidden from user api (only should be used by realm). + public BeatmapInfo() // TODO: consider removing this and migrating all usages to ctor with parameters. { } diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index eb4a0d21d7..61df736a4a 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -44,7 +44,7 @@ namespace osu.Game.Scoring public long OnlineID { get; set; } = -1; [MapTo("User")] - public RealmUser RealmUser { get; set; } = null!; + public RealmUser RealmUser { get; set; } = new RealmUser(); public ScoreInfo(BeatmapInfo beatmap, RulesetInfo ruleset, RealmUser realmUser) { @@ -54,7 +54,7 @@ namespace osu.Game.Scoring } [UsedImplicitly] - public ScoreInfo() // TODO: this bypasses null safeties. needs to be hidden from user api (only should be used by realm). + public ScoreInfo() // TODO: consider removing this and migrating all usages to ctor with parameters. { } From 58f8aae731f391c7845afefe7083aae770155dab Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 11 Jan 2022 13:34:24 +0900 Subject: [PATCH 282/996] Fix one missed instance of `GetResultSafely` --- osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index caa994a98b..d03a8b645d 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -170,7 +170,7 @@ namespace osu.Game.Screens.Select.Leaderboards scores = scores.Detach(); scoreManager.OrderByTotalScoreAsync(scores.ToArray(), cancellationToken) - .ContinueWith(ordered => scoresCallback?.Invoke(ordered.Result), TaskContinuationOptions.OnlyOnRanToCompletion); + .ContinueWith(ordered => scoresCallback?.Invoke(ordered.GetResultSafely()), TaskContinuationOptions.OnlyOnRanToCompletion); return null; } From b5975eee3355a39e6988148245a758bb96d63e45 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 11 Jan 2022 13:36:58 +0900 Subject: [PATCH 283/996] This file should have been deleted in a previous commit (rebase failure) --- .../Beatmaps/IO/ImportBeatmapTest.cs | 148 ------------------ 1 file changed, 148 deletions(-) delete mode 100644 osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs deleted file mode 100644 index 0d973c8aeb..0000000000 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ /dev/null @@ -1,148 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Extensions; -using osu.Game.Beatmaps; -using osu.Game.Database; -using osu.Game.Scoring; -using osu.Game.Tests.Resources; -using osu.Game.Tests.Scores.IO; - -namespace osu.Game.Tests.Beatmaps.IO -{ - [TestFixture] - public class ImportBeatmapTest : ImportTest - { - public static async Task LoadQuickOszIntoOsu(OsuGameBase osu) - { - string temp = TestResources.GetQuickTestBeatmapForImport(); - - var manager = osu.Dependencies.Get(); - - var importedSet = manager.Import(new ImportTask(temp)).GetResultSafely(); - - ensureLoaded(osu).WaitSafely(); - - waitForOrAssert(() => !File.Exists(temp), "Temporary file still exists after standard import", 5000); - - return manager.GetAllUsableBeatmapSets().Find(beatmapSet => beatmapSet.ID == importedSet.Value.ID); - }, TaskCreationOptions.LongRunning); - - public static Task LoadOszIntoOsu(OsuGameBase osu, string path = null, bool virtualTrack = false) => Task.Factory.StartNew(() => - { - string temp = path ?? TestResources.GetTestBeatmapForImport(virtualTrack); - - var manager = osu.Dependencies.Get(); - - var importedSet = manager.Import(new ImportTask(temp)).GetResultSafely(); - - ensureLoaded(osu).WaitSafely(); - - waitForOrAssert(() => !File.Exists(temp), "Temporary file still exists after standard import", 5000); - - return manager.GetAllUsableBeatmapSets().Find(beatmapSet => beatmapSet.ID == importedSet.Value.ID); - }, TaskCreationOptions.LongRunning); - - private void deleteBeatmapSet(BeatmapSetInfo imported, OsuGameBase osu) - { - var manager = osu.Dependencies.Get(); - manager.Delete(imported); - - checkBeatmapSetCount(osu, 0); - checkBeatmapSetCount(osu, 1, true); - checkSingleReferencedFileCount(osu, 0); - - Assert.IsTrue(manager.QueryBeatmapSets(_ => true).First().DeletePending); - } - - private static Task createScoreForBeatmap(OsuGameBase osu, BeatmapInfo beatmapInfo) - { - return ImportScoreTest.LoadScoreIntoOsu(osu, new ScoreInfo - { - OnlineID = 2, - BeatmapInfo = beatmapInfo, - }, new ImportScoreTest.TestArchiveReader()); - } - - private static void checkBeatmapSetCount(OsuGameBase osu, int expected, bool includeDeletePending = false) - { - var manager = osu.Dependencies.Get(); - - Assert.AreEqual(expected, includeDeletePending - ? manager.QueryBeatmapSets(_ => true).ToList().Count - : manager.GetAllUsableBeatmapSets().Count); - } - - private static string hashFile(string filename) - { - using (var s = File.OpenRead(filename)) - return s.ComputeMD5Hash(); - } - - private static void checkBeatmapCount(OsuGameBase osu, int expected) - { - Assert.AreEqual(expected, osu.Dependencies.Get().QueryBeatmaps(_ => true).ToList().Count); - } - - private static void checkSingleReferencedFileCount(OsuGameBase osu, int expected) - { - Assert.AreEqual(expected, osu.Dependencies.Get().Get().FileInfo.Count(f => f.ReferenceCount == 1)); - } - - private static Task ensureLoaded(OsuGameBase osu, int timeout = 60000) => Task.Factory.StartNew(() => - { - IEnumerable resultSets = null; - var store = osu.Dependencies.Get(); - waitForOrAssert(() => (resultSets = store.QueryBeatmapSets(s => s.OnlineID == 241526)).Any(), - @"BeatmapSet did not import to the database in allocated time.", timeout); - - // ensure we were stored to beatmap database backing... - Assert.IsTrue(resultSets.Count() == 1, $@"Incorrect result count found ({resultSets.Count()} but should be 1)."); - IEnumerable queryBeatmaps() => store.QueryBeatmaps(s => s.BeatmapSet.OnlineID == 241526); - IEnumerable queryBeatmapSets() => store.QueryBeatmapSets(s => s.OnlineID == 241526); - - // if we don't re-check here, the set will be inserted but the beatmaps won't be present yet. - waitForOrAssert(() => queryBeatmaps().Count() == 12, - @"Beatmaps did not import to the database in allocated time", timeout); - waitForOrAssert(() => queryBeatmapSets().Count() == 1, - @"BeatmapSet did not import to the database in allocated time", timeout); - int countBeatmapSetBeatmaps = 0; - int countBeatmaps = 0; - waitForOrAssert(() => - (countBeatmapSetBeatmaps = queryBeatmapSets().First().Beatmaps.Count) == - (countBeatmaps = queryBeatmaps().Count()), - $@"Incorrect database beatmap count post-import ({countBeatmaps} but should be {countBeatmapSetBeatmaps}).", timeout); - - var set = queryBeatmapSets().First(); - foreach (BeatmapInfo b in set.Beatmaps) - Assert.IsTrue(set.Beatmaps.Any(c => c.OnlineID == b.OnlineID)); - Assert.IsTrue(set.Beatmaps.Count > 0); - var beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 0))?.Beatmap; - Assert.IsTrue(beatmap?.HitObjects.Any() == true); - beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 1))?.Beatmap; - Assert.IsTrue(beatmap?.HitObjects.Any() == true); - beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 2))?.Beatmap; - Assert.IsTrue(beatmap?.HitObjects.Any() == true); - beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 3))?.Beatmap; - Assert.IsTrue(beatmap?.HitObjects.Any() == true); - }, TaskCreationOptions.LongRunning); - - private static void waitForOrAssert(Func result, string failureMessage, int timeout = 60000) - { - Task task = Task.Factory.StartNew(() => - { - while (!result()) Thread.Sleep(200); - }, TaskCreationOptions.LongRunning); - - Assert.IsTrue(task.Wait(timeout), failureMessage); - } - } -} From 286994a808b5dc684000b20bdaf76c2f2aebc20f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 11 Jan 2022 14:19:35 +0900 Subject: [PATCH 284/996] Fix `BeatmapDifficulty` cloning regression --- osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs | 3 +++ osu.Game/Beatmaps/BeatmapDifficulty.cs | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index 9b2e9fedc5..61f932fd98 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -191,6 +191,9 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps protected override Beatmap CreateBeatmap() => new TaikoBeatmap(); + // Important to note that this is subclassing a realm object. + // Realm doesn't allow this, but for now this can work since we aren't (in theory?) persisting this to the database. + // It is only used during beatmap conversion and processing. internal class TaikoMultiplierAppliedDifficulty : BeatmapDifficulty { public TaikoMultiplierAppliedDifficulty(IBeatmapDifficultyInfo difficulty) diff --git a/osu.Game/Beatmaps/BeatmapDifficulty.cs b/osu.Game/Beatmaps/BeatmapDifficulty.cs index 90a60c0f0c..f6e8b8c57d 100644 --- a/osu.Game/Beatmaps/BeatmapDifficulty.cs +++ b/osu.Game/Beatmaps/BeatmapDifficulty.cs @@ -39,7 +39,7 @@ namespace osu.Game.Beatmaps /// public BeatmapDifficulty Clone() { - var diff = new BeatmapDifficulty(); + var diff = (BeatmapDifficulty)MemberwiseClone(); CopyTo(diff); return diff; } From 2ce80cc0301b1cfb7da577f564a5d14349261521 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 11 Jan 2022 14:40:47 +0900 Subject: [PATCH 285/996] Add back caching in `WorkingBeatmapCache` --- osu.Game/Beatmaps/WorkingBeatmapCache.cs | 31 +++++++++++++++--------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/osu.Game/Beatmaps/WorkingBeatmapCache.cs b/osu.Game/Beatmaps/WorkingBeatmapCache.cs index 6be69b5477..f4324dcc81 100644 --- a/osu.Game/Beatmaps/WorkingBeatmapCache.cs +++ b/osu.Game/Beatmaps/WorkingBeatmapCache.cs @@ -12,6 +12,7 @@ using osu.Framework.IO.Stores; using osu.Framework.Lists; using osu.Framework.Logging; using osu.Framework.Platform; +using osu.Framework.Statistics; using osu.Framework.Testing; using osu.Game.Beatmaps.Formats; using osu.Game.Database; @@ -75,21 +76,27 @@ namespace osu.Game.Beatmaps public virtual WorkingBeatmap GetWorkingBeatmap(BeatmapInfo beatmapInfo) { - // if there are no files, presume the full beatmap info has not yet been fetched from the database. - if (beatmapInfo?.BeatmapSet?.Files.Count == 0) - { - var lookupId = beatmapInfo.ID; - beatmapInfo = BeatmapManager.QueryBeatmap(b => b.ID == lookupId); - } - - // TODO: FUCK THE WORLD :D - if (beatmapInfo?.IsManaged == true) - beatmapInfo = beatmapInfo.Detach(); - if (beatmapInfo?.BeatmapSet == null) return DefaultBeatmap; - return new BeatmapManagerWorkingBeatmap(beatmapInfo, this); + lock (workingCache) + { + var working = workingCache.FirstOrDefault(w => beatmapInfo.Equals(w.BeatmapInfo)); + + if (working != null) + return working; + + // TODO: FUCK THE WORLD :D + if (beatmapInfo?.IsManaged == true) + beatmapInfo = beatmapInfo.Detach(); + + workingCache.Add(working = new BeatmapManagerWorkingBeatmap(beatmapInfo, this)); + + // best effort; may be higher than expected. + GlobalStatistics.Get("Beatmaps", $"Cached {nameof(WorkingBeatmap)}s").Value = workingCache.Count(); + + return working; + } } #region IResourceStorageProvider From 9e2ca583a3aa5e04b5fa313fbc7288c00d2c7ae4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 11 Jan 2022 15:33:54 +0900 Subject: [PATCH 286/996] Fix incorrect realm factory isolation in `TestScenePlaySongSelect` --- osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 396bbc3dc5..3db1d0b25b 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -45,7 +45,10 @@ namespace osu.Game.Tests.Visual.SongSelect [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { + // These DI caches are required to ensure for interactive runs this test scene doesn't nuke all user beatmaps in the local install. + // At a point we have isolated interactive test runs enough, this can likely be removed. Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); + Dependencies.Cache(ContextFactory); Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, defaultBeatmap = Beatmap.Default)); Dependencies.Cache(music = new MusicController()); From cd88ccab4f7486416d25a1235a7ccdf266c4bcc3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 11 Jan 2022 15:40:18 +0900 Subject: [PATCH 287/996] Fix `TestScenePlaySongSelect` failure due to detach clone depth --- osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 3db1d0b25b..6295a52bdd 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -640,8 +640,9 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("Get filtered icon", () => { - filteredBeatmap = songSelect.Carousel.SelectedBeatmapSet.Beatmaps.First(b => b.BPM < maxBPM); - int filteredBeatmapIndex = getBeatmapIndex(filteredBeatmap.BeatmapSet, filteredBeatmap); + var selectedSet = songSelect.Carousel.SelectedBeatmapSet; + filteredBeatmap = selectedSet.Beatmaps.First(b => b.BPM < maxBPM); + int filteredBeatmapIndex = getBeatmapIndex(selectedSet, filteredBeatmap); filteredIcon = set.ChildrenOfType().ElementAt(filteredBeatmapIndex); }); From 6613a7e4aebb99a64f3f27bf154ba1605aacf01f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 11 Jan 2022 15:44:33 +0900 Subject: [PATCH 288/996] Fix another case of test ruleset without overriding `ShortName` primary key --- .../Visual/UserInterface/TestSceneModSelectOverlay.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index f196bbd76e..b429619044 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -459,6 +459,8 @@ namespace osu.Game.Tests.Visual.UserInterface private class TestUnimplementedModOsuRuleset : OsuRuleset { + public override string ShortName => "unimplemented"; + public override IEnumerable GetModsFor(ModType type) { if (type == ModType.Conversion) return base.GetModsFor(type).Concat(new[] { new TestUnimplementedMod() }); From dd19487eb8eab9d13f0fc49e05bdf81ec924c7ab Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 11 Jan 2022 15:55:13 +0900 Subject: [PATCH 289/996] Fix custom import process in `TestSceneDrawableRoomPlaylist` not working with realm --- .../TestSceneDrawableRoomPlaylist.cs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index d87a0a4a1c..5bad36c3dd 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -153,19 +153,20 @@ namespace osu.Game.Tests.Visual.Multiplayer public void TestDownloadButtonHiddenWhenBeatmapExists() { var beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo; + ILive imported = null; Debug.Assert(beatmap.BeatmapSet != null); - AddStep("import beatmap", () => manager.Import(beatmap.BeatmapSet).WaitSafely()); + AddStep("import beatmap", () => imported = manager.Import(beatmap.BeatmapSet).GetResultSafely()); - createPlaylistWithBeatmaps(beatmap); + createPlaylistWithBeatmaps(() => imported.PerformRead(s => s.Beatmaps.Detach())); assertDownloadButtonVisible(false); - AddStep("delete beatmap set", () => manager.Delete(manager.QueryBeatmapSets(_ => true).Single().Value)); + AddStep("delete beatmap set", () => imported.PerformWrite(s => s.DeletePending = true)); assertDownloadButtonVisible(true); - AddStep("undelete beatmap set", () => manager.Undelete(manager.QueryBeatmapSets(_ => true).Single().Value)); + AddStep("undelete beatmap set", () => imported.PerformWrite(s => s.DeletePending = false)); assertDownloadButtonVisible(false); void assertDownloadButtonVisible(bool visible) => AddUntilStep($"download button {(visible ? "shown" : "hidden")}", @@ -181,7 +182,7 @@ namespace osu.Game.Tests.Visual.Multiplayer var byChecksum = CreateAPIBeatmap(); byChecksum.Checksum = "1337"; // Some random checksum that does not exist locally. - createPlaylistWithBeatmaps(byOnlineId, byChecksum); + createPlaylistWithBeatmaps(() => new[] { byOnlineId, byChecksum }); AddAssert("download buttons shown", () => playlist.ChildrenOfType().All(d => d.IsPresent)); } @@ -195,7 +196,7 @@ namespace osu.Game.Tests.Visual.Multiplayer beatmap.BeatmapSet.HasExplicitContent = true; - createPlaylistWithBeatmaps(beatmap); + createPlaylistWithBeatmaps(() => new[] { beatmap }); } [Test] @@ -327,7 +328,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for items to load", () => playlist.ItemMap.Values.All(i => i.IsLoaded)); } - private void createPlaylistWithBeatmaps(params IBeatmapInfo[] beatmaps) + private void createPlaylistWithBeatmaps(Func> beatmaps) { AddStep("create playlist", () => { @@ -340,7 +341,7 @@ namespace osu.Game.Tests.Visual.Multiplayer int index = 0; - foreach (var b in beatmaps) + foreach (var b in beatmaps()) { playlist.Items.Add(new PlaylistItem { From 64a47ff85066be41c54f58cd7bebff40a9fd0278 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 11 Jan 2022 16:30:35 +0900 Subject: [PATCH 290/996] Allow `RealmArchiveModelManager` file operations to be performed on detached instances --- osu.Game/Stores/RealmArchiveModelManager.cs | 27 +++++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/osu.Game/Stores/RealmArchiveModelManager.cs b/osu.Game/Stores/RealmArchiveModelManager.cs index 0b78071d16..00fc5bbb6f 100644 --- a/osu.Game/Stores/RealmArchiveModelManager.cs +++ b/osu.Game/Stores/RealmArchiveModelManager.cs @@ -9,6 +9,7 @@ using System.Threading; using System.Threading.Tasks; using osu.Framework.Platform; using osu.Game.Database; +using osu.Game.Extensions; using osu.Game.IO.Archives; using osu.Game.Models; using osu.Game.Overlays.Notifications; @@ -33,13 +34,29 @@ namespace osu.Game.Stores } public void DeleteFile(TModel item, RealmNamedFileUsage file) => - item.Realm.Write(() => DeleteFile(item, file, item.Realm)); + performFileOperation(item, managed => DeleteFile(managed, file, managed.Realm)); - public void ReplaceFile(TModel item, RealmNamedFileUsage file, Stream contents) - => item.Realm.Write(() => ReplaceFile(file, contents, item.Realm)); + public void ReplaceFile(TModel item, RealmNamedFileUsage file, Stream contents) => + performFileOperation(item, managed => ReplaceFile(file, contents, managed.Realm)); - public void AddFile(TModel item, Stream contents, string filename) - => item.Realm.Write(() => AddFile(item, contents, filename, item.Realm)); + public void AddFile(TModel item, Stream contents, string filename) => + performFileOperation(item, managed => AddFile(managed, contents, filename, managed.Realm)); + + private void performFileOperation(TModel item, Action operation) + { + // While we are detaching so often, this seems like the easiest way to keep things in sync. + // This method should be removed as soon as all the surrounding pieces support non-detached operations. + if (!item.IsManaged) + { + var managed = ContextFactory.Context.Find(item.ID); + managed.Realm.Write(() => operation(managed)); + + item.Files.Clear(); + item.Files.AddRange(managed.Files.Detach()); + } + else + operation(item); + } /// /// Delete a file from within an ongoing realm transaction. From 8c3dc4333d5ccce809d645ce8817947bd4444920 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 11 Jan 2022 16:30:55 +0900 Subject: [PATCH 291/996] Fix incorrect realm access after new beatmap import --- osu.Game/Beatmaps/BeatmapManager.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 4f6b74ba0a..55f05bf05f 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -97,9 +97,12 @@ namespace osu.Game.Beatmaps } }; - var imported = beatmapModelManager.Import(set).GetResultSafely()?.Value; + var imported = beatmapModelManager.Import(set).GetResultSafely(); - return GetWorkingBeatmap(imported?.Beatmaps.First()); + if (imported == null) + throw new InvalidOperationException("Failed to import new beatmap"); + + return imported.PerformRead(s => GetWorkingBeatmap(s.Beatmaps.First())); } /// From dc9ea4adeb2d1fbe75ce58c5b75ebb4fa9b86eb3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 11 Jan 2022 16:31:00 +0900 Subject: [PATCH 292/996] Remove incorrect test assertion --- osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index 4cc6e2ca18..2386446e96 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -49,7 +49,6 @@ namespace osu.Game.Tests.Visual.Editing public void TestCreateNewBeatmap() { AddStep("save beatmap", () => Editor.Save()); - AddAssert("new beatmap persisted", () => EditorBeatmap.BeatmapInfo.IsManaged); AddAssert("new beatmap in database", () => beatmapManager.QueryBeatmapSet(s => s.ID == EditorBeatmap.BeatmapInfo.BeatmapSet.ID)?.Value.DeletePending == false); } From 7509a9ff8f6d1a45182eea6fc096135bc89679f1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 11 Jan 2022 18:17:13 +0900 Subject: [PATCH 293/996] Update `BeatmapModelManager.Save` to work for editor scenarios --- osu.Game/Beatmaps/BeatmapManager.cs | 7 +++-- osu.Game/Beatmaps/BeatmapModelManager.cs | 36 +++++++++--------------- osu.Game/Screens/Edit/Editor.cs | 3 -- 3 files changed, 19 insertions(+), 27 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 55f05bf05f..c869fd1bad 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -82,7 +82,7 @@ namespace osu.Game.Beatmaps } }; - var set = new BeatmapSetInfo + var beatmapSet = new BeatmapSetInfo { Beatmaps = { @@ -97,7 +97,10 @@ namespace osu.Game.Beatmaps } }; - var imported = beatmapModelManager.Import(set).GetResultSafely(); + foreach (BeatmapInfo b in beatmapSet.Beatmaps) + b.BeatmapSet = beatmapSet; + + var imported = beatmapModelManager.Import(beatmapSet).GetResultSafely(); if (imported == null) throw new InvalidOperationException("Failed to import new beatmap"); diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs index 254babc3a1..62d607c4f7 100644 --- a/osu.Game/Beatmaps/BeatmapModelManager.cs +++ b/osu.Game/Beatmaps/BeatmapModelManager.cs @@ -70,35 +70,27 @@ namespace osu.Game.Beatmaps stream.Seek(0, SeekOrigin.Begin); - using (var realm = ContextFactory.CreateContext()) - using (var transaction = realm.BeginWrite()) - { - beatmapInfo = setInfo.Beatmaps.Single(b => b.Equals(beatmapInfo)); + // AddFile generally handles updating/replacing files, but this is a case where the filename may have also changed so let's delete for simplicity. + var existingFileInfo = setInfo.Files.SingleOrDefault(f => string.Equals(f.Filename, beatmapInfo.Path, StringComparison.OrdinalIgnoreCase)); + if (existingFileInfo != null) + DeleteFile(setInfo, existingFileInfo); - // grab the original file (or create a new one if not found). - var existingFileInfo = setInfo.Files.SingleOrDefault(f => string.Equals(f.Filename, beatmapInfo.Path, StringComparison.OrdinalIgnoreCase)); + beatmapInfo.MD5Hash = stream.ComputeMD5Hash(); + beatmapInfo.Hash = stream.ComputeSHA2Hash(); - if (existingFileInfo != null) - { - DeleteFile(setInfo, existingFileInfo); - } - - // metadata may have changed; update the path with the standard format. - var metadata = beatmapInfo.Metadata; - string filename = $"{metadata.Artist} - {metadata.Title} ({metadata.Author}) [{beatmapInfo.DifficultyName}].osu".GetValidArchiveContentFilename(); - - beatmapInfo.MD5Hash = stream.ComputeMD5Hash(); - - stream.Seek(0, SeekOrigin.Begin); - AddFile(setInfo, stream, filename, realm); - - transaction.Commit(); - } + AddFile(setInfo, stream, getFilename(beatmapInfo)); + Update(setInfo); } WorkingBeatmapCache?.Invalidate(beatmapInfo); } + private static string getFilename(BeatmapInfo beatmapInfo) + { + var metadata = beatmapInfo.Metadata; + return $"{metadata.Artist} - {metadata.Title} ({metadata.Author}) [{beatmapInfo.DifficultyName}].osu".GetValidArchiveContentFilename(); + } + /// /// Perform a lookup query on available s. /// diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 3a420f4994..383b59dc01 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -356,9 +356,6 @@ namespace osu.Game.Screens.Edit // no longer new after first user-triggered save. isNewBeatmap = false; - // apply any set-level metadata changes. - beatmapManager.Update(editorBeatmap.BeatmapInfo.BeatmapSet); - // save the loaded beatmap's data stream. beatmapManager.Save(editorBeatmap.BeatmapInfo, editorBeatmap.PlayableBeatmap, editorBeatmap.BeatmapSkin); From 80eee6d7b031680a0437943172209e7362248ce5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 11 Jan 2022 18:18:03 +0900 Subject: [PATCH 294/996] Make `RealmArchiveModelManager.Update` work using automapper --- osu.Game/Screens/Menu/IntroScreen.cs | 6 +----- osu.Game/Stores/RealmArchiveModelManager.cs | 7 ++++++- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index e02066560d..159340a4d7 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -123,11 +123,7 @@ namespace osu.Game.Screens.Menu // this could happen if a user has nuked their files store. for now, reimport to repair this. var import = beatmaps.Import(new ZipArchiveReader(game.Resources.GetStream($"Tracks/{BeatmapFile}"), BeatmapFile)).GetResultSafely(); - import?.PerformWrite(b => - { - b.Protected = true; - beatmaps.Update(b); - }); + import?.PerformWrite(b => b.Protected = true); loadThemedIntro(); } diff --git a/osu.Game/Stores/RealmArchiveModelManager.cs b/osu.Game/Stores/RealmArchiveModelManager.cs index 00fc5bbb6f..7c6f25055f 100644 --- a/osu.Game/Stores/RealmArchiveModelManager.cs +++ b/osu.Game/Stores/RealmArchiveModelManager.cs @@ -202,8 +202,13 @@ namespace osu.Game.Stores public abstract bool IsAvailableLocally(TModel model); - public void Update(TModel skin) + public void Update(TModel model) { + using (var realm = ContextFactory.CreateContext()) + { + var existing = realm.Find(model.ID); + realm.Write(r => model.CopyChangesToRealm(existing)); + } } } } From f986c3ebd4b0728164e7ad6abf665109e1e4c372 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 11 Jan 2022 18:23:04 +0900 Subject: [PATCH 295/996] Add basic write support via automapper --- osu.Game/Database/RealmObjectExtensions.cs | 36 ++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/RealmObjectExtensions.cs b/osu.Game/Database/RealmObjectExtensions.cs index 9827ca6edd..180e372eba 100644 --- a/osu.Game/Database/RealmObjectExtensions.cs +++ b/osu.Game/Database/RealmObjectExtensions.cs @@ -19,7 +19,33 @@ namespace osu.Game.Database { public static class RealmObjectExtensions { - private static readonly IMapper mapper = new MapperConfiguration(c => + private static readonly IMapper write_mapper = new MapperConfiguration(c => + { + c.ShouldMapField = fi => false; + // If we want to limit this further, we can avoid mapping properties with no setter that are not IList<>. + // Takes a bit of effort to determine whether this is the case though, see https://stackoverflow.com/questions/951536/how-do-i-tell-whether-a-type-implements-ilist + c.ShouldMapProperty = pi => pi.SetMethod?.IsPublic == true; + + c.CreateMap(); + c.CreateMap(); + c.CreateMap(); + c.CreateMap(); + c.CreateMap(); + c.CreateMap(); + c.CreateMap(); + c.CreateMap(); + c.CreateMap(); + c.CreateMap(); + c.AddGlobalIgnore(nameof(RealmObjectBase.ObjectSchema)); + + c.ForAllMaps((a, b) => + { + b.PreserveReferences(); + b.MaxDepth(2); + }); + }).CreateMapper(); + + private static readonly IMapper read_mapper = new MapperConfiguration(c => { c.ShouldMapField = fi => false; // If we want to limit this further, we can avoid mapping properties with no setter that are not IList<>. @@ -36,6 +62,7 @@ namespace osu.Game.Database c.CreateMap(); c.CreateMap(); c.CreateMap(); + c.AddGlobalIgnore(nameof(RealmObjectBase.ObjectSchema)); c.ForAllMaps((a, b) => { @@ -77,7 +104,12 @@ namespace osu.Game.Database if (!item.IsManaged) return item; - return mapper.Map(item); + return read_mapper.Map(item); + } + + public static void CopyChangesToRealm(this T source, T destination) where T : RealmObjectBase + { + write_mapper.Map(source, destination); } public static List> ToLiveUnmanaged(this IEnumerable realmList) From b7ee6d186630be137d02074cb02bd24d7cadf012 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 11 Jan 2022 18:33:35 +0900 Subject: [PATCH 296/996] Add protections against test null refs when beatmap load fails --- .../Visual/Background/TestSceneUserDimBackgrounds.cs | 3 +++ osu.Game/Tests/Visual/TestPlayer.cs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs index b1f642b909..f27043e5cb 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs @@ -389,6 +389,9 @@ namespace osu.Game.Tests.Visual.Background while (BlockLoad && !token.IsCancellationRequested) Thread.Sleep(1); + if (!LoadedBeatmapSuccessfully) + return; + StoryboardEnabled = config.GetBindable(OsuSetting.ShowStoryboard); DrawableRuleset.IsPaused.BindTo(IsPaused); } diff --git a/osu.Game/Tests/Visual/TestPlayer.cs b/osu.Game/Tests/Visual/TestPlayer.cs index d68984b144..368f792e28 100644 --- a/osu.Game/Tests/Visual/TestPlayer.cs +++ b/osu.Game/Tests/Visual/TestPlayer.cs @@ -94,6 +94,9 @@ namespace osu.Game.Tests.Visual [BackgroundDependencyLoader] private void load() { + if (!LoadedBeatmapSuccessfully) + return; + ScoreProcessor.NewJudgement += r => Results.Add(r); } } From 7dba3c35513d95d5089502f0a81d29a94f8be602 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 11 Jan 2022 19:25:50 +0900 Subject: [PATCH 297/996] Fix most remaining test issues --- .../Visual/Background/TestSceneUserDimBackgrounds.cs | 1 + osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs | 1 + osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs | 2 +- osu.Game/Screens/Menu/IntroScreen.cs | 2 +- osu.Game/Screens/Select/BeatmapCarousel.cs | 4 ---- osu.Game/Stores/RealmArchiveModelManager.cs | 2 +- 6 files changed, 5 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs index f27043e5cb..3109fd1540 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs @@ -50,6 +50,7 @@ namespace osu.Game.Tests.Visual.Background Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(new OsuConfigManager(LocalStorage)); + Dependencies.Cache(ContextFactory); manager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs index dc826701d2..d4282ff21e 100644 --- a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs +++ b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs @@ -49,6 +49,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(ContextFactory); } public override void SetUpSteps() diff --git a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs index 1fa5d15eb3..1f77b1d383 100644 --- a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs @@ -90,7 +90,7 @@ namespace osu.Game.Online.Rooms private void updateAvailability() { - if (downloadTracker == null) + if (downloadTracker == null || SelectedItem.Value == null) return; switch (downloadTracker.State.Value) diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index 159340a4d7..afc5812039 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -223,7 +223,7 @@ namespace osu.Game.Screens.Menu if (!resuming) { beatmap.Value = initialBeatmap; - Track = initialBeatmap.Track; + Track = beatmap.Value.Track; // ensure the track starts at maximum volume musicController.CurrentTrack.FinishTransforms(); diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index f1ad7db9b5..b002b31c9e 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -676,10 +676,6 @@ namespace osu.Game.Screens.Select if (beatmapSet?.IsManaged == true) beatmapSet = beatmapSet.Detach(); - // todo: probably not required any more. - // foreach (var b in beatmapSet.Beatmaps) - // b.Metadata ??= beatmapSet.Metadata; - var set = new CarouselBeatmapSet(beatmapSet) { GetRecommendedBeatmap = beatmaps => GetRecommendedBeatmap?.Invoke(beatmaps) diff --git a/osu.Game/Stores/RealmArchiveModelManager.cs b/osu.Game/Stores/RealmArchiveModelManager.cs index 7c6f25055f..3783b10a8c 100644 --- a/osu.Game/Stores/RealmArchiveModelManager.cs +++ b/osu.Game/Stores/RealmArchiveModelManager.cs @@ -34,7 +34,7 @@ namespace osu.Game.Stores } public void DeleteFile(TModel item, RealmNamedFileUsage file) => - performFileOperation(item, managed => DeleteFile(managed, file, managed.Realm)); + performFileOperation(item, managed => DeleteFile(managed, managed.Files.First(f => f.Filename == file.Filename), managed.Realm)); public void ReplaceFile(TModel item, RealmNamedFileUsage file, Stream contents) => performFileOperation(item, managed => ReplaceFile(file, contents, managed.Realm)); From e8dcbaf29ada1f918b57be38d24f1f273d9ea083 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 11 Jan 2022 21:32:01 +0900 Subject: [PATCH 298/996] Fix intro screen hitting null reference if intro beatmap is unavailable --- osu.Game/Overlays/MusicController.cs | 7 ++++--- osu.Game/Screens/Menu/IntroScreen.cs | 18 +++++------------- 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 2731096a00..2497f549b3 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -284,11 +284,12 @@ namespace osu.Game.Overlays queuedDirection = TrackChangeDirection.Next; - var playable = BeatmapSets.SkipWhile(i => i.ID != current.BeatmapSetInfo.ID).ElementAtOrDefault(1) ?? BeatmapSets.FirstOrDefault(); + var playableSet = BeatmapSets.SkipWhile(i => i.ID != current.BeatmapSetInfo.ID).ElementAtOrDefault(1) ?? BeatmapSets.FirstOrDefault(); + var playableBeatmap = playableSet?.Beatmaps?.FirstOrDefault(); - if (playable != null) + if (playableBeatmap != null) { - changeBeatmap(beatmaps.GetWorkingBeatmap(playable.Beatmaps.First())); + changeBeatmap(beatmaps.GetWorkingBeatmap(playableBeatmap)); restartTrack(); return true; } diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index afc5812039..09a9f44558 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -141,24 +141,13 @@ namespace osu.Game.Screens.Menu if (s.Beatmaps.Count == 0) return; - initialBeatmap = beatmaps.GetWorkingBeatmap(s.Beatmaps[0].ToLive(realmContextFactory)); + initialBeatmap = beatmaps.GetWorkingBeatmap(s.Beatmaps.First()); }); return UsingThemedIntro = initialBeatmap != null; } } - protected override void LoadComplete() - { - base.LoadComplete(); - - // TODO: This is temporary to get the setInfo on the update thread, to make things work "better" without using ILive everywhere. - var setInfo = beatmaps.QueryBeatmapSets(b => b.Hash == BeatmapHash).FirstOrDefault(); - - if (setInfo?.Value.Beatmaps.Count > 0) - initialBeatmap = beatmaps.GetWorkingBeatmap(setInfo.Value.Beatmaps.First()); - } - public override void OnResuming(IScreen last) { this.FadeIn(300); @@ -222,7 +211,10 @@ namespace osu.Game.Screens.Menu if (!resuming) { - beatmap.Value = initialBeatmap; + // generally this can never be null + // an exception is running ruleset tests, where the osu! ruleset may not be prsent (causing importing the intro to fail). + if (initialBeatmap != null) + beatmap.Value = initialBeatmap; Track = beatmap.Value.Track; // ensure the track starts at maximum volume From c06b5951fd159a63b20a36be2367eb119a84f0b8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 11 Jan 2022 21:36:34 +0900 Subject: [PATCH 299/996] Fix multiple remaining warnings --- osu.Game/Beatmaps/BeatmapManager.cs | 9 +++++++-- osu.Game/Beatmaps/WorkingBeatmapCache.cs | 8 ++++---- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index c869fd1bad..36dcd4f8e8 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -43,11 +43,17 @@ namespace osu.Game.Beatmaps private readonly RealmContextFactory contextFactory; - public BeatmapManager(Storage storage, RealmContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, AudioManager audioManager, IResourceStore gameResources, GameHost? host = null, WorkingBeatmap? defaultBeatmap = null, bool performOnlineLookups = false) + public BeatmapManager(Storage storage, RealmContextFactory contextFactory, RulesetStore rulesets, IAPIProvider? api, AudioManager audioManager, IResourceStore gameResources, GameHost? host = null, WorkingBeatmap? defaultBeatmap = null, bool performOnlineLookups = false) { this.contextFactory = contextFactory; + if (performOnlineLookups) + { + if (api == null) + throw new ArgumentNullException(nameof(api), "API must be provided if online lookups are required."); + onlineBeatmapLookupQueue = new BeatmapOnlineLookupQueue(api, storage); + } var userResources = new RealmFileStore(contextFactory, storage).Store; @@ -56,7 +62,6 @@ namespace osu.Game.Beatmaps beatmapModelManager = CreateBeatmapModelManager(storage, contextFactory, rulesets, onlineBeatmapLookupQueue); workingBeatmapCache = CreateWorkingBeatmapCache(audioManager, gameResources, userResources, defaultBeatmap, host); - workingBeatmapCache.BeatmapManager = beatmapModelManager; beatmapModelManager.WorkingBeatmapCache = workingBeatmapCache; } diff --git a/osu.Game/Beatmaps/WorkingBeatmapCache.cs b/osu.Game/Beatmaps/WorkingBeatmapCache.cs index f4324dcc81..e1399c5272 100644 --- a/osu.Game/Beatmaps/WorkingBeatmapCache.cs +++ b/osu.Game/Beatmaps/WorkingBeatmapCache.cs @@ -31,8 +31,6 @@ namespace osu.Game.Beatmaps /// public readonly WorkingBeatmap DefaultBeatmap; - public BeatmapModelManager BeatmapManager { private get; set; } - private readonly AudioManager audioManager; private readonly IResourceStore resources; private readonly LargeTextureStore largeTextureStore; @@ -87,8 +85,7 @@ namespace osu.Game.Beatmaps return working; // TODO: FUCK THE WORLD :D - if (beatmapInfo?.IsManaged == true) - beatmapInfo = beatmapInfo.Detach(); + beatmapInfo = beatmapInfo.Detach(); workingCache.Add(working = new BeatmapManagerWorkingBeatmap(beatmapInfo, this)); @@ -193,6 +190,9 @@ namespace osu.Game.Beatmaps { Storyboard storyboard; + if (BeatmapInfo.Path == null) + return new Storyboard(); + try { using (var stream = new LineBufferedReader(GetStream(BeatmapSetInfo.GetPathForFile(BeatmapInfo.Path)))) From 9beabad6a4c98898bae646683b63f07d13b5cf51 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 11 Jan 2022 21:41:10 +0900 Subject: [PATCH 300/996] Remove hide/restore event flow --- osu.Game/Beatmaps/BeatmapManager.cs | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 36dcd4f8e8..15dc0c9b61 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -113,16 +113,6 @@ namespace osu.Game.Beatmaps return imported.PerformRead(s => GetWorkingBeatmap(s.Beatmaps.First())); } - /// - /// Fired when a single difficulty has been hidden. - /// - public event Action? BeatmapHidden; - - /// - /// Fired when a single difficulty has been restored. - /// - public event Action? BeatmapRestored; - /// /// Delete a beatmap difficulty. /// @@ -134,8 +124,6 @@ namespace osu.Game.Beatmaps { beatmapInfo.Hidden = true; transaction.Commit(); - - BeatmapHidden?.Invoke(beatmapInfo); } } @@ -150,8 +138,6 @@ namespace osu.Game.Beatmaps { beatmapInfo.Hidden = false; transaction.Commit(); - - BeatmapRestored?.Invoke(beatmapInfo); } } From 46e92c3b601a59d34c85275c74e9af347dd7f3a4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 11 Jan 2022 21:44:18 +0900 Subject: [PATCH 301/996] Clean up `BeatmapManager` query methods --- osu.Game/Beatmaps/BeatmapManager.cs | 17 ----------------- osu.Game/Screens/Menu/IntroScreen.cs | 2 +- 2 files changed, 1 insertion(+), 18 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 15dc0c9b61..2cdae2b047 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -151,22 +151,6 @@ namespace osu.Game.Beatmaps return context.All().Where(b => !b.DeletePending).Detach(); } - /// - /// Perform a lookup query on available s. - /// - /// The query. - /// Results from the provided query. - public List> QueryBeatmapSets(Expression> query) - { - using (var context = contextFactory.CreateContext()) - { - return context.All() - .Where(b => !b.DeletePending) - .Where(query) - .ToLive(contextFactory); - } - } - /// /// Perform a lookup query on available s. /// @@ -197,7 +181,6 @@ namespace osu.Game.Beatmaps /// The query. /// The first result for the provided query, or null if no results were found. public BeatmapInfo? QueryBeatmap(Expression> query) => beatmapModelManager.QueryBeatmap(query)?.Detach(); - // TODO: move detach to usages? /// /// Saves an file against a given . diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index 09a9f44558..c4343625ab 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -131,7 +131,7 @@ namespace osu.Game.Screens.Menu bool loadThemedIntro() { - setInfo = beatmaps.QueryBeatmapSets(b => b.Hash == BeatmapHash).FirstOrDefault(); + setInfo = beatmaps.QueryBeatmapSet(b => b.Hash == BeatmapHash); if (setInfo == null) return false; From bf4133021b590c43a142ca9555bc3dbc0982c7e8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 11 Jan 2022 22:05:02 +0900 Subject: [PATCH 302/996] Update migration test to use realm file as test --- osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs index 4bb54f1625..61ef31e07e 100644 --- a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs +++ b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs @@ -179,7 +179,7 @@ namespace osu.Game.Tests.NonVisual { var osu = LoadOsuIntoHost(host); - const string database_filename = "client.db"; + const string database_filename = "client.realm"; Assert.DoesNotThrow(() => osu.Migrate(customPath)); Assert.That(File.Exists(Path.Combine(customPath, database_filename))); From e0c59f4b3ccccb57f7d28488884668de479c7366 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 11 Jan 2022 22:16:06 +0900 Subject: [PATCH 303/996] Localise EF context factory usage to migration only --- .../Visual/Navigation/TestSceneOsuGame.cs | 2 - osu.Game/Database/DatabaseContextFactory.cs | 6 +-- osu.Game/OsuGameBase.cs | 40 +++---------------- 3 files changed, 9 insertions(+), 39 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs b/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs index e17e4a88da..b8d1636ea0 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs @@ -12,7 +12,6 @@ using osu.Framework.Platform; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Configuration; -using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Input; using osu.Game.Input.Bindings; @@ -57,7 +56,6 @@ namespace osu.Game.Tests.Visual.Navigation private IReadOnlyList requiredGameBaseDependencies => new[] { typeof(OsuGameBase), - typeof(DatabaseContextFactory), typeof(Bindable), typeof(IBindable), typeof(Bindable>), diff --git a/osu.Game/Database/DatabaseContextFactory.cs b/osu.Game/Database/DatabaseContextFactory.cs index 94fa967d72..f79505d7c5 100644 --- a/osu.Game/Database/DatabaseContextFactory.cs +++ b/osu.Game/Database/DatabaseContextFactory.cs @@ -13,7 +13,7 @@ namespace osu.Game.Database { private readonly Storage storage; - private const string database_name = @"client.db"; + public const string DATABASE_NAME = @"client.db"; private ThreadLocal threadContexts; @@ -139,7 +139,7 @@ namespace osu.Game.Database threadContexts = new ThreadLocal(CreateContext, true); } - protected virtual OsuDbContext CreateContext() => new OsuDbContext(CreateDatabaseConnectionString(database_name, storage)) + protected virtual OsuDbContext CreateContext() => new OsuDbContext(CreateDatabaseConnectionString(DATABASE_NAME, storage)) { Database = { AutoTransactionsEnabled = false } }; @@ -152,7 +152,7 @@ namespace osu.Game.Database try { - storage.Delete(database_name); + storage.Delete(DATABASE_NAME); } catch { diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index d18ae6d2d8..a003c6b8ab 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -149,8 +149,6 @@ namespace osu.Game private MultiplayerClient multiplayerClient; - private DatabaseContextFactory contextFactory; - private RealmContextFactory realmFactory; protected override Container Content => content; @@ -186,13 +184,13 @@ namespace osu.Game Resources.AddStore(new DllResourceStore(OsuResources.ResourceAssembly)); - dependencies.Cache(contextFactory = new DatabaseContextFactory(Storage)); + DatabaseContextFactory efContextFactory = Storage.Exists(DatabaseContextFactory.DATABASE_NAME) + ? new DatabaseContextFactory(Storage) + : null; - runMigrations(); - - dependencies.Cache(realmFactory = new RealmContextFactory(Storage, "client", contextFactory)); - - new EFToRealmMigrator(contextFactory, realmFactory, LocalConfig).Run(); + dependencies.Cache(realmFactory = new RealmContextFactory(Storage, "client", efContextFactory)); + if (efContextFactory != null) + new EFToRealmMigrator(efContextFactory, realmFactory, LocalConfig).Run(); dependencies.CacheAs(Storage); @@ -397,7 +395,6 @@ namespace osu.Game Scheduler.Add(() => { realmBlocker = realmFactory.BlockAllOperations(); - contextFactory.FlushConnections(); readyToRun.Set(); }, false); @@ -458,29 +455,6 @@ namespace osu.Game AvailableMods.Value = dict; } - private void runMigrations() - { - try - { - using (var db = contextFactory.GetForWrite(false)) - db.Context.Migrate(); - } - catch (Exception e) - { - Logger.Error(e.InnerException ?? e, "Migration failed! We'll be starting with a fresh database.", LoggingTarget.Database); - - // if we failed, let's delete the database and start fresh. - // todo: we probably want a better (non-destructive) migrations/recovery process at a later point than this. - contextFactory.ResetDatabase(); - - Logger.Log("Database purged successfully.", LoggingTarget.Database); - - // only run once more, then hard bail. - using (var db = contextFactory.GetForWrite(false)) - db.Context.Migrate(); - } - } - protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); @@ -489,8 +463,6 @@ namespace osu.Game BeatmapManager?.Dispose(); LocalConfig?.Dispose(); - contextFactory?.FlushConnections(); - realmFactory?.Dispose(); } } From d5239d550a0a1952591c5cb57f091cf543eb26e8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 11 Jan 2022 22:55:00 +0900 Subject: [PATCH 304/996] Add refetch for non-managed hide/restore attempts --- osu.Game/Beatmaps/BeatmapManager.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 2cdae2b047..b0c7efc9d9 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -122,6 +122,9 @@ namespace osu.Game.Beatmaps using (var realm = contextFactory.CreateContext()) using (var transaction = realm.BeginWrite()) { + if (!beatmapInfo.IsManaged) + beatmapInfo = realm.Find(beatmapInfo.ID); + beatmapInfo.Hidden = true; transaction.Commit(); } @@ -136,6 +139,9 @@ namespace osu.Game.Beatmaps using (var realm = contextFactory.CreateContext()) using (var transaction = realm.BeginWrite()) { + if (!beatmapInfo.IsManaged) + beatmapInfo = realm.Find(beatmapInfo.ID); + beatmapInfo.Hidden = false; transaction.Commit(); } From 46206f70d6a158b9f4c540cffef656300c5eb106 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 11 Jan 2022 22:57:47 +0900 Subject: [PATCH 305/996] Fix beatmap mass deletion flow --- osu.Game/Beatmaps/BeatmapManager.cs | 13 +++++++++++++ .../Sections/Maintenance/GeneralSettings.cs | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index b0c7efc9d9..6f5370a55a 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -234,6 +234,19 @@ namespace osu.Game.Beatmaps beatmapModelManager.Delete(items, silent); } + public void Delete(Expression>? filter = null, bool silent = false) + { + using (var context = contextFactory.CreateContext()) + { + var items = context.All().Where(s => !s.DeletePending); + + if (filter != null) + items = items.Where(filter); + + beatmapModelManager.Delete(items.ToList(), silent); + } + } + public void Undelete(List items, bool silent = false) { beatmapModelManager.Undelete(items, silent); diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs index 93884812fe..736ba04e51 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs @@ -54,7 +54,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance dialogOverlay?.Push(new MassDeleteConfirmationDialog(() => { deleteBeatmapsButton.Enabled.Value = false; - Task.Run(() => beatmaps.Delete(beatmaps.GetAllUsableBeatmapSets())).ContinueWith(t => Schedule(() => deleteBeatmapsButton.Enabled.Value = true)); + Task.Run(() => beatmaps.Delete()).ContinueWith(t => Schedule(() => deleteBeatmapsButton.Enabled.Value = true)); })); } }); From 72656ae01e8cc108b5249538f36331d28c063305 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 11 Jan 2022 23:04:36 +0900 Subject: [PATCH 306/996] Fix beatmap restore/undelete flows --- osu.Game/Beatmaps/BeatmapManager.cs | 29 ++++++++++++------- .../Sections/Maintenance/GeneralSettings.cs | 15 ++-------- 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 6f5370a55a..9b2b7c960d 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -147,6 +147,18 @@ namespace osu.Game.Beatmaps } } + public void RestoreAll() + { + using (var realm = contextFactory.CreateContext()) + using (var transaction = realm.BeginWrite()) + { + foreach (var beatmap in realm.All().Where(b => b.Hidden)) + beatmap.Hidden = false; + + transaction.Commit(); + } + } + /// /// Returns a list of all usable s. /// @@ -168,17 +180,6 @@ namespace osu.Game.Beatmaps return context.All().FirstOrDefault(query)?.ToLive(contextFactory); } - /// - /// Perform a lookup query on available s. - /// - /// The query. - /// Results from the provided query. - public IQueryable QueryBeatmaps(Expression> query) - { - using (var context = contextFactory.CreateContext()) - return context.All().Where(query); - } - #region Delegation to BeatmapModelManager (methods which previously existed locally). /// @@ -247,6 +248,12 @@ namespace osu.Game.Beatmaps } } + public void UndeleteAll() + { + using (var context = contextFactory.CreateContext()) + beatmapModelManager.Undelete(context.All().Where(s => s.DeletePending).ToList()); + } + public void Undelete(List items, bool silent = false) { beatmapModelManager.Undelete(items, silent); diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs index 736ba04e51..aa02d086f4 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Linq; using System.Threading.Tasks; using JetBrains.Annotations; using osu.Framework.Allocation; @@ -106,10 +105,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance dialogOverlay?.Push(new MassDeleteConfirmationDialog(() => { deleteSkinsButton.Enabled.Value = false; - Task.Run(() => - { - skins.Delete(); - }).ContinueWith(t => Schedule(() => deleteSkinsButton.Enabled.Value = true)); + Task.Run(() => skins.Delete()).ContinueWith(t => Schedule(() => deleteSkinsButton.Enabled.Value = true)); })); } }); @@ -147,11 +143,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance Action = () => { restoreButton.Enabled.Value = false; - Task.Run(() => - { - foreach (var b in beatmaps.QueryBeatmaps(b => b.Hidden).ToList()) - beatmaps.Restore(b); - }).ContinueWith(t => Schedule(() => restoreButton.Enabled.Value = true)); + Task.Run(beatmaps.RestoreAll).ContinueWith(t => Schedule(() => restoreButton.Enabled.Value = true)); } }, undeleteButton = new SettingsButton @@ -160,8 +152,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance Action = () => { undeleteButton.Enabled.Value = false; - // TODO: reimplement similar to SkinManager? - // Task.Run(() => beatmaps.Undelete(beatmaps.QueryBeatmapSets(b => b.DeletePending).ToList())).ContinueWith(t => Schedule(() => undeleteButton.Enabled.Value = true)); + Task.Run(beatmaps.UndeleteAll).ContinueWith(t => Schedule(() => undeleteButton.Enabled.Value = true)); } }, }); From 0aff1c232b9581244434410313da2a0d69993557 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 12 Jan 2022 01:03:59 +0900 Subject: [PATCH 307/996] Fix deleted/hidden carousel queries --- osu.Game/Screens/Select/BeatmapCarousel.cs | 40 ++++++++++++++-------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index b002b31c9e..f355554482 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -151,7 +151,9 @@ namespace osu.Game.Screens.Select private CarouselRoot root; private IDisposable subscriptionSets; + private IDisposable subscriptionDeletedSets; private IDisposable subscriptionBeatmaps; + private IDisposable subscriptionHiddenBeatmaps; private readonly DrawablePool setPool = new DrawablePool(100); @@ -192,6 +194,24 @@ namespace osu.Game.Screens.Select subscriptionSets = realmFactory.Context.All().Where(s => !s.DeletePending && !s.Protected).QueryAsyncWithNotifications(beatmapSetsChanged); subscriptionBeatmaps = realmFactory.Context.All().Where(b => !b.Hidden).QueryAsyncWithNotifications(beatmapsChanged); + + // Can't use main subscriptions because we can't lookup deleted indices. + // https://github.com/realm/realm-dotnet/discussions/2634#discussioncomment-1605595. + subscriptionDeletedSets = realmFactory.Context.All().Where(s => s.DeletePending && !s.Protected).QueryAsyncWithNotifications(deletedBeatmapSetsChanged); + subscriptionHiddenBeatmaps = realmFactory.Context.All().Where(b => b.Hidden).QueryAsyncWithNotifications(beatmapsChanged); + } + + private void deletedBeatmapSetsChanged(IRealmCollection sender, ChangeSet changes, Exception error) + { + // If loading test beatmaps, avoid overwriting with realm subscription callbacks. + if (loadedTestBeatmaps) + return; + + if (changes == null) + return; + + foreach (int i in changes.InsertedIndices) + RemoveBeatmapSet(sender[i]); } private void beatmapSetsChanged(IRealmCollection sender, ChangeSet changes, Exception error) @@ -210,16 +230,8 @@ namespace osu.Game.Screens.Select foreach (int i in changes.NewModifiedIndices) UpdateBeatmapSet(sender[i]); - // moves also appear as deletes / inserts but aren't important to us. - if (!changes.Moves.Any()) - { - foreach (int i in changes.InsertedIndices) - UpdateBeatmapSet(sender[i]); - - // TODO: This can not work, as we recently found out https://github.com/realm/realm-dotnet/discussions/2634#discussioncomment-1605595. - foreach (int i in changes.DeletedIndices) - RemoveBeatmapSet(sender[i]); - } + foreach (int i in changes.InsertedIndices) + UpdateBeatmapSet(sender[i]); } private void beatmapsChanged(IRealmCollection sender, ChangeSet changes, Exception error) @@ -228,9 +240,7 @@ namespace osu.Game.Screens.Select if (changes == null) return; - // TODO: we can probably handle hidden items at a per-panel level (ie. start a realm subscription from there)? - // might be cleaner than handling via reconstruction of the whole set's panels. - foreach (int i in changes.NewModifiedIndices) + foreach (int i in changes.InsertedIndices) UpdateBeatmapSet(sender[i].BeatmapSet); } @@ -673,7 +683,7 @@ namespace osu.Game.Screens.Select return null; // TODO: FUCK THE WORLD :D - if (beatmapSet?.IsManaged == true) + if (beatmapSet.IsManaged) beatmapSet = beatmapSet.Detach(); var set = new CarouselBeatmapSet(beatmapSet) @@ -930,7 +940,9 @@ namespace osu.Game.Screens.Select base.Dispose(isDisposing); subscriptionSets?.Dispose(); + subscriptionDeletedSets?.Dispose(); subscriptionBeatmaps?.Dispose(); + subscriptionHiddenBeatmaps?.Dispose(); } } } From 157dfdaa825f72ea345a5d4c1463ac9639cba364 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 12 Jan 2022 01:18:11 +0900 Subject: [PATCH 308/996] Fix protected beatmap sets getting deleted --- osu.Game/Beatmaps/BeatmapManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 9b2b7c960d..21cda18c1a 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -239,7 +239,7 @@ namespace osu.Game.Beatmaps { using (var context = contextFactory.CreateContext()) { - var items = context.All().Where(s => !s.DeletePending); + var items = context.All().Where(s => !s.DeletePending && !s.Protected); if (filter != null) items = items.Where(filter); From 017285b694bc0ed45697f94d376077fdd0f73db1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 12 Jan 2022 01:18:36 +0900 Subject: [PATCH 309/996] Update `MusicController` to handle deletions more correctly --- osu.Game/Overlays/MusicController.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 2497f549b3..44c2916f12 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -84,12 +84,16 @@ namespace osu.Game.Overlays { base.LoadComplete(); + var availableBeatmaps = realmFactory.Context + .All() + .Where(s => !s.DeletePending); + // ensure we're ready before completing async load. // probably not a good way of handling this (as there is a period we aren't watching for changes until the realm subscription finishes up. - foreach (var s in realmFactory.Context.All()) + foreach (var s in availableBeatmaps) beatmapSets.Add(s); - beatmapSubscription = realmFactory.Context.All().QueryAsyncWithNotifications(beatmapsChanged); + beatmapSubscription = availableBeatmaps.QueryAsyncWithNotifications(beatmapsChanged); } private void beatmapsChanged(IRealmCollection sender, ChangeSet changes, Exception error) @@ -98,7 +102,7 @@ namespace osu.Game.Overlays return; foreach (int i in changes.InsertedIndices) - beatmapSets.Insert(i, sender[i]); + beatmapSets.Insert(i, sender[i].Detach()); foreach (int i in changes.DeletedIndices.OrderByDescending(i => i)) beatmapSets.RemoveAt(i); @@ -285,7 +289,7 @@ namespace osu.Game.Overlays queuedDirection = TrackChangeDirection.Next; var playableSet = BeatmapSets.SkipWhile(i => i.ID != current.BeatmapSetInfo.ID).ElementAtOrDefault(1) ?? BeatmapSets.FirstOrDefault(); - var playableBeatmap = playableSet?.Beatmaps?.FirstOrDefault(); + var playableBeatmap = playableSet?.Beatmaps.FirstOrDefault(); if (playableBeatmap != null) { From dc3730f33413b90789a1e8f5e7742b8ac2e627ff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 12 Jan 2022 01:29:40 +0900 Subject: [PATCH 310/996] Fix song select import popup not always showing --- osu.Game/Screens/Select/SongSelect.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 9ba5e77bbb..41700bec17 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -295,7 +295,7 @@ namespace osu.Game.Screens.Select Schedule(() => { // if we have no beatmaps, let's prompt the user to import from over a stable install if he has one. - if (!beatmaps.GetAllUsableBeatmapSets().Any() && DisplayStableImportPrompt) + if (beatmaps.QueryBeatmapSet(s => !s.Protected && !s.DeletePending) == null && DisplayStableImportPrompt) { dialogOverlay.Push(new ImportFromStablePopup(() => { From 6db3c32dd1d81bcc774398fa627be04951d84bfe Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 12 Jan 2022 00:32:32 +0300 Subject: [PATCH 311/996] Handle automapper realm cyclic references via `AfterMap`s This may not be the cleanest solution, but there don't seem to be any way towards this either. - `UseDestinationValue` has been inherited by default as noted in https://docs.automapper.org/en/stable/10.0-Upgrade-Guide.html#usedestinationvalue-is-now-inherited-by-default, and its behaviour in this case would be using the nested **managed** realm object for the destination member rather than creating an unmanaged version. - `MaxDepth` already sets `PreserveReferences` so there's no point of using it. - `MaxDepth` should probably not be set for all maps, only for those with cyclic references, to avoid the expensive overhead of `PreserveReferences`, as mentioned in https://docs.automapper.org/en/stable/5.0-Upgrade-Guide.html#circular-references. That aside, `MaxDepth` should actually only be set to `1` for `BeatmapSetInfo` mapping, because we don't want AutoMapper to create a nested instance of `BeatmapSetInfo` in each mapped/detached beatmap, but for some reason, doing that will cause automapper to not map any beatmap inside the set and leave it with 0 beatmaps. While on the other hand, using `MaxDepth(2)` for `BeatmapSetInfo` works, but creates an unused instance of a `BeatmapSetInfo` inside each mapped beatmap, which may not be ideal. For `BeatmapInfo`, it has to be `MaxDepth(2)`, in which the first `BeatmapInfo` depth would be itself (when detaching a beatmap), and the second would be nested beatmaps inside the mapped/detached `BeatmapSetInfo` within the beatmap. (note that when detaching a beatmap set, the unused instance of `BeatmapSetInfo` within each beatmap of that beatmap set doesn't also have a list of unused beatmaps as one might expect from the depth specification, it surprisingly has 0 beatmaps) This causes it to create an unused instance of `BeatmapInfo` in the beatmap set resembling the root mapped/detached beatmap, but that one might be inevitable. --- osu.Game/Database/RealmObjectExtensions.cs | 44 +++++++++++++++------- 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/osu.Game/Database/RealmObjectExtensions.cs b/osu.Game/Database/RealmObjectExtensions.cs index 180e372eba..83d23e2fc2 100644 --- a/osu.Game/Database/RealmObjectExtensions.cs +++ b/osu.Game/Database/RealmObjectExtensions.cs @@ -34,15 +34,23 @@ namespace osu.Game.Database c.CreateMap(); c.CreateMap(); c.CreateMap(); - c.CreateMap(); - c.CreateMap(); - c.AddGlobalIgnore(nameof(RealmObjectBase.ObjectSchema)); - - c.ForAllMaps((a, b) => + c.CreateMap().MaxDepth(2).AfterMap((s, d) => { - b.PreserveReferences(); - b.MaxDepth(2); + for (int i = 0; i < d.BeatmapSet?.Beatmaps.Count; i++) + { + if (d.BeatmapSet.Beatmaps[i].Equals(d)) + { + d.BeatmapSet.Beatmaps[i] = d; + break; + } + } }); + c.CreateMap().MaxDepth(2).AfterMap((s, d) => + { + foreach (var beatmap in d.Beatmaps) + beatmap.BeatmapSet = d; + }); + c.AddGlobalIgnore(nameof(RealmObjectBase.ObjectSchema)); }).CreateMapper(); private static readonly IMapper read_mapper = new MapperConfiguration(c => @@ -60,15 +68,23 @@ namespace osu.Game.Database c.CreateMap(); c.CreateMap(); c.CreateMap(); - c.CreateMap(); - c.CreateMap(); - c.AddGlobalIgnore(nameof(RealmObjectBase.ObjectSchema)); - - c.ForAllMaps((a, b) => + c.CreateMap().MaxDepth(2).AfterMap((s, d) => { - b.PreserveReferences(); - b.MaxDepth(2); + for (int i = 0; i < d.BeatmapSet?.Beatmaps.Count; i++) + { + if (d.BeatmapSet.Beatmaps[i].Equals(d)) + { + d.BeatmapSet.Beatmaps[i] = d; + break; + } + } }); + c.CreateMap().MaxDepth(2).AfterMap((s, d) => + { + foreach (var beatmap in d.Beatmaps) + beatmap.BeatmapSet = d; + }); + c.AddGlobalIgnore(nameof(RealmObjectBase.ObjectSchema)); }).CreateMapper(); /// From 51d6db1bca65a02f1d66675f2e766125364d7f1e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 12 Jan 2022 12:39:40 +0900 Subject: [PATCH 312/996] Add equatable support to `IUser` and `RealmUser` Not sure this will stick, but let's add it for now to make testing detach support work nicely. --- osu.Game/Beatmaps/IBeatmapMetadataInfo.cs | 2 +- osu.Game/Models/RealmUser.cs | 11 ++++++++++- osu.Game/Users/IUser.cs | 13 ++++++++++++- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/IBeatmapMetadataInfo.cs b/osu.Game/Beatmaps/IBeatmapMetadataInfo.cs index 968ad14928..61adc0ac34 100644 --- a/osu.Game/Beatmaps/IBeatmapMetadataInfo.cs +++ b/osu.Game/Beatmaps/IBeatmapMetadataInfo.cs @@ -73,7 +73,7 @@ namespace osu.Game.Beatmaps && TitleUnicode == other.TitleUnicode && Artist == other.Artist && ArtistUnicode == other.ArtistUnicode - && Author == other.Author + && Author.Equals(other.Author) && Source == other.Source && Tags == other.Tags && PreviewTime == other.PreviewTime diff --git a/osu.Game/Models/RealmUser.cs b/osu.Game/Models/RealmUser.cs index 154ece502f..ff35528827 100644 --- a/osu.Game/Models/RealmUser.cs +++ b/osu.Game/Models/RealmUser.cs @@ -1,17 +1,26 @@ // 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.Game.Users; using Realms; namespace osu.Game.Models { - public class RealmUser : EmbeddedObject, IUser + public class RealmUser : EmbeddedObject, IUser, IEquatable { public int OnlineID { get; set; } = 1; public string Username { get; set; } public bool IsBot => false; + + public bool Equals(RealmUser other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + + return OnlineID == other.OnlineID && Username == other.Username; + } } } diff --git a/osu.Game/Users/IUser.cs b/osu.Game/Users/IUser.cs index 3995531fd9..d9a352872f 100644 --- a/osu.Game/Users/IUser.cs +++ b/osu.Game/Users/IUser.cs @@ -1,14 +1,25 @@ // 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.Game.Database; +#nullable enable + namespace osu.Game.Users { - public interface IUser : IHasOnlineID + public interface IUser : IHasOnlineID, IEquatable { string Username { get; } bool IsBot { get; } + + bool IEquatable.Equals(IUser? other) + { + if (other == null) + return false; + + return OnlineID == other.OnlineID && Username == other.Username; + } } } From 509301d94f86aaa3ca4649bf2334417235b32b88 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 12 Jan 2022 12:40:09 +0900 Subject: [PATCH 313/996] Update detach test to assert correct behaviour --- .../Database/BeatmapImporterTests.cs | 42 +++++++++---------- 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index 78b8628669..49f992139c 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -43,39 +43,35 @@ namespace osu.Game.Tests.Database using (var importer = new BeatmapModelManager(realmFactory, storage)) using (new RulesetStore(realmFactory, storage)) { - ILive? imported; + ILive? beatmapSet; using (var reader = new ZipArchiveReader(TestResources.GetTestBeatmapStream())) - imported = await importer.Import(reader); + beatmapSet = await importer.Import(reader); - Assert.NotNull(imported); - Debug.Assert(imported != null); + Assert.NotNull(beatmapSet); + Debug.Assert(beatmapSet != null); - BeatmapSetInfo? detached = null; + BeatmapSetInfo? detachedBeatmapSet = null; - imported.PerformRead(live => + beatmapSet.PerformRead(live => { - var timer = new Stopwatch(); - timer.Start(); - detached = live.Detach(); - Logger.Log($"Detach took {timer.ElapsedMilliseconds} ms"); + detachedBeatmapSet = live.Detach(); - Logger.Log($"NamedFiles: {live.Files.Count} {detached.Files.Count}"); - Logger.Log($"Files: {live.Files.Select(f => f.File).Count()} {detached.Files.Select(f => f.File).Count()}"); - Logger.Log($"Difficulties: {live.Beatmaps.Count} {detached.Beatmaps.Count}"); - Logger.Log($"BeatmapDifficulties: {live.Beatmaps.Select(f => f.Difficulty).Count()} {detached.Beatmaps.Select(f => f.Difficulty).Count()}"); - Logger.Log($"Metadata: {live.Metadata} {detached.Metadata}"); + Assert.AreEqual(live.Files.Count, detachedBeatmapSet.Files.Count); + Assert.AreEqual(live.Files.Select(f => f.File).Count(), detachedBeatmapSet.Files.Select(f => f.File).Count()); + Assert.AreEqual(live.Beatmaps.Count, detachedBeatmapSet.Beatmaps.Count); + Assert.AreEqual(live.Beatmaps.Select(f => f.Difficulty).Count(), detachedBeatmapSet.Beatmaps.Select(f => f.Difficulty).Count()); + Assert.AreEqual(live.Metadata, detachedBeatmapSet.Metadata); }); - Logger.Log("Testing detached-ness"); + Debug.Assert(detachedBeatmapSet != null); - Debug.Assert(detached != null); - - Logger.Log($"NamedFiles: {detached.Files.Count}"); - Logger.Log($"Files: {detached.Files.Select(f => f.File).Count()}"); - Logger.Log($"Difficulties: {detached.Beatmaps.Count}"); - Logger.Log($"BeatmapDifficulties: {detached.Beatmaps.Select(f => f.Difficulty).Count()}"); - Logger.Log($"Metadata: {detached.Metadata}"); + // Check detached instances can all be accessed without throwing. + Assert.NotNull(detachedBeatmapSet.Files.Count); + Assert.NotZero(detachedBeatmapSet.Files.Select(f => f.File).Count()); + Assert.NotNull(detachedBeatmapSet.Beatmaps.Count); + Assert.NotZero(detachedBeatmapSet.Beatmaps.Select(f => f.Difficulty).Count()); + Assert.NotNull(detachedBeatmapSet.Metadata); } }); } From c92aff8d2b3ef101358943367bb8806f950ae3c1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 12 Jan 2022 12:42:45 +0900 Subject: [PATCH 314/996] Add test of cyclic beatmap/beatmapset references --- osu.Game.Tests/Database/BeatmapImporterTests.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index 49f992139c..a4dd341226 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -36,7 +36,7 @@ namespace osu.Game.Tests.Database public class BeatmapImporterTests : RealmTest { [Test] - public void TestDetach() + public void TestDetachBeatmapSet() { RunTestWithRealmAsync(async (realmFactory, storage) => { @@ -71,7 +71,11 @@ namespace osu.Game.Tests.Database Assert.NotZero(detachedBeatmapSet.Files.Select(f => f.File).Count()); Assert.NotNull(detachedBeatmapSet.Beatmaps.Count); Assert.NotZero(detachedBeatmapSet.Beatmaps.Select(f => f.Difficulty).Count()); + Assert.NotNull(detachedBeatmapSet.Beatmaps.First().Path); Assert.NotNull(detachedBeatmapSet.Metadata); + + // Check cyclic reference to beatmap set + Assert.AreEqual(detachedBeatmapSet, detachedBeatmapSet.Beatmaps.First().BeatmapSet); } }); } From 580ad03f8d66492f7cd0382dbb372a77e14b1247 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 12 Jan 2022 12:56:28 +0900 Subject: [PATCH 315/996] Combine mapper configurations and add more explanation about special cases --- osu.Game/Database/RealmObjectExtensions.cs | 41 +++------------------- 1 file changed, 4 insertions(+), 37 deletions(-) diff --git a/osu.Game/Database/RealmObjectExtensions.cs b/osu.Game/Database/RealmObjectExtensions.cs index 83d23e2fc2..a5e5962a04 100644 --- a/osu.Game/Database/RealmObjectExtensions.cs +++ b/osu.Game/Database/RealmObjectExtensions.cs @@ -19,43 +19,11 @@ namespace osu.Game.Database { public static class RealmObjectExtensions { - private static readonly IMapper write_mapper = new MapperConfiguration(c => + private static readonly IMapper mapper = new MapperConfiguration(c => { c.ShouldMapField = fi => false; - // If we want to limit this further, we can avoid mapping properties with no setter that are not IList<>. - // Takes a bit of effort to determine whether this is the case though, see https://stackoverflow.com/questions/951536/how-do-i-tell-whether-a-type-implements-ilist - c.ShouldMapProperty = pi => pi.SetMethod?.IsPublic == true; - c.CreateMap(); - c.CreateMap(); - c.CreateMap(); - c.CreateMap(); - c.CreateMap(); - c.CreateMap(); - c.CreateMap(); - c.CreateMap(); - c.CreateMap().MaxDepth(2).AfterMap((s, d) => - { - for (int i = 0; i < d.BeatmapSet?.Beatmaps.Count; i++) - { - if (d.BeatmapSet.Beatmaps[i].Equals(d)) - { - d.BeatmapSet.Beatmaps[i] = d; - break; - } - } - }); - c.CreateMap().MaxDepth(2).AfterMap((s, d) => - { - foreach (var beatmap in d.Beatmaps) - beatmap.BeatmapSet = d; - }); - c.AddGlobalIgnore(nameof(RealmObjectBase.ObjectSchema)); - }).CreateMapper(); - - private static readonly IMapper read_mapper = new MapperConfiguration(c => - { - c.ShouldMapField = fi => false; + // This is specifically to avoid mapping explicit interface implementations. // If we want to limit this further, we can avoid mapping properties with no setter that are not IList<>. // Takes a bit of effort to determine whether this is the case though, see https://stackoverflow.com/questions/951536/how-do-i-tell-whether-a-type-implements-ilist c.ShouldMapProperty = pi => pi.GetMethod?.IsPublic == true; @@ -84,7 +52,6 @@ namespace osu.Game.Database foreach (var beatmap in d.Beatmaps) beatmap.BeatmapSet = d; }); - c.AddGlobalIgnore(nameof(RealmObjectBase.ObjectSchema)); }).CreateMapper(); /// @@ -120,12 +87,12 @@ namespace osu.Game.Database if (!item.IsManaged) return item; - return read_mapper.Map(item); + return mapper.Map(item); } public static void CopyChangesToRealm(this T source, T destination) where T : RealmObjectBase { - write_mapper.Map(source, destination); + mapper.Map(source, destination); } public static List> ToLiveUnmanaged(this IEnumerable realmList) From a307f7e90eb5cbf050b0fc0a2dff9f860a51c1c3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 12 Jan 2022 12:56:42 +0900 Subject: [PATCH 316/996] Add test coverage of updating via copying changes from detached instance --- .../Database/BeatmapImporterTests.cs | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index a4dd341226..0644313bf9 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -80,6 +80,48 @@ namespace osu.Game.Tests.Database }); } + [Test] + public void TestUpdateDetachedBeatmapSet() + { + RunTestWithRealmAsync(async (realmFactory, storage) => + { + using (var importer = new BeatmapModelManager(realmFactory, storage)) + using (new RulesetStore(realmFactory, storage)) + { + ILive? beatmapSet; + + using (var reader = new ZipArchiveReader(TestResources.GetTestBeatmapStream())) + beatmapSet = await importer.Import(reader); + + Assert.NotNull(beatmapSet); + Debug.Assert(beatmapSet != null); + + BeatmapSetInfo? detachedBeatmapSet = null; + + beatmapSet.PerformRead(s => detachedBeatmapSet = s.Detach()); + + Debug.Assert(detachedBeatmapSet != null); + + var newUser = new RealmUser { Username = "peppy", OnlineID = 2 }; + + detachedBeatmapSet.Beatmaps.First().Metadata.Artist = "New Artist"; + detachedBeatmapSet.Beatmaps.First().Metadata.Author = newUser; + + Assert.AreNotEqual(detachedBeatmapSet.Status, BeatmapOnlineStatus.Ranked); + detachedBeatmapSet.Status = BeatmapOnlineStatus.Ranked; + + beatmapSet.PerformWrite(s => detachedBeatmapSet.CopyChangesToRealm(s)); + + beatmapSet.PerformRead(s => + { + Assert.AreEqual(BeatmapOnlineStatus.Ranked, s.Status); + Assert.AreEqual("New Artist", s.Beatmaps.First().Metadata.Artist); + Assert.AreEqual(newUser, s.Beatmaps.First().Metadata.Author); + }); + } + }); + } + [Test] public void TestImportBeatmapThenCleanup() { From a4de0f93fa1a813aa3b9e4782a0302c4a6b4390f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 12 Jan 2022 14:38:37 +0900 Subject: [PATCH 317/996] Move manager `Update` methods to be explicit to where they are still used by legacy code Also fixes skin hash repopulation being completely broken. --- osu.Game/Beatmaps/BeatmapManager.cs | 5 ----- osu.Game/Beatmaps/BeatmapModelManager.cs | 9 +++++++++ osu.Game/Database/IModelManager.cs | 7 ------- osu.Game/Scoring/ScoreManager.cs | 5 ----- osu.Game/Skinning/SkinModelManager.cs | 4 ++-- osu.Game/Stores/RealmArchiveModelManager.cs | 9 --------- 6 files changed, 11 insertions(+), 28 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 21cda18c1a..f901b0fbc1 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -220,11 +220,6 @@ namespace osu.Game.Beatmaps return beatmapModelManager.IsAvailableLocally(model); } - public void Update(BeatmapSetInfo item) - { - beatmapModelManager.Update(item); - } - public bool Delete(BeatmapSetInfo item) { return beatmapModelManager.Delete(item); diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs index 62d607c4f7..73ada09ef6 100644 --- a/osu.Game/Beatmaps/BeatmapModelManager.cs +++ b/osu.Game/Beatmaps/BeatmapModelManager.cs @@ -101,5 +101,14 @@ namespace osu.Game.Beatmaps using (var context = ContextFactory.CreateContext()) return context.All().FirstOrDefault(query)?.Detach(); } + + public void Update(BeatmapSetInfo item) + { + using (var realm = ContextFactory.CreateContext()) + { + var existing = realm.Find(item.ID); + realm.Write(r => item.CopyChangesToRealm(existing)); + } + } } } diff --git a/osu.Game/Database/IModelManager.cs b/osu.Game/Database/IModelManager.cs index e7218b621c..187ac86a59 100644 --- a/osu.Game/Database/IModelManager.cs +++ b/osu.Game/Database/IModelManager.cs @@ -12,13 +12,6 @@ namespace osu.Game.Database public interface IModelManager where TModel : class { - /// - /// Perform an update of the specified item. - /// TODO: Support file additions/removals. - /// - /// The item to update. - void Update(TModel item); - /// /// Delete an item from the manager. /// Is a no-op for already deleted items. diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 62e61d3a94..ce5d2acc21 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -251,11 +251,6 @@ namespace osu.Game.Scoring #region Implementation of IModelManager - public void Update(ScoreInfo item) - { - scoreModelManager.Update(item); - } - public bool Delete(ScoreInfo item) { return scoreModelManager.Delete(item); diff --git a/osu.Game/Skinning/SkinModelManager.cs b/osu.Game/Skinning/SkinModelManager.cs index 66a1808dc1..1689a89b43 100644 --- a/osu.Game/Skinning/SkinModelManager.cs +++ b/osu.Game/Skinning/SkinModelManager.cs @@ -210,13 +210,13 @@ namespace osu.Game.Skinning { using (var realm = ContextFactory.CreateContext()) { - var skinsWithoutHashes = realm.All().Where(i => string.IsNullOrEmpty(i.Hash)).ToArray(); + var skinsWithoutHashes = realm.All().Where(i => !i.Protected && string.IsNullOrEmpty(i.Hash)).ToArray(); foreach (SkinInfo skin in skinsWithoutHashes) { try { - Update(skin); + checkSkinIniMetadata(skin, realm); } catch (Exception e) { diff --git a/osu.Game/Stores/RealmArchiveModelManager.cs b/osu.Game/Stores/RealmArchiveModelManager.cs index 3783b10a8c..4365fdab1e 100644 --- a/osu.Game/Stores/RealmArchiveModelManager.cs +++ b/osu.Game/Stores/RealmArchiveModelManager.cs @@ -201,14 +201,5 @@ namespace osu.Game.Stores } public abstract bool IsAvailableLocally(TModel model); - - public void Update(TModel model) - { - using (var realm = ContextFactory.CreateContext()) - { - var existing = realm.Find(model.ID); - realm.Write(r => model.CopyChangesToRealm(existing)); - } - } } } From f24b2b1be3c408e035aef9dd13ded223cd0f0ac6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 12 Jan 2022 14:38:58 +0900 Subject: [PATCH 318/996] Make copying detached changes to realm only exposed for `BeatmapSet` Also fixes remaining issues with the copy process. --- .../Database/BeatmapImporterTests.cs | 14 ++++- osu.Game/Beatmaps/BeatmapInfo.cs | 1 + osu.Game/Database/RealmObjectExtensions.cs | 61 +++++++++++++++++-- 3 files changed, 71 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index 0644313bf9..1bba73bea0 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -110,13 +110,25 @@ namespace osu.Game.Tests.Database Assert.AreNotEqual(detachedBeatmapSet.Status, BeatmapOnlineStatus.Ranked); detachedBeatmapSet.Status = BeatmapOnlineStatus.Ranked; - beatmapSet.PerformWrite(s => detachedBeatmapSet.CopyChangesToRealm(s)); + beatmapSet.PerformWrite(s => + { + detachedBeatmapSet.CopyChangesToRealm(s); + }); beatmapSet.PerformRead(s => { + // Check above changes explicitly. Assert.AreEqual(BeatmapOnlineStatus.Ranked, s.Status); Assert.AreEqual("New Artist", s.Beatmaps.First().Metadata.Artist); Assert.AreEqual(newUser, s.Beatmaps.First().Metadata.Author); + Assert.NotZero(s.Files.Count); + + // Check nothing was lost in the copy operation. + Assert.AreEqual(s.Files.Count, detachedBeatmapSet.Files.Count); + Assert.AreEqual(s.Files.Select(f => f.File).Count(), detachedBeatmapSet.Files.Select(f => f.File).Count()); + Assert.AreEqual(s.Beatmaps.Count, detachedBeatmapSet.Beatmaps.Count); + Assert.AreEqual(s.Beatmaps.Select(f => f.Difficulty).Count(), detachedBeatmapSet.Beatmaps.Select(f => f.Difficulty).Count()); + Assert.AreEqual(s.Metadata, detachedBeatmapSet.Metadata); }); } }); diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 53aba67275..1b732b3c8a 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -162,6 +162,7 @@ namespace osu.Game.Beatmaps public Guid BeatmapSetInfoID => BeatmapSet?.ID ?? Guid.Empty; [Ignored] + [IgnoreMap] public BeatmapDifficulty BaseDifficulty { get => Difficulty; diff --git a/osu.Game/Database/RealmObjectExtensions.cs b/osu.Game/Database/RealmObjectExtensions.cs index a5e5962a04..188d619627 100644 --- a/osu.Game/Database/RealmObjectExtensions.cs +++ b/osu.Game/Database/RealmObjectExtensions.cs @@ -19,6 +19,50 @@ namespace osu.Game.Database { public static class RealmObjectExtensions { + private static readonly IMapper write_mapper = new MapperConfiguration(c => + { + c.ShouldMapField = fi => false; + c.ShouldMapProperty = pi => pi.SetMethod?.IsPublic == true; + + c.CreateMap() + .ForMember(s => s.Author, cc => cc.Ignore()) + .AfterMap((s, d) => + { + copyChangesToRealm(s.Author, d.Author); + }); + c.CreateMap(); + c.CreateMap(); + c.CreateMap(); + c.CreateMap(); + c.CreateMap() + .ForMember(s => s.Ruleset, cc => cc.Ignore()) + .ForMember(s => s.Metadata, cc => cc.Ignore()) + .ForMember(s => s.Difficulty, cc => cc.Ignore()) + .ForMember(s => s.BeatmapSet, cc => cc.Ignore()) + .AfterMap((s, d) => + { + d.Ruleset = d.Realm.Find(s.Ruleset.ShortName); + copyChangesToRealm(s.Difficulty, d.Difficulty); + copyChangesToRealm(s.Metadata, d.Metadata); + }); + c.CreateMap() + .ForMember(s => s.Beatmaps, cc => cc.Ignore()) + .AfterMap((s, d) => + { + foreach (var beatmap in s.Beatmaps) + { + var existing = d.Beatmaps.FirstOrDefault(b => b.ID == beatmap.ID); + + if (existing != null) + copyChangesToRealm(beatmap, existing); + else + d.Beatmaps.Add(beatmap); + } + }); + + c.AddGlobalIgnore(nameof(RealmObjectBase.ObjectSchema)); + }).CreateMapper(); + private static readonly IMapper mapper = new MapperConfiguration(c => { c.ShouldMapField = fi => false; @@ -52,6 +96,8 @@ namespace osu.Game.Database foreach (var beatmap in d.Beatmaps) beatmap.BeatmapSet = d; }); + + c.AddGlobalIgnore(nameof(RealmObjectBase.ObjectSchema)); }).CreateMapper(); /// @@ -90,10 +136,17 @@ namespace osu.Game.Database return mapper.Map(item); } - public static void CopyChangesToRealm(this T source, T destination) where T : RealmObjectBase - { - mapper.Map(source, destination); - } + /// + /// Copy changes in a detached beatmap back to realm. + /// This is a temporary method to handle existing flows only. It should not be used going forward if we can avoid it. + /// + /// The detached beatmap to copy from. + /// The live beatmap to copy to. + public static void CopyChangesToRealm(this BeatmapSetInfo source, BeatmapSetInfo destination) + => copyChangesToRealm(source, destination); + + private static void copyChangesToRealm(T source, T destination) where T : RealmObjectBase + => write_mapper.Map(source, destination); public static List> ToLiveUnmanaged(this IEnumerable realmList) where T : RealmObject, IHasGuidPrimaryKey From 5f7365e8f39e510021a9d123c19892d09e2a65a8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 12 Jan 2022 15:09:56 +0900 Subject: [PATCH 319/996] Ensure scores are cleaned up alongside beatmap so they don't have a null reference --- osu.Game/Beatmaps/BeatmapInfo.cs | 5 +++++ osu.Game/Database/RealmContextFactory.cs | 14 ++++++++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 1b732b3c8a..8a2ee95020 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -11,6 +11,7 @@ using osu.Game.Database; using osu.Game.Models; using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets; +using osu.Game.Scoring; using Realms; #nullable enable @@ -36,6 +37,10 @@ namespace osu.Game.Beatmaps public BeatmapMetadata Metadata { get; set; } = new BeatmapMetadata(); + [IgnoreMap] + [Backlink(nameof(ScoreInfo.Beatmap))] + public IQueryable Scores { get; } = null!; + public BeatmapInfo(RulesetInfo ruleset, BeatmapDifficulty difficulty, BeatmapMetadata metadata) { Ruleset = ruleset; diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 968caaa7ce..1aa6b2043c 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -113,12 +113,18 @@ namespace osu.Game.Database { var pendingDeleteSets = realm.All().Where(s => s.DeletePending); - foreach (var s in pendingDeleteSets) + foreach (var beatmapSet in pendingDeleteSets) { - foreach (var b in s.Beatmaps) - realm.Remove(b); + foreach (var beatmap in beatmapSet.Beatmaps) + { + // Cascade delete related scores, else they will have a null beatmap against the model's spec. + foreach (var score in beatmap.Scores) + realm.Remove(score); - realm.Remove(s); + realm.Remove(beatmap); + } + + realm.Remove(beatmapSet); } var pendingDeleteSkins = realm.All().Where(s => s.DeletePending); From 34aa1bf21d46e404fb18e0e5ca6045967c2a7f2c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 12 Jan 2022 15:34:39 +0900 Subject: [PATCH 320/996] Sanitise and remove some usages of `Detach` which are no longer required --- osu.Game.Tests/Scores/IO/ImportScoreTest.cs | 3 +-- .../Visual/UserInterface/TestSceneDeleteLocalScore.cs | 2 +- osu.Game/Beatmaps/BeatmapInfo.cs | 2 +- osu.Game/Beatmaps/WorkingBeatmapCache.cs | 1 - osu.Game/Screens/Select/BeatmapCarousel.cs | 4 +--- 5 files changed, 4 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs index 22b792cee4..9b583494cb 100644 --- a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs +++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs @@ -10,7 +10,6 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Platform; using osu.Game.Beatmaps; -using osu.Game.Database; using osu.Game.IO.Archives; using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets.Mods; @@ -162,7 +161,7 @@ namespace osu.Game.Tests.Scores.IO var scoreManager = osu.Dependencies.Get(); await scoreManager.Import(score, archive); - return scoreManager.Query(_ => true).Detach(); + return scoreManager.Query(_ => true); } internal class TestArchiveReader : ArchiveReader diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index 17168bf07f..63df6a6390 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -95,7 +95,7 @@ namespace osu.Game.Tests.Visual.UserInterface imported?.PerformRead(s => { - beatmapInfo = s.Beatmaps[0].Detach(); + beatmapInfo = s.Beatmaps[0]; for (int i = 0; i < 50; i++) { diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 8a2ee95020..fb32c11365 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -188,7 +188,7 @@ namespace osu.Game.Beatmaps public int BeatmapVersion; - public BeatmapInfo Clone() => (BeatmapInfo)this.Detach().MemberwiseClone(); + public BeatmapInfo Clone() => (BeatmapInfo)MemberwiseClone(); public override string ToString() => this.GetDisplayTitle(); diff --git a/osu.Game/Beatmaps/WorkingBeatmapCache.cs b/osu.Game/Beatmaps/WorkingBeatmapCache.cs index e1399c5272..6947752c47 100644 --- a/osu.Game/Beatmaps/WorkingBeatmapCache.cs +++ b/osu.Game/Beatmaps/WorkingBeatmapCache.cs @@ -84,7 +84,6 @@ namespace osu.Game.Beatmaps if (working != null) return working; - // TODO: FUCK THE WORLD :D beatmapInfo = beatmapInfo.Detach(); workingCache.Add(working = new BeatmapManagerWorkingBeatmap(beatmapInfo, this)); diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index f355554482..8b4de447e5 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -682,9 +682,7 @@ namespace osu.Game.Screens.Select if (beatmapSet.Beatmaps.All(b => b.Hidden)) return null; - // TODO: FUCK THE WORLD :D - if (beatmapSet.IsManaged) - beatmapSet = beatmapSet.Detach(); + beatmapSet = beatmapSet.Detach(); var set = new CarouselBeatmapSet(beatmapSet) { From e12025dd48018cbdf4feb298026539b4878f2591 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 12 Jan 2022 16:17:11 +0900 Subject: [PATCH 321/996] Cascade delete metadata when beatmaps are deleted --- osu.Game/Database/RealmContextFactory.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 1aa6b2043c..0802641b06 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -121,6 +121,8 @@ namespace osu.Game.Database foreach (var score in beatmap.Scores) realm.Remove(score); + realm.Remove(beatmap.Metadata); + realm.Remove(beatmap); } From eb70a1eeb70004c71e301ca9163eacd5bd351026 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 12 Jan 2022 18:05:25 +0900 Subject: [PATCH 322/996] Replace compatibility properties with direct references --- osu.Game/Beatmaps/BeatmapInfo.cs | 2 +- osu.Game/Models/RealmNamedFileUsage.cs | 10 ---------- .../Edit/Checks/CheckTooShortAudioFiles.cs | 2 +- .../Rulesets/Edit/Checks/CheckZeroByteFiles.cs | 2 +- osu.Game/Scoring/ScoreInfo.cs | 14 ++++---------- osu.Game/Scoring/ScoreManager.cs | 8 ++++---- osu.Game/Scoring/ScoreModelManager.cs | 4 ++-- osu.Game/Screens/Select/Carousel/TopLocalRank.cs | 2 +- .../Select/Leaderboards/BeatmapLeaderboard.cs | 2 +- osu.Game/Storyboards/Storyboard.cs | 2 +- 10 files changed, 16 insertions(+), 32 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index fb32c11365..1c1f81f143 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -38,7 +38,7 @@ namespace osu.Game.Beatmaps public BeatmapMetadata Metadata { get; set; } = new BeatmapMetadata(); [IgnoreMap] - [Backlink(nameof(ScoreInfo.Beatmap))] + [Backlink(nameof(ScoreInfo.BeatmapInfo))] public IQueryable Scores { get; } = null!; public BeatmapInfo(RulesetInfo ruleset, BeatmapDifficulty difficulty, BeatmapMetadata metadata) diff --git a/osu.Game/Models/RealmNamedFileUsage.cs b/osu.Game/Models/RealmNamedFileUsage.cs index 801c826292..17e32510a8 100644 --- a/osu.Game/Models/RealmNamedFileUsage.cs +++ b/osu.Game/Models/RealmNamedFileUsage.cs @@ -31,15 +31,5 @@ namespace osu.Game.Models } IFileInfo INamedFileUsage.File => File; - - #region Compatibility properties - - public RealmFile FileInfo - { - get => File; - set => File = value; - } - - #endregion } } diff --git a/osu.Game/Rulesets/Edit/Checks/CheckTooShortAudioFiles.cs b/osu.Game/Rulesets/Edit/Checks/CheckTooShortAudioFiles.cs index 5cc98c5537..381ebd4af6 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckTooShortAudioFiles.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckTooShortAudioFiles.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Edit.Checks foreach (var file in beatmapSet.Files) { - using (Stream data = context.WorkingBeatmap.GetStream(file.FileInfo.GetStoragePath())) + using (Stream data = context.WorkingBeatmap.GetStream(file.File.GetStoragePath())) { if (data == null) continue; diff --git a/osu.Game/Rulesets/Edit/Checks/CheckZeroByteFiles.cs b/osu.Game/Rulesets/Edit/Checks/CheckZeroByteFiles.cs index ab9959aec2..324ebe6b50 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckZeroByteFiles.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckZeroByteFiles.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Edit.Checks foreach (var file in beatmapSet.Files) { - using (Stream data = context.WorkingBeatmap.GetStream(file.FileInfo.GetStoragePath())) + using (Stream data = context.WorkingBeatmap.GetStream(file.File.GetStoragePath())) { if (data?.Length == 0) yield return new IssueTemplateZeroBytes(this).Create(file.Filename); diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 61df736a4a..ffec54aca7 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -49,7 +49,7 @@ namespace osu.Game.Scoring public ScoreInfo(BeatmapInfo beatmap, RulesetInfo ruleset, RealmUser realmUser) { Ruleset = ruleset; - Beatmap = beatmap; + BeatmapInfo = beatmap; RealmUser = realmUser; } @@ -94,7 +94,7 @@ namespace osu.Game.Scoring public double? PP { get; set; } - public BeatmapInfo Beatmap { get; set; } = null!; + public BeatmapInfo BeatmapInfo { get; set; } = null!; public RulesetInfo Ruleset { get; set; } = null!; @@ -129,7 +129,7 @@ namespace osu.Game.Scoring public int RankInt { get; set; } IRulesetInfo IScoreInfo.Ruleset => Ruleset; - IBeatmapInfo IScoreInfo.Beatmap => Beatmap; + IBeatmapInfo IScoreInfo.Beatmap => BeatmapInfo; IUser IScoreInfo.User => User; IEnumerable IHasNamedFiles.Files => Files; @@ -139,13 +139,7 @@ namespace osu.Game.Scoring private Mod[]? mods; - public Guid BeatmapInfoID => Beatmap.ID; - - public BeatmapInfo BeatmapInfo - { - get => Beatmap; - set => Beatmap = value; - } + public Guid BeatmapInfoID => BeatmapInfo.ID; public int UserID => RealmUser.OnlineID; diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index ce5d2acc21..ad2be7d813 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -70,7 +70,7 @@ namespace osu.Game.Scoring // Compute difficulties asynchronously first to prevent blocking via the GetTotalScore() call below. foreach (var s in scores) { - await difficultyCache.GetDifficultyAsync(s.Beatmap, s.Ruleset, s.Mods, cancellationToken).ConfigureAwait(false); + await difficultyCache.GetDifficultyAsync(s.BeatmapInfo, s.Ruleset, s.Mods, cancellationToken).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); } } @@ -154,11 +154,11 @@ namespace osu.Game.Scoring // This score is guaranteed to be an osu!stable score. // The combo must be determined through either the beatmap's max combo value or the difficulty calculator, as lazer's scoring has changed and the score statistics cannot be used. - if (score.Beatmap.MaxCombo != null) - beatmapMaxCombo = score.Beatmap.MaxCombo.Value; + if (score.BeatmapInfo.MaxCombo != null) + beatmapMaxCombo = score.BeatmapInfo.MaxCombo.Value; else { - if (!score.Beatmap.IsManaged || difficulties == null) + if (!score.BeatmapInfo.IsManaged || difficulties == null) { // We don't have enough information (max combo) to compute the score, so use the provided score. return score.TotalScore; diff --git a/osu.Game/Scoring/ScoreModelManager.cs b/osu.Game/Scoring/ScoreModelManager.cs index 1068aba231..09a5919f12 100644 --- a/osu.Game/Scoring/ScoreModelManager.cs +++ b/osu.Game/Scoring/ScoreModelManager.cs @@ -58,8 +58,8 @@ namespace osu.Game.Scoring protected override Task Populate(ScoreInfo model, ArchiveReader? archive, Realm realm, CancellationToken cancellationToken = default) { // Ensure the beatmap is not detached. - if (!model.Beatmap.IsManaged) - model.Beatmap = realm.Find(model.Beatmap.ID); + if (!model.BeatmapInfo.IsManaged) + model.BeatmapInfo = realm.Find(model.BeatmapInfo.ID); if (!model.Ruleset.IsManaged) model.Ruleset = realm.Find(model.Ruleset.ShortName); diff --git a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs index 9a0085c62a..fb1fa09253 100644 --- a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs +++ b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs @@ -49,7 +49,7 @@ namespace osu.Game.Screens.Select.Carousel base.LoadComplete(); scoreSubscription = realmFactory.Context.All() - .Filter($"{nameof(ScoreInfo.Beatmap)}.{nameof(BeatmapInfo.ID)} = $0", beatmapInfo.ID) + .Filter($"{nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} = $0", beatmapInfo.ID) .QueryAsyncWithNotifications((_, changes, ___) => { if (changes == null) diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index d03a8b645d..49f2ea5d64 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -110,7 +110,7 @@ namespace osu.Game.Screens.Select.Leaderboards return; scoreSubscription = realmFactory.Context.All() - .Filter($"{nameof(ScoreInfo.Beatmap)}.{nameof(BeatmapInfo.ID)} = $0", beatmapInfo.ID) + .Filter($"{nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} = $0", beatmapInfo.ID) .QueryAsyncWithNotifications((_, changes, ___) => { if (changes == null) diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs index 9502dd8968..ff496aea36 100644 --- a/osu.Game/Storyboards/Storyboard.cs +++ b/osu.Game/Storyboards/Storyboard.cs @@ -97,7 +97,7 @@ namespace osu.Game.Storyboards { Drawable drawable = null; - string storyboardPath = BeatmapInfo.BeatmapSet?.Files.FirstOrDefault(f => f.Filename.Equals(path, StringComparison.OrdinalIgnoreCase))?.FileInfo.GetStoragePath(); + string storyboardPath = BeatmapInfo.BeatmapSet?.Files.FirstOrDefault(f => f.Filename.Equals(path, StringComparison.OrdinalIgnoreCase))?.File.GetStoragePath(); if (!string.IsNullOrEmpty(storyboardPath)) drawable = new Sprite { Texture = textureStore.Get(storyboardPath) }; From ef0f794fd61c61ec776380bfdc3a5479f765bf3c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 12 Jan 2022 18:08:26 +0900 Subject: [PATCH 323/996] Remove stay newline --- osu.Game/Screens/Select/BeatmapCarousel.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 8b4de447e5..734d9bbde7 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -158,7 +158,6 @@ namespace osu.Game.Screens.Select private readonly DrawablePool setPool = new DrawablePool(100); public BeatmapCarousel() - { root = new CarouselRoot(this); InternalChild = new OsuContextMenuContainer From 4106ebf881190418427f27f370637eb319c8839f Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 12 Jan 2022 18:29:23 +0900 Subject: [PATCH 324/996] Fix mania requiring PERFECTs to maintain HP --- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 2 +- .../Scoring/ManiaHealthProcessor.cs | 23 +++++++++++++++++++ .../Rulesets/Scoring/JudgementProcessor.cs | 9 +++++++- 3 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 osu.Game.Rulesets.Mania/Scoring/ManiaHealthProcessor.cs diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index b0e7545d3e..6fc7dc018b 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Mania public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor(); - public override HealthProcessor CreateHealthProcessor(double drainStartTime) => new DrainingHealthProcessor(drainStartTime, 0.5); + public override HealthProcessor CreateHealthProcessor(double drainStartTime) => new ManiaHealthProcessor(drainStartTime, 0.5); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap, this); diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaHealthProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaHealthProcessor.cs new file mode 100644 index 0000000000..57c2ba9c6d --- /dev/null +++ b/osu.Game.Rulesets.Mania/Scoring/ManiaHealthProcessor.cs @@ -0,0 +1,23 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Mania.Scoring +{ + public class ManiaHealthProcessor : DrainingHealthProcessor + { + /// + public ManiaHealthProcessor(double drainStartTime, double drainLenience = 0) + : base(drainStartTime, drainLenience) + { + } + + protected override HitResult GetSimulatedHitResult(Judgement judgement) + { + // Users are not expected to attain perfect judgements for all notes due to the tighter hit window. + return judgement.MaxResult == HitResult.Perfect ? HitResult.Great : judgement.MaxResult; + } + } +} diff --git a/osu.Game/Rulesets/Scoring/JudgementProcessor.cs b/osu.Game/Rulesets/Scoring/JudgementProcessor.cs index ed4a16f0e8..c3c4a2c949 100644 --- a/osu.Game/Rulesets/Scoring/JudgementProcessor.cs +++ b/osu.Game/Rulesets/Scoring/JudgementProcessor.cs @@ -135,7 +135,7 @@ namespace osu.Game.Rulesets.Scoring if (result == null) throw new InvalidOperationException($"{GetType().ReadableName()} must provide a {nameof(JudgementResult)} through {nameof(CreateResult)}."); - result.Type = judgement.MaxResult; + result.Type = GetSimulatedHitResult(judgement); ApplyResult(result); } } @@ -145,5 +145,12 @@ namespace osu.Game.Rulesets.Scoring base.Update(); hasCompleted.Value = JudgedHits == MaxHits && (JudgedHits == 0 || lastAppliedResult.TimeAbsolute < Clock.CurrentTime); } + + /// + /// Gets a simulated for a judgement. Used during to simulate a "perfect" play. + /// + /// The judgement to simulate a for. + /// The simulated for the judgement. + protected virtual HitResult GetSimulatedHitResult(Judgement judgement) => judgement.MaxResult; } } From 8c8c5f4c3340b72846b02428936a8f7e485bf4a9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 12 Jan 2022 14:38:37 +0900 Subject: [PATCH 325/996] Fix skin hash repopulation not working since realm migration --- osu.Game/Skinning/SkinModelManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/SkinModelManager.cs b/osu.Game/Skinning/SkinModelManager.cs index 822cb8efa0..964d99a2e5 100644 --- a/osu.Game/Skinning/SkinModelManager.cs +++ b/osu.Game/Skinning/SkinModelManager.cs @@ -210,13 +210,13 @@ namespace osu.Game.Skinning { using (var realm = ContextFactory.CreateContext()) { - var skinsWithoutHashes = realm.All().Where(i => string.IsNullOrEmpty(i.Hash)).ToArray(); + var skinsWithoutHashes = realm.All().Where(i => !i.Protected && string.IsNullOrEmpty(i.Hash)).ToArray(); foreach (SkinInfo skin in skinsWithoutHashes) { try { - Update(skin); + realm.Write(r => skin.Hash = ComputeHash(skin)); } catch (Exception e) { From 38cc1ce098c9e957a0d46106c2f8857177148f38 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 12 Jan 2022 18:51:30 +0900 Subject: [PATCH 326/996] Add missing ruleset in test scores --- .../Visual/SongSelect/TestSceneUserTopScoreContainer.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScoreContainer.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScoreContainer.cs index 7af9e9eb40..dd7f9951bf 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScoreContainer.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScoreContainer.cs @@ -10,6 +10,7 @@ using osuTK.Graphics; using osu.Game.Online.Leaderboards; using osu.Game.Overlays; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu; using osu.Game.Scoring; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Users; @@ -61,6 +62,7 @@ namespace osu.Game.Tests.Visual.SongSelect MaxCombo = 244, TotalScore = 1707827, Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + Ruleset = new OsuRuleset().RulesetInfo, User = new APIUser { Id = 6602580, @@ -79,6 +81,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 1, MaxCombo = 244, TotalScore = 1707827, + Ruleset = new OsuRuleset().RulesetInfo, User = new APIUser { Id = 4608074, @@ -97,6 +100,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 1, MaxCombo = 244, TotalScore = 1707827, + Ruleset = new OsuRuleset().RulesetInfo, User = new APIUser { Id = 1541390, From 51251e3204aa4b5097eabecc0f312d244672022f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 12 Jan 2022 22:34:07 +0900 Subject: [PATCH 327/996] Fix CI reported warnings --- .../Statistics/AccuracyHeatmap.cs | 2 +- .../Beatmaps/Formats/OsuJsonDecoderTest.cs | 2 +- .../Beatmaps/IO/OszArchiveReaderTest.cs | 2 +- .../Beatmaps/TestSceneEditorBeatmap.cs | 68 +++++++++++++++++-- osu.Game.Tests/Scores/IO/ImportScoreTest.cs | 6 -- .../TestSceneMultiplayerMatchSongSelect.cs | 2 +- .../SongSelect/TestSceneAdvancedStats.cs | 2 +- osu.Game.Tournament/TournamentGameBase.cs | 2 +- .../Online/API/Requests/Responses/APIScore.cs | 4 +- osu.Game/Online/Rooms/MultiplayerScore.cs | 2 +- .../Sections/Ranks/DrawableProfileScore.cs | 10 ++- .../Edit/Checks/CheckBackgroundQuality.cs | 2 +- .../Rulesets/Edit/Checks/CheckFilePresence.cs | 2 +- .../Edit/Checks/CheckTooShortAudioFiles.cs | 45 ++++++------ .../Edit/Checks/CheckZeroByteFiles.cs | 11 +-- .../Mods/DifficultyAdjustSettingsControl.cs | 3 - .../Components/Menus/DifficultyMenuItem.cs | 2 +- .../Screens/Edit/Compose/ComposeScreen.cs | 2 +- osu.Game/Screens/Edit/Editor.cs | 3 +- osu.Game/Screens/Edit/EditorBeatmap.cs | 2 +- osu.Game/Screens/Edit/Setup/SetupScreen.cs | 2 +- .../Ranking/Statistics/StatisticsPanel.cs | 6 +- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 2 +- osu.Game/Screens/Select/FilterControl.cs | 2 +- .../Tests/Beatmaps/HitObjectSampleTest.cs | 2 + 25 files changed, 123 insertions(+), 65 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs index db4a6eb50b..6c76da7925 100644 --- a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs +++ b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs @@ -174,7 +174,7 @@ namespace osu.Game.Rulesets.Osu.Statistics pointGrid.Content = points; - if (score.HitEvents == null || score.HitEvents.Count == 0) + if (score.HitEvents.Count == 0) return; // Todo: This should probably not be done like this. diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs index bfd6ff0314..06ed638e0a 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs @@ -31,7 +31,7 @@ namespace osu.Game.Tests.Beatmaps.Formats { var beatmap = decodeAsJson(normal); var meta = beatmap.BeatmapInfo.Metadata; - Assert.AreEqual(241526, beatmap.BeatmapInfo.BeatmapSet.OnlineID); + Assert.AreEqual(241526, beatmap.BeatmapInfo.BeatmapSet?.OnlineID); Assert.AreEqual("Soleily", meta.Artist); Assert.AreEqual("Soleily", meta.ArtistUnicode); Assert.AreEqual("03. Renatus - Soleily 192kbps.mp3", meta.AudioFile); diff --git a/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs b/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs index b2ab1eeaa6..810ea5dbd0 100644 --- a/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs @@ -56,7 +56,7 @@ namespace osu.Game.Tests.Beatmaps.IO var meta = beatmap.Metadata; - Assert.AreEqual(241526, beatmap.BeatmapInfo.BeatmapSet.OnlineID); + Assert.AreEqual(241526, beatmap.BeatmapInfo.BeatmapSet?.OnlineID); Assert.AreEqual("Soleily", meta.Artist); Assert.AreEqual("Soleily", meta.ArtistUnicode); Assert.AreEqual("03. Renatus - Soleily 192kbps.mp3", meta.AudioFile); diff --git a/osu.Game.Tests/Beatmaps/TestSceneEditorBeatmap.cs b/osu.Game.Tests/Beatmaps/TestSceneEditorBeatmap.cs index bf5b517603..153788c2cf 100644 --- a/osu.Game.Tests/Beatmaps/TestSceneEditorBeatmap.cs +++ b/osu.Game.Tests/Beatmaps/TestSceneEditorBeatmap.cs @@ -7,6 +7,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Testing; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit; @@ -30,7 +31,13 @@ namespace osu.Game.Tests.Beatmaps AddStep("add beatmap", () => { - Child = editorBeatmap = new EditorBeatmap(new OsuBeatmap()); + Child = editorBeatmap = new EditorBeatmap(new OsuBeatmap + { + BeatmapInfo = + { + Ruleset = new OsuRuleset().RulesetInfo, + }, + }); editorBeatmap.HitObjectAdded += h => addedObject = h; }); @@ -49,7 +56,14 @@ namespace osu.Game.Tests.Beatmaps EditorBeatmap editorBeatmap = null; AddStep("add beatmap", () => { - Child = editorBeatmap = new EditorBeatmap(new OsuBeatmap { HitObjects = { hitCircle } }); + Child = editorBeatmap = new EditorBeatmap(new OsuBeatmap + { + BeatmapInfo = + { + Ruleset = new OsuRuleset().RulesetInfo, + }, + HitObjects = { hitCircle } + }); editorBeatmap.HitObjectRemoved += h => removedObject = h; }); AddStep("remove hitobject", () => editorBeatmap.Remove(editorBeatmap.HitObjects.First())); @@ -71,7 +85,14 @@ namespace osu.Game.Tests.Beatmaps { EditorBeatmap editorBeatmap; - Child = editorBeatmap = new EditorBeatmap(new OsuBeatmap { HitObjects = { hitCircle } }); + Child = editorBeatmap = new EditorBeatmap(new OsuBeatmap + { + BeatmapInfo = + { + Ruleset = new OsuRuleset().RulesetInfo, + }, + HitObjects = { hitCircle } + }); editorBeatmap.HitObjectUpdated += h => changedObject = h; }); @@ -91,7 +112,13 @@ namespace osu.Game.Tests.Beatmaps AddStep("add beatmap", () => { - Child = editorBeatmap = new EditorBeatmap(new OsuBeatmap()); + Child = editorBeatmap = new EditorBeatmap(new OsuBeatmap + { + BeatmapInfo = + { + Ruleset = new OsuRuleset().RulesetInfo, + }, + }); editorBeatmap.HitObjectUpdated += h => changedObject = h; }); @@ -111,7 +138,14 @@ namespace osu.Game.Tests.Beatmaps public void TestRemovedHitObjectStartTimeChangeEvent() { var hitCircle = new HitCircle(); - var editorBeatmap = new EditorBeatmap(new OsuBeatmap { HitObjects = { hitCircle } }); + var editorBeatmap = new EditorBeatmap(new OsuBeatmap + { + BeatmapInfo = + { + Ruleset = new OsuRuleset().RulesetInfo, + }, + HitObjects = { hitCircle } + }); HitObject changedObject = null; editorBeatmap.HitObjectUpdated += h => changedObject = h; @@ -131,6 +165,10 @@ namespace osu.Game.Tests.Beatmaps { var editorBeatmap = new EditorBeatmap(new OsuBeatmap { + BeatmapInfo = + { + Ruleset = new OsuRuleset().RulesetInfo, + }, HitObjects = { new HitCircle(), @@ -156,6 +194,10 @@ namespace osu.Game.Tests.Beatmaps var editorBeatmap = new EditorBeatmap(new OsuBeatmap { + BeatmapInfo = + { + Ruleset = new OsuRuleset().RulesetInfo, + }, HitObjects = { new HitCircle(), @@ -185,7 +227,13 @@ namespace osu.Game.Tests.Beatmaps { updatedObjects.Clear(); - Child = editorBeatmap = new EditorBeatmap(new OsuBeatmap()); + Child = editorBeatmap = new EditorBeatmap(new OsuBeatmap + { + BeatmapInfo = + { + Ruleset = new OsuRuleset().RulesetInfo, + }, + }); for (int i = 0; i < 10; i++) { @@ -220,7 +268,13 @@ namespace osu.Game.Tests.Beatmaps { updatedObjects.Clear(); - Child = editorBeatmap = new EditorBeatmap(new OsuBeatmap()); + Child = editorBeatmap = new EditorBeatmap(new OsuBeatmap + { + BeatmapInfo = + { + Ruleset = new OsuRuleset().RulesetInfo, + }, + }); editorBeatmap.Add(new HitCircle()); }); diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs index 9b583494cb..2b6c9f76fc 100644 --- a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs +++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs @@ -9,11 +9,9 @@ using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Platform; -using osu.Game.Beatmaps; using osu.Game.IO.Archives; using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; @@ -151,12 +149,8 @@ namespace osu.Game.Tests.Scores.IO public static async Task LoadScoreIntoOsu(OsuGameBase osu, ScoreInfo score, ArchiveReader archive = null) { - var beatmapManager = osu.Dependencies.Get(); - // clone to avoid attaching the input score to realm. score = score.DeepClone(); - score.BeatmapInfo ??= beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps.First(); - score.Ruleset ??= new OsuRuleset().RulesetInfo; var scoreManager = osu.Dependencies.Get(); await scoreManager.Import(score, archive); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index 7f7c97ac48..c6fb418a5b 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -70,7 +70,7 @@ namespace osu.Game.Tests.Visual.Multiplayer var beatmap = new BeatmapInfo { - Ruleset = rulesets.GetRuleset(i % 4), + Ruleset = rulesets.GetRuleset(i % 4) ?? throw new InvalidOperationException(), OnlineID = beatmapId, Length = length, BPM = bpm, diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs index e916e8d9ac..bc3e8553f7 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs @@ -67,7 +67,7 @@ namespace osu.Game.Tests.Visual.SongSelect { AddStep("set beatmap", () => advancedStats.BeatmapInfo = new BeatmapInfo { - Ruleset = rulesets.GetRuleset(3), + Ruleset = rulesets.GetRuleset(3) ?? throw new InvalidOperationException(), BaseDifficulty = new BeatmapDifficulty { CircleSize = 5, diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index d08322a3e8..75b929273c 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -81,7 +81,7 @@ namespace osu.Game.Tournament ladder ??= new LadderInfo(); - ladder.Ruleset.Value = RulesetStore.GetRuleset(ladder.Ruleset.Value?.ShortName) + ladder.Ruleset.Value = RulesetStore.GetRuleset(ladder.Ruleset.Value.ShortName) ?? RulesetStore.AvailableRulesets.First(); bool addedInfo = false; diff --git a/osu.Game/Online/API/Requests/Responses/APIScore.cs b/osu.Game/Online/API/Requests/Responses/APIScore.cs index 71f277570d..d8f4ba835d 100644 --- a/osu.Game/Online/API/Requests/Responses/APIScore.cs +++ b/osu.Game/Online/API/Requests/Responses/APIScore.cs @@ -86,7 +86,7 @@ namespace osu.Game.Online.API.Requests.Responses /// public ScoreInfo CreateScoreInfo(RulesetStore rulesets, BeatmapInfo beatmap = null) { - var ruleset = rulesets.GetRuleset(RulesetID); + var ruleset = rulesets.GetRuleset(RulesetID) ?? throw new InvalidOperationException(); var rulesetInstance = ruleset.CreateInstance(); @@ -99,7 +99,7 @@ namespace osu.Game.Online.API.Requests.Responses { TotalScore = TotalScore, MaxCombo = MaxCombo, - BeatmapInfo = beatmap, + BeatmapInfo = beatmap ?? new BeatmapInfo(), User = User, Accuracy = Accuracy, OnlineID = OnlineID, diff --git a/osu.Game/Online/Rooms/MultiplayerScore.cs b/osu.Game/Online/Rooms/MultiplayerScore.cs index b90680a925..f1bb57bd9d 100644 --- a/osu.Game/Online/Rooms/MultiplayerScore.cs +++ b/osu.Game/Online/Rooms/MultiplayerScore.cs @@ -73,7 +73,7 @@ namespace osu.Game.Online.Rooms TotalScore = TotalScore, MaxCombo = MaxCombo, BeatmapInfo = beatmap, - Ruleset = rulesets.GetRuleset(playlistItem.RulesetID), + Ruleset = rulesets.GetRuleset(playlistItem.RulesetID) ?? throw new InvalidOperationException(), Statistics = Statistics, User = User, Accuracy = Accuracy, diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs index 562be0403e..998f5d158e 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.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.Diagnostics; using System.Linq; using JetBrains.Annotations; @@ -131,9 +132,14 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks Origin = Anchor.CentreRight, Direction = FillDirection.Horizontal, Spacing = new Vector2(2), - Children = Score.Mods.Select(mod => new ModIcon(rulesets.GetRuleset(Score.RulesetID).CreateInstance().CreateModFromAcronym(mod.Acronym)) + Children = Score.Mods.Select(mod => { - Scale = new Vector2(0.35f) + var ruleset = rulesets.GetRuleset(Score.RulesetID) ?? throw new InvalidOperationException(); + + return new ModIcon(ruleset.CreateInstance().CreateModFromAcronym(mod.Acronym)) + { + Scale = new Vector2(0.35f) + }; }).ToList(), } } diff --git a/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs b/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs index 7ce2ee802e..1f65752fa6 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs @@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Edit.Checks else if (texture.Width < low_width || texture.Height < low_height) yield return new IssueTemplateLowResolution(this).Create(texture.Width, texture.Height); - string storagePath = context.Beatmap.BeatmapInfo.BeatmapSet.GetPathForFile(backgroundFile); + string storagePath = context.Beatmap.BeatmapInfo.BeatmapSet?.GetPathForFile(backgroundFile); using (Stream stream = context.WorkingBeatmap.GetStream(storagePath)) { diff --git a/osu.Game/Rulesets/Edit/Checks/CheckFilePresence.cs b/osu.Game/Rulesets/Edit/Checks/CheckFilePresence.cs index 33bcac1e75..a1605a11d0 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckFilePresence.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckFilePresence.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Edit.Checks } // If the file is set, also make sure it still exists. - string storagePath = context.Beatmap.BeatmapInfo.BeatmapSet.GetPathForFile(filename); + string storagePath = context.Beatmap.BeatmapInfo.BeatmapSet?.GetPathForFile(filename); if (storagePath != null) yield break; diff --git a/osu.Game/Rulesets/Edit/Checks/CheckTooShortAudioFiles.cs b/osu.Game/Rulesets/Edit/Checks/CheckTooShortAudioFiles.cs index 381ebd4af6..6015d0a1b2 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckTooShortAudioFiles.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckTooShortAudioFiles.cs @@ -30,32 +30,35 @@ namespace osu.Game.Rulesets.Edit.Checks { var beatmapSet = context.Beatmap.BeatmapInfo.BeatmapSet; - foreach (var file in beatmapSet.Files) + if (beatmapSet != null) { - using (Stream data = context.WorkingBeatmap.GetStream(file.File.GetStoragePath())) + foreach (var file in beatmapSet.Files) { - if (data == null) - continue; - - var fileCallbacks = new FileCallbacks(new DataStreamFileProcedures(data)); - int decodeStream = Bass.CreateStream(StreamSystem.NoBuffer, BassFlags.Decode | BassFlags.Prescan, fileCallbacks.Callbacks, fileCallbacks.Handle); - - if (decodeStream == 0) + using (Stream data = context.WorkingBeatmap.GetStream(file.File.GetStoragePath())) { - // If the file is not likely to be properly parsed by Bass, we don't produce Error issues about it. - // Image files and audio files devoid of audio data both fail, for example, but neither would be issues in this check. - if (hasAudioExtension(file.Filename) && probablyHasAudioData(data)) - yield return new IssueTemplateBadFormat(this).Create(file.Filename); + if (data == null) + continue; - continue; + var fileCallbacks = new FileCallbacks(new DataStreamFileProcedures(data)); + int decodeStream = Bass.CreateStream(StreamSystem.NoBuffer, BassFlags.Decode | BassFlags.Prescan, fileCallbacks.Callbacks, fileCallbacks.Handle); + + if (decodeStream == 0) + { + // If the file is not likely to be properly parsed by Bass, we don't produce Error issues about it. + // Image files and audio files devoid of audio data both fail, for example, but neither would be issues in this check. + if (hasAudioExtension(file.Filename) && probablyHasAudioData(data)) + yield return new IssueTemplateBadFormat(this).Create(file.Filename); + + continue; + } + + long length = Bass.ChannelGetLength(decodeStream); + double ms = Bass.ChannelBytes2Seconds(decodeStream, length) * 1000; + + // Extremely short audio files do not play on some soundcards, resulting in nothing being heard in-game for some users. + if (ms > 0 && ms < ms_threshold) + yield return new IssueTemplateTooShort(this).Create(file.Filename, ms); } - - long length = Bass.ChannelGetLength(decodeStream); - double ms = Bass.ChannelBytes2Seconds(decodeStream, length) * 1000; - - // Extremely short audio files do not play on some soundcards, resulting in nothing being heard in-game for some users. - if (ms > 0 && ms < ms_threshold) - yield return new IssueTemplateTooShort(this).Create(file.Filename, ms); } } } diff --git a/osu.Game/Rulesets/Edit/Checks/CheckZeroByteFiles.cs b/osu.Game/Rulesets/Edit/Checks/CheckZeroByteFiles.cs index 324ebe6b50..75cb08002f 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckZeroByteFiles.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckZeroByteFiles.cs @@ -21,12 +21,15 @@ namespace osu.Game.Rulesets.Edit.Checks { var beatmapSet = context.Beatmap.BeatmapInfo.BeatmapSet; - foreach (var file in beatmapSet.Files) + if (beatmapSet != null) { - using (Stream data = context.WorkingBeatmap.GetStream(file.File.GetStoragePath())) + foreach (var file in beatmapSet.Files) { - if (data?.Length == 0) - yield return new IssueTemplateZeroBytes(this).Create(file.Filename); + using (Stream data = context.WorkingBeatmap.GetStream(file.File.GetStoragePath())) + { + if (data?.Length == 0) + yield return new IssueTemplateZeroBytes(this).Create(file.Filename); + } } } } diff --git a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs index 67b24d24d0..5d7565e56b 100644 --- a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs +++ b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs @@ -76,9 +76,6 @@ namespace osu.Game.Rulesets.Mods var difficulty = beatmap.Value.BeatmapInfo.BaseDifficulty; - if (difficulty == null) - return; - // generally should always be implemented, else the slider will have a zero default. if (difficultyBindable.ReadCurrentFromDifficulty == null) return; diff --git a/osu.Game/Screens/Edit/Components/Menus/DifficultyMenuItem.cs b/osu.Game/Screens/Edit/Components/Menus/DifficultyMenuItem.cs index 75dc479c25..f17fe4c3ce 100644 --- a/osu.Game/Screens/Edit/Components/Menus/DifficultyMenuItem.cs +++ b/osu.Game/Screens/Edit/Components/Menus/DifficultyMenuItem.cs @@ -13,7 +13,7 @@ namespace osu.Game.Screens.Edit.Components.Menus public BeatmapInfo BeatmapInfo { get; } public DifficultyMenuItem(BeatmapInfo beatmapInfo, bool selected, Action difficultyChangeFunc) - : base(beatmapInfo.DifficultyName ?? "(unnamed)", null) + : base(string.IsNullOrEmpty(beatmapInfo.DifficultyName) ? "(unnamed)" : beatmapInfo.DifficultyName, null) { BeatmapInfo = beatmapInfo; State.Value = selected; diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs index 9386538a78..2bdf59b21c 100644 --- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs +++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs @@ -40,7 +40,7 @@ namespace osu.Game.Screens.Edit.Compose { var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - ruleset = parent.Get>().Value.BeatmapInfo.Ruleset?.CreateInstance(); + ruleset = parent.Get>().Value.BeatmapInfo.Ruleset.CreateInstance(); composer = ruleset?.CreateHitObjectComposer(); // make the composer available to the timeline and other components in this screen. diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 383b59dc01..8c4b458534 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -599,7 +599,8 @@ namespace osu.Game.Screens.Edit if (isNewBeatmap) { // confirming exit without save means we should delete the new beatmap completely. - beatmapManager.Delete(playableBeatmap.BeatmapInfo.BeatmapSet); + if (playableBeatmap.BeatmapInfo.BeatmapSet != null) + beatmapManager.Delete(playableBeatmap.BeatmapInfo.BeatmapSet); // eagerly clear contents before restoring default beatmap to prevent value change callbacks from firing. ClearInternal(); diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 98fad09192..c9449f3259 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -102,7 +102,7 @@ namespace osu.Game.Screens.Edit if (beatmapSkin is Skin skin) BeatmapSkin = new EditorBeatmapSkin(skin); - beatmapProcessor = playableBeatmap.BeatmapInfo.Ruleset?.CreateInstance().CreateBeatmapProcessor(PlayableBeatmap); + beatmapProcessor = playableBeatmap.BeatmapInfo.Ruleset.CreateInstance().CreateBeatmapProcessor(PlayableBeatmap); foreach (var obj in HitObjects) trackStartTime(obj); diff --git a/osu.Game/Screens/Edit/Setup/SetupScreen.cs b/osu.Game/Screens/Edit/Setup/SetupScreen.cs index 8d726f7752..231d977aab 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreen.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreen.cs @@ -33,7 +33,7 @@ namespace osu.Game.Screens.Edit.Setup new DesignSection(), }; - var rulesetSpecificSection = beatmap.BeatmapInfo.Ruleset?.CreateInstance().CreateEditorSetupSection(); + var rulesetSpecificSection = beatmap.BeatmapInfo.Ruleset.CreateInstance().CreateEditorSetupSection(); if (rulesetSpecificSection != null) sectionsEnumerable.Add(rulesetSpecificSection); diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index 827e128467..567a2307dd 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -13,7 +12,6 @@ using osu.Framework.Input.Events; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; using osu.Game.Online.Placeholders; -using osu.Game.Rulesets.Mods; using osu.Game.Scoring; using osuTK; @@ -76,7 +74,7 @@ namespace osu.Game.Screens.Ranking.Statistics if (newScore == null) return; - if (newScore.HitEvents == null || newScore.HitEvents.Count == 0) + if (newScore.HitEvents.Count == 0) { content.Add(new FillFlowContainer { @@ -104,7 +102,7 @@ namespace osu.Game.Screens.Ranking.Statistics // Todo: The placement of this is temporary. Eventually we'll both generate the playable beatmap _and_ run through it in a background task to generate the hit events. Task.Run(() => { - playableBeatmap = beatmapManager.GetWorkingBeatmap(newScore.BeatmapInfo).GetPlayableBeatmap(newScore.Ruleset, newScore.Mods ?? Array.Empty()); + playableBeatmap = beatmapManager.GetWorkingBeatmap(newScore.BeatmapInfo).GetPlayableBeatmap(newScore.Ruleset, newScore.Mods); }, loadCancellation.Token).ContinueWith(t => Schedule(() => { var rows = new FillFlowContainer diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index b37877b35f..ea531e89c8 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -324,7 +324,7 @@ namespace osu.Game.Screens.Select }); // no difficulty means it can't have a status to show - if (beatmapInfo.DifficultyName == null) + if (string.IsNullOrEmpty(beatmapInfo.DifficultyName)) StatusPill.Hide(); addInfoLabels(); diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index e54b25873b..b53d64260a 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -53,7 +53,7 @@ namespace osu.Game.Screens.Select if (!maximumStars.IsDefault) criteria.UserStarDifficulty.Max = maximumStars.Value; - criteria.RulesetCriteria = ruleset.Value.CreateInstance()?.CreateRulesetFilterCriteria(); + criteria.RulesetCriteria = ruleset.Value.CreateInstance().CreateRulesetFilterCriteria(); FilterQueryParser.ApplyQueries(criteria, query); return criteria; diff --git a/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs b/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs index 4e955975a0..10cb210f4d 100644 --- a/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs +++ b/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs @@ -98,6 +98,8 @@ namespace osu.Game.Tests.Beatmaps userSkinInfo.Files.Clear(); userSkinInfo.Files.Add(new RealmNamedFileUsage(new RealmFile { Hash = userFile }, userFile)); + Debug.Assert(beatmapInfo.BeatmapSet != null); + beatmapInfo.BeatmapSet.Files.Clear(); beatmapInfo.BeatmapSet.Files.Add(new RealmNamedFileUsage(new RealmFile { Hash = beatmapFile }, beatmapFile)); From b5f670cc5b2649b4b0dbecd2098588ca092444c2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 12 Jan 2022 23:17:35 +0900 Subject: [PATCH 328/996] Add far too many fixes for ruleset non-nullable requirements --- .../Editor/CatchSelectionBlueprintTestScene.cs | 8 +++++++- .../Editor/TestSceneManiaBeatSnapGrid.cs | 8 +++++++- .../Editor/TestSceneManiaComposeScreen.cs | 4 ++-- .../Editor/TestSceneManiaHitObjectComposer.cs | 4 ++-- .../Editor/TestSceneOsuDistanceSnapGrid.cs | 8 +++++++- .../Editor/TestSceneTaikoHitObjectComposer.cs | 4 ++-- osu.Game.Tests/Editing/EditorChangeHandlerTest.cs | 11 +++++++++-- .../TestSceneHitObjectComposerDistanceSnapping.cs | 8 +++++++- osu.Game.Tests/Scores/IO/ImportScoreTest.cs | 11 +++++++++++ .../Visual/Editing/TestSceneDesignSection.cs | 9 ++++++++- .../Visual/Editing/TestSceneDistanceSnapGrid.cs | 9 ++++++++- .../Visual/Editing/TestSceneHitObjectComposer.cs | 9 ++++++++- .../Visual/Editing/TestSceneMetadataSection.cs | 9 ++++++++- osu.Game.Tests/Visual/Editing/TestSceneSetupScreen.cs | 8 +++++++- osu.Game.Tournament/TournamentGameBase.cs | 5 +++-- 15 files changed, 96 insertions(+), 19 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/CatchSelectionBlueprintTestScene.cs b/osu.Game.Rulesets.Catch.Tests/Editor/CatchSelectionBlueprintTestScene.cs index d4c2c0f0af..e345e03c96 100644 --- a/osu.Game.Rulesets.Catch.Tests/Editor/CatchSelectionBlueprintTestScene.cs +++ b/osu.Game.Rulesets.Catch.Tests/Editor/CatchSelectionBlueprintTestScene.cs @@ -29,7 +29,13 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor protected CatchSelectionBlueprintTestScene() { - EditorBeatmap = new EditorBeatmap(new CatchBeatmap()) { Difficulty = { CircleSize = 0 } }; + EditorBeatmap = new EditorBeatmap(new CatchBeatmap + { + BeatmapInfo = + { + Ruleset = new CatchRuleset().RulesetInfo, + } + }) { Difficulty = { CircleSize = 0 } }; EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 100 diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs index 5ccb191a9b..50be13c4e0 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs @@ -29,7 +29,13 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor private ScrollingTestContainer.TestScrollingInfo scrollingInfo = new ScrollingTestContainer.TestScrollingInfo(); [Cached(typeof(EditorBeatmap))] - private EditorBeatmap editorBeatmap = new EditorBeatmap(new ManiaBeatmap(new StageDefinition())); + private EditorBeatmap editorBeatmap = new EditorBeatmap(new ManiaBeatmap(new StageDefinition()) + { + BeatmapInfo = + { + Ruleset = new ManiaRuleset().RulesetInfo + } + }); private readonly ManiaBeatSnapGrid beatSnapGrid; diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs index a30e09cd29..5dd7c23ab6 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs @@ -31,10 +31,10 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor { AddStep("setup compose screen", () => { - var editorBeatmap = new EditorBeatmap(new ManiaBeatmap(new StageDefinition { Columns = 4 })) + var editorBeatmap = new EditorBeatmap(new ManiaBeatmap(new StageDefinition { Columns = 4 }) { BeatmapInfo = { Ruleset = new ManiaRuleset().RulesetInfo }, - }; + }); Beatmap.Value = CreateWorkingBeatmap(editorBeatmap.PlayableBeatmap); diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs index 01d80881fa..9788dfe844 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs @@ -203,10 +203,10 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor { InternalChildren = new Drawable[] { - EditorBeatmap = new EditorBeatmap(new ManiaBeatmap(new StageDefinition { Columns = 4 })) + EditorBeatmap = new EditorBeatmap(new ManiaBeatmap(new StageDefinition { Columns = 4 }) { BeatmapInfo = { Ruleset = new ManiaRuleset().RulesetInfo } - }, + }), Composer = new ManiaHitObjectComposer(new ManiaRuleset()) }; diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs index ef43c3a696..c770e2d96f 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs @@ -40,7 +40,13 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor public TestSceneOsuDistanceSnapGrid() { - editorBeatmap = new EditorBeatmap(new OsuBeatmap()); + editorBeatmap = new EditorBeatmap(new OsuBeatmap + { + BeatmapInfo = + { + Ruleset = new OsuRuleset().RulesetInfo + } + }); } [SetUp] diff --git a/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoHitObjectComposer.cs b/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoHitObjectComposer.cs index 626537053a..55eb2fa66b 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoHitObjectComposer.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoHitObjectComposer.cs @@ -40,10 +40,10 @@ namespace osu.Game.Rulesets.Taiko.Tests.Editor { InternalChildren = new Drawable[] { - EditorBeatmap = new EditorBeatmap(new TaikoBeatmap()) + EditorBeatmap = new EditorBeatmap(new TaikoBeatmap { BeatmapInfo = { Ruleset = new TaikoRuleset().RulesetInfo } - }, + }), new TaikoHitObjectComposer(new TaikoRuleset()) }; diff --git a/osu.Game.Tests/Editing/EditorChangeHandlerTest.cs b/osu.Game.Tests/Editing/EditorChangeHandlerTest.cs index 481cb3230e..2d61948a2a 100644 --- a/osu.Game.Tests/Editing/EditorChangeHandlerTest.cs +++ b/osu.Game.Tests/Editing/EditorChangeHandlerTest.cs @@ -2,7 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; -using osu.Game.Beatmaps; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit; @@ -158,7 +159,13 @@ namespace osu.Game.Tests.Editing private (EditorChangeHandler, EditorBeatmap) createChangeHandler() { - var beatmap = new EditorBeatmap(new Beatmap()); + var beatmap = new EditorBeatmap(new OsuBeatmap + { + BeatmapInfo = + { + Ruleset = new OsuRuleset().RulesetInfo, + }, + }); var changeHandler = new EditorChangeHandler(beatmap); diff --git a/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs index 8eb9452736..43f22e4e90 100644 --- a/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs +++ b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs @@ -35,7 +35,13 @@ namespace osu.Game.Tests.Editing RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - editorBeatmap = new EditorBeatmap(new OsuBeatmap()), + editorBeatmap = new EditorBeatmap(new OsuBeatmap + { + BeatmapInfo = + { + Ruleset = new OsuRuleset().RulesetInfo, + }, + }), Content = new Container { RelativeSizeAxes = Axes.Both, diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs index 2b6c9f76fc..0e6d560167 100644 --- a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs +++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs @@ -9,9 +9,11 @@ using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Platform; +using osu.Game.Beatmaps; using osu.Game.IO.Archives; using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; @@ -39,6 +41,8 @@ namespace osu.Game.Tests.Scores.IO User = new APIUser { Username = "Test user" }, Date = DateTimeOffset.Now, OnlineID = 12345, + Ruleset = new OsuRuleset().RulesetInfo, + BeatmapInfo = new BeatmapInfo() }; var imported = await LoadScoreIntoOsu(osu, toImport); @@ -70,6 +74,8 @@ namespace osu.Game.Tests.Scores.IO var toImport = new ScoreInfo { User = new APIUser { Username = "Test user" }, + BeatmapInfo = new BeatmapInfo(), + Ruleset = new OsuRuleset().RulesetInfo, Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() }, }; @@ -97,6 +103,8 @@ namespace osu.Game.Tests.Scores.IO var toImport = new ScoreInfo { User = new APIUser { Username = "Test user" }, + BeatmapInfo = new BeatmapInfo(), + Ruleset = new OsuRuleset().RulesetInfo, Statistics = new Dictionary { { HitResult.Perfect, 100 }, @@ -128,6 +136,8 @@ namespace osu.Game.Tests.Scores.IO await LoadScoreIntoOsu(osu, new ScoreInfo { User = new APIUser { Username = "Test user" }, + BeatmapInfo = new BeatmapInfo(), + Ruleset = new OsuRuleset().RulesetInfo, OnlineID = 2 }, new TestArchiveReader()); @@ -137,6 +147,7 @@ namespace osu.Game.Tests.Scores.IO Assert.That(scoreManager.IsAvailableLocally(new ScoreInfo { User = new APIUser { Username = "Test user" }, + BeatmapInfo = new BeatmapInfo(), OnlineID = 2 })); } diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDesignSection.cs b/osu.Game.Tests/Visual/Editing/TestSceneDesignSection.cs index 00f2979691..10917df075 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneDesignSection.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneDesignSection.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics.UserInterface; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Rulesets.Osu; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Setup; using osuTK.Input; @@ -25,7 +26,13 @@ namespace osu.Game.Tests.Visual.Editing [SetUpSteps] public void SetUp() { - AddStep("create blank beatmap", () => editorBeatmap = new EditorBeatmap(new Beatmap())); + AddStep("create blank beatmap", () => editorBeatmap = new EditorBeatmap(new Beatmap + { + BeatmapInfo = + { + Ruleset = new OsuRuleset().RulesetInfo + } + })); AddStep("create section", () => Child = new DependencyProvidingContainer { RelativeSizeAxes = Axes.Both, diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs b/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs index d1efd22d6f..0d9e06e471 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Compose.Components; @@ -29,7 +30,13 @@ namespace osu.Game.Tests.Visual.Editing public TestSceneDistanceSnapGrid() { - editorBeatmap = new EditorBeatmap(new OsuBeatmap()); + editorBeatmap = new EditorBeatmap(new OsuBeatmap + { + BeatmapInfo = + { + Ruleset = new OsuRuleset().RulesetInfo + } + }); editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = beat_length }); } diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs index eee0d6672c..145d738f60 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs @@ -39,9 +39,16 @@ namespace osu.Game.Tests.Visual.Editing { Beatmap.Value = CreateWorkingBeatmap(new Beatmap { + BeatmapInfo = + { + Ruleset = new OsuRuleset().RulesetInfo + }, HitObjects = new List { - new HitCircle { Position = new Vector2(256, 192), Scale = 0.5f }, + new HitCircle + { + Position = new Vector2(256, 192), Scale = 0.5f + }, new HitCircle { Position = new Vector2(344, 148), Scale = 0.5f }, new Slider { diff --git a/osu.Game.Tests/Visual/Editing/TestSceneMetadataSection.cs b/osu.Game.Tests/Visual/Editing/TestSceneMetadataSection.cs index 4621436cc6..4ecfb0975b 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneMetadataSection.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneMetadataSection.cs @@ -5,6 +5,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Rulesets.Osu; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Setup; @@ -13,7 +14,13 @@ namespace osu.Game.Tests.Visual.Editing public class TestSceneMetadataSection : OsuTestScene { [Cached] - private EditorBeatmap editorBeatmap = new EditorBeatmap(new Beatmap()); + private EditorBeatmap editorBeatmap = new EditorBeatmap(new Beatmap + { + BeatmapInfo = + { + Ruleset = new OsuRuleset().RulesetInfo + }, + }); private TestMetadataSection metadataSection; diff --git a/osu.Game.Tests/Visual/Editing/TestSceneSetupScreen.cs b/osu.Game.Tests/Visual/Editing/TestSceneSetupScreen.cs index 03e78ce854..2f6cf46b21 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneSetupScreen.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneSetupScreen.cs @@ -29,7 +29,13 @@ namespace osu.Game.Tests.Visual.Editing public TestSceneSetupScreen() { - editorBeatmap = new EditorBeatmap(new OsuBeatmap()); + editorBeatmap = new EditorBeatmap(new OsuBeatmap + { + BeatmapInfo = + { + Ruleset = new OsuRuleset().RulesetInfo + } + }); } [Test] diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index 75b929273c..f318c8bd85 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -81,8 +81,9 @@ namespace osu.Game.Tournament ladder ??= new LadderInfo(); - ladder.Ruleset.Value = RulesetStore.GetRuleset(ladder.Ruleset.Value.ShortName) - ?? RulesetStore.AvailableRulesets.First(); + ladder.Ruleset.Value = ladder.Ruleset.Value != null + ? RulesetStore.GetRuleset(ladder.Ruleset.Value.ShortName) + : RulesetStore.AvailableRulesets.First(); bool addedInfo = false; From b2d09b7b10a577602c49f84f47eff3a5a5912a17 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 12 Jan 2022 23:42:12 +0900 Subject: [PATCH 329/996] Fix further warnings --- osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs | 2 +- .../Visual/Playlists/TestScenePlaylistsRoomCreation.cs | 2 ++ osu.Game/Screens/Select/BeatmapClearScoresDialog.cs | 2 +- osu.Game/Skinning/LegacyBeatmapSkin.cs | 2 +- osu.Game/Storyboards/Storyboard.cs | 2 +- 5 files changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 677aaf6f78..6ec14e6351 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -117,7 +117,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(string.Empty, metadata.Source); Assert.AreEqual("MBC7 Unisphere 地球ヤバイEP Chikyu Yabai", metadata.Tags); Assert.AreEqual(557821, beatmapInfo.OnlineID); - Assert.AreEqual(241526, beatmapInfo.BeatmapSet.OnlineID); + Assert.AreEqual(241526, beatmapInfo.BeatmapSet?.OnlineID); } } diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs index eacc3d5e31..2180f0f717 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs @@ -145,6 +145,8 @@ namespace osu.Game.Tests.Visual.Playlists modifiedBeatmap.HitObjects.Clear(); modifiedBeatmap.HitObjects.Add(new HitCircle { StartTime = 5000 }); + Debug.Assert(modifiedBeatmap.BeatmapInfo.BeatmapSet != null); + manager.Import(modifiedBeatmap.BeatmapInfo.BeatmapSet).WaitSafely(); }); diff --git a/osu.Game/Screens/Select/BeatmapClearScoresDialog.cs b/osu.Game/Screens/Select/BeatmapClearScoresDialog.cs index afae858b46..774d3b4b28 100644 --- a/osu.Game/Screens/Select/BeatmapClearScoresDialog.cs +++ b/osu.Game/Screens/Select/BeatmapClearScoresDialog.cs @@ -18,7 +18,7 @@ namespace osu.Game.Screens.Select public BeatmapClearScoresDialog(BeatmapInfo beatmapInfo, Action onCompletion) { - BodyText = $@"{beatmapInfo.Metadata?.Artist} - {beatmapInfo.Metadata?.Title}"; + BodyText = beatmapInfo.GetDisplayTitle(); Icon = FontAwesome.Solid.Eraser; HeaderText = @"Clearing all local scores. Are you sure?"; Buttons = new PopupDialogButton[] diff --git a/osu.Game/Skinning/LegacyBeatmapSkin.cs b/osu.Game/Skinning/LegacyBeatmapSkin.cs index d44d3dce49..f80a980351 100644 --- a/osu.Game/Skinning/LegacyBeatmapSkin.cs +++ b/osu.Game/Skinning/LegacyBeatmapSkin.cs @@ -77,6 +77,6 @@ namespace osu.Game.Skinning } private static SkinInfo createSkinInfo(BeatmapInfo beatmapInfo) => - new SkinInfo { Name = beatmapInfo.ToString(), Creator = beatmapInfo.Metadata?.Author.Username ?? string.Empty }; + new SkinInfo { Name = beatmapInfo.ToString(), Creator = beatmapInfo.Metadata.Author.Username ?? string.Empty }; } } diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs index ff496aea36..b86deeab89 100644 --- a/osu.Game/Storyboards/Storyboard.cs +++ b/osu.Game/Storyboards/Storyboard.cs @@ -78,7 +78,7 @@ namespace osu.Game.Storyboards { get { - string backgroundPath = BeatmapInfo.BeatmapSet?.Metadata?.BackgroundFile; + string backgroundPath = BeatmapInfo.BeatmapSet?.Metadata.BackgroundFile; if (string.IsNullOrEmpty(backgroundPath)) return false; From 4c79145c11fc54f28ec9ef23f12520791f44df43 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Jan 2022 00:28:16 +0900 Subject: [PATCH 330/996] Fix potential mod nullref in `APIUserScoreAggregate`'s `CreateScoreInfo` implementation --- .../Online/API/Requests/Responses/APIUserScoreAggregate.cs | 5 ++++- osu.Game/Scoring/ScoreInfo.cs | 4 +--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/API/Requests/Responses/APIUserScoreAggregate.cs b/osu.Game/Online/API/Requests/Responses/APIUserScoreAggregate.cs index 9a7f0832a6..a298a8625a 100644 --- a/osu.Game/Online/API/Requests/Responses/APIUserScoreAggregate.cs +++ b/osu.Game/Online/API/Requests/Responses/APIUserScoreAggregate.cs @@ -1,7 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using Newtonsoft.Json; +using osu.Game.Rulesets.Mods; using osu.Game.Scoring; namespace osu.Game.Online.API.Requests.Responses @@ -42,7 +44,8 @@ namespace osu.Game.Online.API.Requests.Responses PP = PP, TotalScore = TotalScore, User = User, - Position = Position + Position = Position, + Mods = Array.Empty() }; } } diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index ffec54aca7..45e5b8f1a7 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -186,14 +186,12 @@ namespace osu.Game.Scoring { get { - var rulesetInstance = Ruleset.CreateInstance(); - Mod[] scoreMods = Array.Empty(); if (mods != null) scoreMods = mods; else if (localAPIMods != null) - scoreMods = APIMods.Select(m => m.ToMod(rulesetInstance)).ToArray(); + scoreMods = APIMods.Select(m => m.ToMod(Ruleset.CreateInstance())).ToArray(); return scoreMods; } From 2212bea17f46a36e21cb43a45978fcdde522636c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Jan 2022 01:34:29 +0900 Subject: [PATCH 331/996] Fix test failures due to double `BeginPlaying` calls --- .../Visual/Background/TestSceneUserDimBackgrounds.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs index b1f642b909..5b2cf877ba 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs @@ -36,7 +36,7 @@ using osuTK.Graphics; namespace osu.Game.Tests.Visual.Background { [TestFixture] - public class TestSceneUserDimBackgrounds : OsuManualInputManagerTestScene + public class TestSceneUserDimBackgrounds : ScreenTestScene { private DummySongSelect songSelect; private TestPlayerLoader playerLoader; @@ -56,14 +56,12 @@ namespace osu.Game.Tests.Visual.Background Beatmap.SetDefault(); } - [SetUp] - public virtual void SetUp() => Schedule(() => + public override void SetUpSteps() { - var stack = new OsuScreenStack { RelativeSizeAxes = Axes.Both }; - Child = stack; + base.SetUpSteps(); - stack.Push(songSelect = new DummySongSelect()); - }); + AddStep("push song select", () => Stack.Push(songSelect = new DummySongSelect())); + } /// /// User settings should always be ignored on song select screen. From afce976f086bc8aabe3b4d5f4ee6bcf63c91c5f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 12 Jan 2022 19:24:59 +0100 Subject: [PATCH 332/996] Fix oversubscription to `StartTimeBindable.ValueChanged` --- osu.Game/Rulesets/Objects/HitObject.cs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index 7d08261035..c590cc302f 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -136,10 +136,19 @@ namespace osu.Game.Rulesets.Objects foreach (var h in nestedHitObjects) h.ApplyDefaults(controlPointInfo, difficulty, cancellationToken); - // importantly, this callback is only registered after default application - // to ensure that the read of `this.GetEndTime()` within doesn't return an invalid value + // `ApplyDefaults()` may be called multiple times on a single hitobject. + // to prevent subscribing to `StartTimeBindable.ValueChanged` multiple times with the same callback, + // remove the previous subscription (if present) before (re-)registering. + StartTimeBindable.ValueChanged -= onStartTimeChanged; + + // this callback must be (re-)registered after default application + // to ensure that the read of `this.GetEndTime()` within `onStartTimeChanged` doesn't return an invalid value // if `StartTimeBindable` is changed prior to default application. - StartTimeBindable.ValueChanged += time => + StartTimeBindable.ValueChanged += onStartTimeChanged; + + DefaultsApplied?.Invoke(this); + + void onStartTimeChanged(ValueChangedEvent time) { double offset = time.NewValue - time.OldValue; @@ -148,9 +157,7 @@ namespace osu.Game.Rulesets.Objects DifficultyControlPoint.Time = time.NewValue; SampleControlPoint.Time = this.GetEndTime() + control_point_leniency; - }; - - DefaultsApplied?.Invoke(this); + } } protected virtual void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty) From 672c1d36dc29a7580c6fb8028ac95d1a120de5b4 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 13 Jan 2022 05:52:04 +0900 Subject: [PATCH 333/996] Fix intermitten screen navigation tests --- .../Visual/Navigation/TestSceneScreenNavigation.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index d094d8b688..60aabf5639 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -251,8 +251,9 @@ namespace osu.Game.Tests.Visual.Navigation [Test] public void TestModSelectInput() { - TestPlaySongSelect songSelect = null; + AddUntilStep("Wait for toolbar to load", () => Game.Toolbar.IsLoaded); + TestPlaySongSelect songSelect = null; PushAndConfirm(() => songSelect = new TestPlaySongSelect()); AddStep("Show mods overlay", () => songSelect.ModSelectOverlay.Show()); @@ -272,8 +273,9 @@ namespace osu.Game.Tests.Visual.Navigation [Test] public void TestBeatmapOptionsInput() { - TestPlaySongSelect songSelect = null; + AddUntilStep("Wait for toolbar to load", () => Game.Toolbar.IsLoaded); + TestPlaySongSelect songSelect = null; PushAndConfirm(() => songSelect = new TestPlaySongSelect()); AddStep("Show options overlay", () => songSelect.BeatmapOptionsOverlay.Show()); @@ -293,6 +295,8 @@ namespace osu.Game.Tests.Visual.Navigation [Test] public void TestSettingsViaHotkeyFromMainMenu() { + AddUntilStep("Wait for toolbar to load", () => Game.Toolbar.IsLoaded); + AddAssert("toolbar not displayed", () => Game.Toolbar.State.Value == Visibility.Hidden); AddStep("press settings hotkey", () => @@ -308,10 +312,11 @@ namespace osu.Game.Tests.Visual.Navigation [Test] public void TestToolbarHiddenByUser() { - AddStep("Enter menu", () => InputManager.Key(Key.Enter)); - AddUntilStep("Wait for toolbar to load", () => Game.Toolbar.IsLoaded); + AddStep("Enter menu", () => InputManager.Key(Key.Enter)); + AddUntilStep("Toolbar is visible", () => Game.Toolbar.State.Value == Visibility.Visible); + AddStep("Hide toolbar", () => { InputManager.PressKey(Key.ControlLeft); From 5185f6010e177c69b62f3a20de4bc73582c803b3 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Wed, 12 Jan 2022 15:46:20 -0800 Subject: [PATCH 334/996] Fix missed identifier typos --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 4 ++-- osu.sln.DotSettings | 7 ++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 4ab513bf19..5531bf8b5a 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -533,9 +533,9 @@ namespace osu.Game.Rulesets.Objects.Drawables protected double CalculateSamplePlaybackBalance(double position) { float balanceAdjustAmount = positionalHitsoundsLevel.Value * 2; - double returnedvalue = balanceAdjustAmount * (position - 0.5f); + double returnedValue = balanceAdjustAmount * (position - 0.5f); - return returnedvalue; + return returnedValue; } /// diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index a9b2a652f9..fcb35907a3 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -946,6 +946,7 @@ private void load() True True True + True True True True @@ -957,6 +958,7 @@ private void load() True True True + True True True True @@ -986,6 +988,7 @@ private void load() True True True + True True True True @@ -993,8 +996,10 @@ private void load() True True True + True True True True True - True + True + True From 66773f6d7d26436ac4b0c751f70c17f09cd1fb95 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Wed, 12 Jan 2022 15:49:28 -0800 Subject: [PATCH 335/996] Use comments to disable identifier typos instead --- osu.Desktop/Windows/WindowsKey.cs | 5 ++--- .../Online/API/Requests/GetUserRecentActivitiesRequest.cs | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Desktop/Windows/WindowsKey.cs b/osu.Desktop/Windows/WindowsKey.cs index ec4d3f5d74..fdca2028d3 100644 --- a/osu.Desktop/Windows/WindowsKey.cs +++ b/osu.Desktop/Windows/WindowsKey.cs @@ -2,12 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; +// ReSharper disable IdentifierTypo + namespace osu.Desktop.Windows { - [SuppressMessage("ReSharper", "IdentifierTypo")] internal class WindowsKey { private delegate int LowLevelKeyboardProcDelegate(int nCode, int wParam, ref KdDllHookStruct lParam); @@ -16,7 +16,6 @@ namespace osu.Desktop.Windows private const int wh_keyboard_ll = 13; private const int wm_keydown = 256; - private const int wm_syskeyup = 261; //Resharper disable once NotAccessedField.Local diff --git a/osu.Game/Online/API/Requests/GetUserRecentActivitiesRequest.cs b/osu.Game/Online/API/Requests/GetUserRecentActivitiesRequest.cs index 62b27b31b4..f2fa51bde7 100644 --- a/osu.Game/Online/API/Requests/GetUserRecentActivitiesRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserRecentActivitiesRequest.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Online.API.Requests @@ -20,10 +19,11 @@ namespace osu.Game.Online.API.Requests protected override string Target => $"users/{userId}/recent_activity"; } - [SuppressMessage("ReSharper", "IdentifierTypo")] public enum RecentActivityType { Achievement, + + // ReSharper disable once IdentifierTypo BeatmapPlaycount, BeatmapsetApprove, BeatmapsetDelete, From a18b2836262d4fb2c925ccc9d6d726f78fc3af44 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Wed, 12 Jan 2022 15:52:33 -0800 Subject: [PATCH 336/996] Revert identifier typo bump --- osu.sln.DotSettings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index fcb35907a3..c29d812fc5 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -91,7 +91,7 @@ WARNING HINT DO_NOT_SHOW - WARNING + HINT HINT ERROR WARNING From e1f5acd689c49fc2c5a100e2559a1c2d1b228fd2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Jan 2022 12:37:57 +0900 Subject: [PATCH 337/996] Remove names from user dictionary --- .../Visual/Online/TestSceneUserPanel.cs | 33 +++++++++---------- osu.sln.DotSettings | 2 -- 2 files changed, 15 insertions(+), 20 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs index 52d5eb2c65..6c3678b0d2 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs @@ -20,8 +20,8 @@ namespace osu.Game.Tests.Visual.Online private readonly Bindable activity = new Bindable(); private readonly Bindable status = new Bindable(); - private UserGridPanel peppy; - private TestUserListPanel evast; + private UserGridPanel boundPanel1; + private TestUserListPanel boundPanel2; [Resolved] private IRulesetStore rulesetStore { get; set; } @@ -29,8 +29,6 @@ namespace osu.Game.Tests.Visual.Online [SetUp] public void SetUp() => Schedule(() => { - UserGridPanel flyte; - activity.Value = null; status.Value = null; @@ -56,14 +54,15 @@ namespace osu.Game.Tests.Visual.Online Colour = "99EB47", CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", }), - flyte = new UserGridPanel(new APIUser + new UserGridPanel(new APIUser { Username = @"flyte", Id = 3103765, Country = new Country { FlagName = @"JP" }, - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg" + CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg", + Status = { Value = new UserStatusOnline() } }) { Width = 300 }, - peppy = new UserGridPanel(new APIUser + boundPanel1 = new UserGridPanel(new APIUser { Username = @"peppy", Id = 2, @@ -72,7 +71,7 @@ namespace osu.Game.Tests.Visual.Online IsSupporter = true, SupportLevel = 3, }) { Width = 300 }, - evast = new TestUserListPanel(new APIUser + boundPanel2 = new TestUserListPanel(new APIUser { Username = @"Evast", Id = 8195163, @@ -84,13 +83,11 @@ namespace osu.Game.Tests.Visual.Online }, }; - flyte.Status.Value = new UserStatusOnline(); + boundPanel1.Status.BindTo(status); + boundPanel1.Activity.BindTo(activity); - peppy.Status.BindTo(status); - peppy.Activity.BindTo(activity); - - evast.Status.BindTo(status); - evast.Activity.BindTo(activity); + boundPanel2.Status.BindTo(status); + boundPanel2.Activity.BindTo(activity); }); [Test] @@ -121,14 +118,14 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestUserActivityChange() { - AddAssert("visit message is visible", () => evast.LastVisitMessage.IsPresent); + AddAssert("visit message is visible", () => boundPanel2.LastVisitMessage.IsPresent); AddStep("set online status", () => status.Value = new UserStatusOnline()); - AddAssert("visit message is not visible", () => !evast.LastVisitMessage.IsPresent); + AddAssert("visit message is not visible", () => !boundPanel2.LastVisitMessage.IsPresent); AddStep("set choosing activity", () => activity.Value = new UserActivity.ChoosingBeatmap()); AddStep("set offline status", () => status.Value = new UserStatusOffline()); - AddAssert("visit message is visible", () => evast.LastVisitMessage.IsPresent); + AddAssert("visit message is visible", () => boundPanel2.LastVisitMessage.IsPresent); AddStep("set online status", () => status.Value = new UserStatusOnline()); - AddAssert("visit message is not visible", () => !evast.LastVisitMessage.IsPresent); + AddAssert("visit message is not visible", () => !boundPanel2.LastVisitMessage.IsPresent); } private UserActivity soloGameStatusForRuleset(int rulesetId) => new UserActivity.InSoloGame(null, rulesetStore.GetRuleset(rulesetId)); diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index c29d812fc5..2ff0f4d30b 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -939,10 +939,8 @@ private void load() True True True - True True True - True True True True From ebe8ba3c3c16289e5d824278fda1a7f110fda2b9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Jan 2022 12:47:11 +0900 Subject: [PATCH 338/996] Fix unintended change to dotsetting --- osu.sln.DotSettings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index 0da109ae7c..44d75f265c 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -789,7 +789,7 @@ See the LICENCE file in the repository root for full licence text. <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - True + True True True True From 1a29098f3b99a804c6c51cc8067455dfaf3c0959 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Jan 2022 12:48:45 +0900 Subject: [PATCH 339/996] Change default value and add comment explaining why skins are never "locally available" --- osu.Game/Skinning/SkinModelManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/SkinModelManager.cs b/osu.Game/Skinning/SkinModelManager.cs index 2aa21c0629..dbd209e984 100644 --- a/osu.Game/Skinning/SkinModelManager.cs +++ b/osu.Game/Skinning/SkinModelManager.cs @@ -263,6 +263,6 @@ namespace osu.Game.Skinning }); } - public override bool IsAvailableLocally(SkinInfo model) => false; + public override bool IsAvailableLocally(SkinInfo model) => true; // skins do not have online download support yet. } } From 65dd80e6f63cb7523c687030d2f65b2084011c34 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Jan 2022 12:59:16 +0900 Subject: [PATCH 340/996] Sanitise mods / statistics cache logic in `ScoreInfo` --- osu.Game/Scoring/ScoreInfo.cs | 101 +++++++++++++++++----------------- 1 file changed, 52 insertions(+), 49 deletions(-) diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 45e5b8f1a7..a10039bc72 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -46,6 +46,12 @@ namespace osu.Game.Scoring [MapTo("User")] public RealmUser RealmUser { get; set; } = new RealmUser(); + [MapTo("Mods")] + public string ModsJson { get; set; } = string.Empty; + + [MapTo("Statistics")] + public string StatisticsJson { get; set; } = string.Empty; + public ScoreInfo(BeatmapInfo beatmap, RulesetInfo ruleset, RealmUser realmUser) { Ruleset = ruleset; @@ -98,27 +104,6 @@ namespace osu.Game.Scoring public RulesetInfo Ruleset { get; set; } = null!; - private Dictionary? statistics; - - [Ignored] - public Dictionary Statistics - { - get - { - if (statistics != null) - return statistics; - - if (!string.IsNullOrEmpty(StatisticsJson)) - statistics = JsonConvert.DeserializeObject>(StatisticsJson); - - return statistics ??= new Dictionary(); - } - set => statistics = value; - } - - [MapTo("Statistics")] - public string StatisticsJson { get; set; } = null!; - public ScoreRank Rank { get => (ScoreRank)RankInt; @@ -135,10 +120,6 @@ namespace osu.Game.Scoring #region Properties required to make things work with existing usages - private APIMod[]? localAPIMods; - - private Mod[]? mods; - public Guid BeatmapInfoID => BeatmapInfo.ID; public int UserID => RealmUser.OnlineID; @@ -177,61 +158,83 @@ namespace osu.Game.Scoring [Ignored] public bool IsLegacyScore => Mods.OfType().Any(); - // Used for database serialisation/deserialisation. - [MapTo("Mods")] - public string ModsJson { get; set; } = string.Empty; + private Dictionary? statistics; + + [Ignored] + public Dictionary Statistics + { + get + { + if (statistics != null) + return statistics; + + if (!string.IsNullOrEmpty(StatisticsJson)) + statistics = JsonConvert.DeserializeObject>(StatisticsJson); + + return statistics ??= new Dictionary(); + } + set => statistics = value; + } + + private Mod[]? mods; [Ignored] public Mod[] Mods { get { - Mod[] scoreMods = Array.Empty(); - if (mods != null) - scoreMods = mods; - else if (localAPIMods != null) - scoreMods = APIMods.Select(m => m.ToMod(Ruleset.CreateInstance())).ToArray(); + return mods; - return scoreMods; + if (apiMods != null) + return APIMods.Select(m => m.ToMod(Ruleset.CreateInstance())).ToArray(); + + return Array.Empty(); } set { - localAPIMods = null; + apiMods = null; mods = value; - ModsJson = JsonConvert.SerializeObject(APIMods); + + updateModsJson(); } } + private APIMod[]? apiMods; + // Used for API serialisation/deserialisation. [Ignored] public APIMod[] APIMods { get { - if (localAPIMods == null) - { - // prioritise reading from realm backing - if (!string.IsNullOrEmpty(ModsJson)) - localAPIMods = JsonConvert.DeserializeObject(ModsJson); + if (apiMods != null) return apiMods; - // then check mods set via Mods property. - if (mods != null) - localAPIMods = mods.Select(m => new APIMod(m)).ToArray(); - } + // prioritise reading from realm backing + if (!string.IsNullOrEmpty(ModsJson)) + apiMods = JsonConvert.DeserializeObject(ModsJson); - return localAPIMods ?? Array.Empty(); + // then check mods set via Mods property. + if (mods != null) + apiMods = mods.Select(m => new APIMod(m)).ToArray(); + + return apiMods ?? Array.Empty(); } set { - localAPIMods = value; + apiMods = value; + mods = null; // We potentially can't update this yet due to Ruleset being late-bound, so instead update on read as necessary. - mods = null; - ModsJson = JsonConvert.SerializeObject(APIMods); + updateModsJson(); } } + private void updateModsJson() + { + ModsJson = JsonConvert.SerializeObject(APIMods); + } + public IEnumerable GetStatisticsForDisplay() { foreach (var r in Ruleset.CreateInstance().GetHitResults()) From 085893c9b41992b1a8ea74c77d58faf43b9d2d74 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Jan 2022 13:03:57 +0900 Subject: [PATCH 341/996] Fix stray bracket --- osu.Game/Database/RealmObjectExtensions.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Database/RealmObjectExtensions.cs b/osu.Game/Database/RealmObjectExtensions.cs index 188d619627..a162bd64d3 100644 --- a/osu.Game/Database/RealmObjectExtensions.cs +++ b/osu.Game/Database/RealmObjectExtensions.cs @@ -154,8 +154,7 @@ namespace osu.Game.Database return realmList.Select(l => new RealmLiveUnmanaged(l)).Cast>().ToList(); } - public static ILive ToLiveUnmanaged(this T realmObject - ) + public static ILive ToLiveUnmanaged(this T realmObject) where T : RealmObject, IHasGuidPrimaryKey { return new RealmLiveUnmanaged(realmObject); From 7baff18764322ddc8c674b0ab29105c996a24f7d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Jan 2022 13:14:44 +0900 Subject: [PATCH 342/996] Add back `PerformRead` return safety by checking `IsManaged` status of returned data --- osu.Game/Database/RealmLive.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/osu.Game/Database/RealmLive.cs b/osu.Game/Database/RealmLive.cs index 45bc0e4200..6594224666 100644 --- a/osu.Game/Database/RealmLive.cs +++ b/osu.Game/Database/RealmLive.cs @@ -61,15 +61,18 @@ namespace osu.Game.Database /// The action to perform. public TReturn PerformRead(Func perform) { - // TODO: this is weird and kinda wrong... unmanaged objects should be allowed? - // if (typeof(RealmObjectBase).IsAssignableFrom(typeof(TReturn))) - // throw new InvalidOperationException(@$"Realm live objects should not exit the scope of {nameof(PerformRead)}."); - if (!IsManaged) return perform(data); using (var realm = realmFactory.CreateContext()) - return perform(realm.Find(ID)); + { + var returnData = perform(realm.Find(ID)); + + if (returnData is RealmObjectBase realmObject && realmObject.IsManaged) + throw new InvalidOperationException(@$"Managed realm objects should not exit the scope of {nameof(PerformRead)}."); + + return returnData; + } } /// From 86b2ac3217dde65e3f9ef6b9fb46c3e5be3f2ece Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Jan 2022 13:19:49 +0900 Subject: [PATCH 343/996] Remove unnecessary `Ruleset` null check in `BeatmapDifficultyCache` --- osu.Game/Beatmaps/BeatmapDifficultyCache.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs index 981120c5a8..f102daeef5 100644 --- a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs +++ b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs @@ -136,7 +136,7 @@ namespace osu.Game.Beatmaps var localRulesetInfo = rulesetInfo as RulesetInfo; // Difficulty can only be computed if the beatmap and ruleset are locally available. - if (localBeatmapInfo?.Ruleset == null || localRulesetInfo == null) + if (localBeatmapInfo == null || localRulesetInfo == null) { // If not, fall back to the existing star difficulty (e.g. from an online source). return Task.FromResult(new StarDifficulty(beatmapInfo.StarRating, (beatmapInfo as IBeatmapOnlineInfo)?.MaxCombo ?? 0)); From b77cb344d56ee1d92eee8b6d9fefc269ad7b165a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Jan 2022 13:23:41 +0900 Subject: [PATCH 344/996] Use `ctor` rather than `MemberwiseClone` to guarantee a safer clone of `BeatmapDifficulty` --- osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs | 2 ++ osu.Game/Beatmaps/BeatmapDifficulty.cs | 7 +------ 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index 61f932fd98..613874b7d6 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -208,6 +208,8 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps #region Overrides of BeatmapDifficulty + public override BeatmapDifficulty Clone() => new TaikoMultiplierAppliedDifficulty(this); + public override void CopyTo(BeatmapDifficulty other) { base.CopyTo(other); diff --git a/osu.Game/Beatmaps/BeatmapDifficulty.cs b/osu.Game/Beatmaps/BeatmapDifficulty.cs index f6e8b8c57d..7e0462f1e8 100644 --- a/osu.Game/Beatmaps/BeatmapDifficulty.cs +++ b/osu.Game/Beatmaps/BeatmapDifficulty.cs @@ -37,12 +37,7 @@ namespace osu.Game.Beatmaps /// /// Returns a shallow-clone of this . /// - public BeatmapDifficulty Clone() - { - var diff = (BeatmapDifficulty)MemberwiseClone(); - CopyTo(diff); - return diff; - } + public virtual BeatmapDifficulty Clone() => new BeatmapDifficulty(this); public virtual void CopyTo(BeatmapDifficulty difficulty) { From 7a81fe19f6ba8ae6f266924cb8e8046f9aa0659c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Jan 2022 13:28:46 +0900 Subject: [PATCH 345/996] Bump realm schema version to allow upgrades --- osu.Game/Database/RealmContextFactory.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 0802641b06..5cdae15935 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -47,8 +47,9 @@ namespace osu.Game.Database /// 10 2021-11-22 Use ShortName instead of RulesetID for ruleset settings. /// 11 2021-11-22 Use ShortName instead of RulesetID for ruleset key bindings. /// 12 2021-11-24 Add Status to RealmBeatmapSet. + /// 13 2022-01-13 Final migration of beatmaps and scores to realm (multiple new storage fields). /// - private const int schema_version = 12; + private const int schema_version = 13; /// /// Lock object which is held during sections, blocking context creation during blocking periods. From dcc354aa7c24640c8aceef89623c0efce472b273 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Jan 2022 13:40:09 +0900 Subject: [PATCH 346/996] Fix deleted scores not being cleaned up on next startup --- osu.Game/Database/RealmContextFactory.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 5cdae15935..9307e06be0 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -18,6 +18,7 @@ using osu.Game.Models; using osu.Game.Skinning; using osu.Game.Stores; using osu.Game.Rulesets; +using osu.Game.Scoring; using Realms; #nullable enable @@ -112,6 +113,11 @@ namespace osu.Game.Database using (var realm = CreateContext()) using (var transaction = realm.BeginWrite()) { + var pendingDeleteScores = realm.All().Where(s => s.DeletePending); + + foreach (var score in pendingDeleteScores) + realm.Remove(score); + var pendingDeleteSets = realm.All().Where(s => s.DeletePending); foreach (var beatmapSet in pendingDeleteSets) From 0a133c7e97c5184f2e8fe3c6edfc70bd254508b9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Jan 2022 13:47:23 +0900 Subject: [PATCH 347/996] Fix typo in comment in `IntroScreen` --- 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 c4343625ab..0f5dbb2842 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -212,7 +212,7 @@ namespace osu.Game.Screens.Menu if (!resuming) { // generally this can never be null - // an exception is running ruleset tests, where the osu! ruleset may not be prsent (causing importing the intro to fail). + // an exception is running ruleset tests, where the osu! ruleset may not be present (causing importing the intro to fail). if (initialBeatmap != null) beatmap.Value = initialBeatmap; Track = beatmap.Value.Track; From 6025fe325df0d49e5526b697d514d86c0788259a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Jan 2022 15:08:51 +0900 Subject: [PATCH 348/996] Fix filter criteria not being applied after carousel loads new beatmap sets --- osu.Game/Screens/Select/BeatmapCarousel.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 734d9bbde7..13ece08144 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -124,8 +124,7 @@ namespace osu.Game.Screens.Select itemsCache.Invalidate(); ScrollToSelected(); - // apply any pending filter operation that may have been delayed (see applyActiveCriteria's scheduling behaviour when BeatmapSetsLoaded is false). - FlushPendingFilterOperations(); + applyActiveCriteria(false); // Run on late scheduler want to ensure this runs after all pending UpdateBeatmapSet / RemoveBeatmapSet operations are run. SchedulerAfterChildren.Add(() => From 88145dedf14bf0cedf13aabf6d7d1be29a050ac2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Jan 2022 15:27:43 +0900 Subject: [PATCH 349/996] Remove oudated comments --- osu.Game/Stores/RealmArchiveModelImporter.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Stores/RealmArchiveModelImporter.cs b/osu.Game/Stores/RealmArchiveModelImporter.cs index 4aca079e2e..9927840c4e 100644 --- a/osu.Game/Stores/RealmArchiveModelImporter.cs +++ b/osu.Game/Stores/RealmArchiveModelImporter.cs @@ -393,9 +393,6 @@ namespace osu.Game.Stores LogForModel(item, @"Found existing but failed re-use check."); existing.DeletePending = true; - - // todo: actually delete? i don't think this is required... - // ModelStore.PurgeDeletable(s => s.ID == existing.ID); } PreImport(item, realm); From 70c107b434eeab82d57f71e34556347d59a7aff3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Jan 2022 16:26:35 +0900 Subject: [PATCH 350/996] Remove pointless override method in `RealmArchiveModelManager` --- osu.Game/Stores/RealmArchiveModelManager.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/osu.Game/Stores/RealmArchiveModelManager.cs b/osu.Game/Stores/RealmArchiveModelManager.cs index 4365fdab1e..b456dae343 100644 --- a/osu.Game/Stores/RealmArchiveModelManager.cs +++ b/osu.Game/Stores/RealmArchiveModelManager.cs @@ -5,12 +5,9 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Threading; -using System.Threading.Tasks; using osu.Framework.Platform; using osu.Game.Database; using osu.Game.Extensions; -using osu.Game.IO.Archives; using osu.Game.Models; using osu.Game.Overlays.Notifications; using Realms; @@ -93,11 +90,6 @@ namespace osu.Game.Stores item.Files.Add(namedUsage); } - public override async Task?> Import(TModel item, ArchiveReader? archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) - { - return await base.Import(item, archive, lowPriority, cancellationToken).ConfigureAwait(false); - } - /// /// Delete multiple items. /// This will post notifications tracking progress. From bdb2979b2e895e0e85ef2137114f44ccf270ea48 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Jan 2022 16:27:07 +0900 Subject: [PATCH 351/996] Remove `async` from `Populate` method --- osu.Game/Scoring/ScoreModelManager.cs | 5 +---- osu.Game/Skinning/SkinModelManager.cs | 5 +---- osu.Game/Stores/BeatmapImporter.cs | 5 +---- osu.Game/Stores/RealmArchiveModelImporter.cs | 12 ++++++------ 4 files changed, 9 insertions(+), 18 deletions(-) diff --git a/osu.Game/Scoring/ScoreModelManager.cs b/osu.Game/Scoring/ScoreModelManager.cs index 09a5919f12..fcf7244226 100644 --- a/osu.Game/Scoring/ScoreModelManager.cs +++ b/osu.Game/Scoring/ScoreModelManager.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading; -using System.Threading.Tasks; using Newtonsoft.Json; using osu.Framework.Logging; using osu.Framework.Platform; @@ -55,7 +54,7 @@ namespace osu.Game.Scoring public Score GetScore(ScoreInfo score) => new LegacyDatabasedScore(score, rulesets, beatmaps(), Files.Store); - protected override Task Populate(ScoreInfo model, ArchiveReader? archive, Realm realm, CancellationToken cancellationToken = default) + protected override void Populate(ScoreInfo model, ArchiveReader? archive, Realm realm, CancellationToken cancellationToken = default) { // Ensure the beatmap is not detached. if (!model.BeatmapInfo.IsManaged) @@ -66,8 +65,6 @@ namespace osu.Game.Scoring if (string.IsNullOrEmpty(model.StatisticsJson)) model.StatisticsJson = JsonConvert.SerializeObject(model.Statistics); - - return Task.CompletedTask; } public override bool IsAvailableLocally(ScoreInfo model) diff --git a/osu.Game/Skinning/SkinModelManager.cs b/osu.Game/Skinning/SkinModelManager.cs index dbd209e984..b8313f63a3 100644 --- a/osu.Game/Skinning/SkinModelManager.cs +++ b/osu.Game/Skinning/SkinModelManager.cs @@ -7,7 +7,6 @@ using System.IO; using System.Linq; using System.Text; using System.Threading; -using System.Threading.Tasks; using Newtonsoft.Json; using osu.Framework.Logging; using osu.Framework.Platform; @@ -49,7 +48,7 @@ namespace osu.Game.Skinning protected override bool HasCustomHashFunction => true; - protected override Task Populate(SkinInfo model, ArchiveReader? archive, Realm realm, CancellationToken cancellationToken = default) + protected override void Populate(SkinInfo model, ArchiveReader? archive, Realm realm, CancellationToken cancellationToken = default) { var skinInfoFile = model.Files.SingleOrDefault(f => f.Filename == skin_info_file); @@ -83,8 +82,6 @@ namespace osu.Game.Skinning model.InstantiationInfo = createInstance(model).GetType().GetInvariantInstantiationInfo(); checkSkinIniMetadata(model, realm); - - return Task.CompletedTask; } private void checkSkinIniMetadata(SkinInfo item, Realm realm) diff --git a/osu.Game/Stores/BeatmapImporter.cs b/osu.Game/Stores/BeatmapImporter.cs index 429d3951cf..53971b1d96 100644 --- a/osu.Game/Stores/BeatmapImporter.cs +++ b/osu.Game/Stores/BeatmapImporter.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; -using System.Threading.Tasks; using osu.Framework.Audio.Track; using osu.Framework.Extensions; using osu.Framework.Extensions.IEnumerableExtensions; @@ -53,7 +52,7 @@ namespace osu.Game.Stores protected override bool ShouldDeleteArchive(string path) => Path.GetExtension(path).ToLowerInvariant() == ".osz"; - protected override Task Populate(BeatmapSetInfo beatmapSet, ArchiveReader? archive, Realm realm, CancellationToken cancellationToken = default) + protected override void Populate(BeatmapSetInfo beatmapSet, ArchiveReader? archive, Realm realm, CancellationToken cancellationToken = default) { if (archive != null) beatmapSet.Beatmaps.AddRange(createBeatmapDifficulties(beatmapSet.Files, realm)); @@ -87,8 +86,6 @@ namespace osu.Game.Stores LogForModel(beatmapSet, "Disassociating beatmap set ID due to loss of all beatmap IDs"); } } - - return Task.CompletedTask; } protected override void PreImport(BeatmapSetInfo beatmapSet, Realm realm) diff --git a/osu.Game/Stores/RealmArchiveModelImporter.cs b/osu.Game/Stores/RealmArchiveModelImporter.cs index 9927840c4e..bcae9f2451 100644 --- a/osu.Game/Stores/RealmArchiveModelImporter.cs +++ b/osu.Game/Stores/RealmArchiveModelImporter.cs @@ -318,7 +318,7 @@ namespace osu.Game.Stores /// An optional archive to use for model population. /// Whether this is a low priority import. /// An optional cancellation token. - public virtual async Task?> Import(TModel item, ArchiveReader? archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) + public virtual Task?> Import(TModel item, ArchiveReader? archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) { using (var realm = ContextFactory.CreateContext()) { @@ -352,7 +352,7 @@ namespace osu.Game.Stores transaction.Commit(); } - return existing.ToLive(ContextFactory); + return Task.FromResult((ILive?)existing.ToLive(ContextFactory)); } LogForModel(item, @"Found existing (optimised) but failed pre-check."); @@ -373,7 +373,7 @@ namespace osu.Game.Stores item.Hash = ComputeHash(item); // TODO: we may want to run this outside of the transaction. - await Populate(item, archive, realm, cancellationToken).ConfigureAwait(false); + Populate(item, archive, realm, cancellationToken); if (!checkedExisting) existing = CheckForExisting(item, realm); @@ -387,7 +387,7 @@ namespace osu.Game.Stores existing.DeletePending = false; transaction.Commit(); - return existing.ToLive(ContextFactory); + return Task.FromResult((ILive?)existing.ToLive(ContextFactory)); } LogForModel(item, @"Found existing but failed re-use check."); @@ -413,7 +413,7 @@ namespace osu.Game.Stores throw; } - return item.ToLive(ContextFactory); + return Task.FromResult((ILive?)item.ToLive(ContextFactory)); } } @@ -480,7 +480,7 @@ namespace osu.Game.Stores /// The archive to use as a reference for population. May be null. /// The current realm context. /// An optional cancellation token. - protected abstract Task Populate(TModel model, ArchiveReader? archive, Realm realm, CancellationToken cancellationToken = default); + protected abstract void Populate(TModel model, ArchiveReader? archive, Realm realm, CancellationToken cancellationToken = default); /// /// Perform any final actions before the import to database executes. From 93c78253d65065ba649f382183d4e12b643ab44a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Jan 2022 16:13:30 +0900 Subject: [PATCH 352/996] Add synchronous fetch flow to `BeatmapOnlineLookupQueue` The async flow doesn't work great with the realm import process. We might be able to improve on this going forward, but for the time being adding a synchronous path seems safest. After all, we are already an an asynchronous (dedicated) thread pool at this point. --- osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs | 21 ++++++++++++------- osu.Game/Stores/BeatmapImporter.cs | 6 +----- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs b/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs index 444bf09487..aecbd6f6e2 100644 --- a/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs +++ b/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs @@ -54,6 +54,12 @@ namespace osu.Game.Beatmaps prepareLocalCache(); } + public void Update(BeatmapSetInfo beatmapSet, CancellationToken cancellationToken) + { + foreach (var b in beatmapSet.Beatmaps) + lookup(beatmapSet, b); + } + public Task UpdateAsync(BeatmapSetInfo beatmapSet, CancellationToken cancellationToken) { return Task.WhenAll(beatmapSet.Beatmaps.Select(b => UpdateAsync(beatmapSet, b, cancellationToken)).ToArray()); @@ -73,13 +79,17 @@ namespace osu.Game.Beatmaps var req = new GetBeatmapRequest(beatmapInfo); - req.Failure += fail; - try { // intentionally blocking to limit web request concurrency api.Perform(req); + if (req.CompletionState == APIRequestCompletionState.Failed) + { + logForModel(set, $"Online retrieval failed for {beatmapInfo}"); + beatmapInfo.OnlineID = -1; + } + var res = req.Response; if (res != null) @@ -99,13 +109,8 @@ namespace osu.Game.Beatmaps } catch (Exception e) { - fail(e); - } - - void fail(Exception e) - { - beatmapInfo.OnlineID = -1; logForModel(set, $"Online retrieval failed for {beatmapInfo} ({e.Message})"); + beatmapInfo.OnlineID = -1; } } diff --git a/osu.Game/Stores/BeatmapImporter.cs b/osu.Game/Stores/BeatmapImporter.cs index 53971b1d96..a9cb46ca29 100644 --- a/osu.Game/Stores/BeatmapImporter.cs +++ b/osu.Game/Stores/BeatmapImporter.cs @@ -71,11 +71,7 @@ namespace osu.Game.Stores bool hadOnlineIDs = beatmapSet.Beatmaps.Any(b => b.OnlineID > 0); - if (onlineLookupQueue != null) - { - // TODO: this required `BeatmapOnlineLookupQueue` to somehow support new types. - // await onlineLookupQueue.UpdateAsync(beatmapSet, cancellationToken).ConfigureAwait(false); - } + onlineLookupQueue?.Update(beatmapSet, cancellationToken); // ensure at least one beatmap was able to retrieve or keep an online ID, else drop the set ID. if (hadOnlineIDs && !beatmapSet.Beatmaps.Any(b => b.OnlineID > 0)) From c61419dfe5606b2749381b9a7eae275d4762e4bb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Jan 2022 16:56:09 +0900 Subject: [PATCH 353/996] Fix scores not using correct filename/display strings I've updated all cases where we should have been using `GetDisplayString()` anyway, but left the `ToString()` implementations in place for safety. They should probably be removed in the future. --- osu.Game/Database/LegacyExporter.cs | 2 +- osu.Game/Scoring/ScoreInfo.cs | 2 ++ osu.Game/Stores/RealmArchiveModelImporter.cs | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/LegacyExporter.cs b/osu.Game/Database/LegacyExporter.cs index 802ccec6ed..ee960b6b30 100644 --- a/osu.Game/Database/LegacyExporter.cs +++ b/osu.Game/Database/LegacyExporter.cs @@ -35,7 +35,7 @@ namespace osu.Game.Database /// The item to export. public void Export(TModel item) { - string filename = $"{item.ToString().GetValidArchiveContentFilename()}{FileExtension}"; + string filename = $"{item.GetDisplayString().GetValidArchiveContentFilename()}{FileExtension}"; using (var stream = exportStorage.GetStream(filename, FileAccess.Write, FileMode.Create)) ExportModelTo(item, stream); diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index a10039bc72..9b4f4bf784 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -274,5 +274,7 @@ namespace osu.Game.Scoring } #endregion + + public override string ToString() => this.GetDisplayTitle(); } } diff --git a/osu.Game/Stores/RealmArchiveModelImporter.cs b/osu.Game/Stores/RealmArchiveModelImporter.cs index bcae9f2451..2ea7aecc94 100644 --- a/osu.Game/Stores/RealmArchiveModelImporter.cs +++ b/osu.Game/Stores/RealmArchiveModelImporter.cs @@ -169,7 +169,7 @@ namespace osu.Game.Stores else { notification.CompletionText = imported.Count == 1 - ? $"Imported {imported.First()}!" + ? $"Imported {imported.First().GetDisplayString()}!" : $"Imported {imported.Count} {HumanisedModelName}s!"; if (imported.Count > 0 && PostImport != null) From 2de0c34bc9fa10ed728a599ae563172348fa7bf2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Jan 2022 17:10:48 +0900 Subject: [PATCH 354/996] Reduce exposure of `ChannelManager` --- osu.Game/Online/Chat/StandAloneChatDisplay.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/Chat/StandAloneChatDisplay.cs b/osu.Game/Online/Chat/StandAloneChatDisplay.cs index 81fd5ad98c..f83bf4877e 100644 --- a/osu.Game/Online/Chat/StandAloneChatDisplay.cs +++ b/osu.Game/Online/Chat/StandAloneChatDisplay.cs @@ -25,7 +25,7 @@ namespace osu.Game.Online.Chat protected readonly ChatTextBox TextBox; - protected ChannelManager ChannelManager; + private ChannelManager channelManager; private StandAloneDrawableChannel drawableChannel; @@ -80,7 +80,7 @@ namespace osu.Game.Online.Chat [BackgroundDependencyLoader(true)] private void load(ChannelManager manager) { - ChannelManager ??= manager; + channelManager ??= manager; } protected virtual StandAloneDrawableChannel CreateDrawableChannel(Channel channel) => @@ -94,9 +94,9 @@ namespace osu.Game.Online.Chat return; if (text[0] == '/') - ChannelManager?.PostCommand(text.Substring(1), Channel.Value); + channelManager?.PostCommand(text.Substring(1), Channel.Value); else - ChannelManager?.PostMessage(text, target: Channel.Value); + channelManager?.PostMessage(text, target: Channel.Value); TextBox.Text = string.Empty; } From 0bd34253e7a4614014c69f386acc5e89b38725b7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Jan 2022 17:31:49 +0900 Subject: [PATCH 355/996] Increase chat polling rate during multiplayer lobby / games --- osu.Game/OsuGame.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index c5a465ae96..21d84a365b 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -820,7 +820,17 @@ namespace osu.Game loadComponentSingleFile(CreateHighPerformanceSession(), Add); - chatOverlay.State.ValueChanged += state => channelManager.HighPollRate.Value = state.NewValue == Visibility.Visible; + chatOverlay.State.BindValueChanged(_ => updateChatPollRate()); + // Multiplayer modes need to increase poll rate temporarily. + API.Activity.BindValueChanged(_ => updateChatPollRate(), true); + + void updateChatPollRate() + { + channelManager.HighPollRate.Value = + chatOverlay.State.Value == Visibility.Visible + || API.Activity.Value is UserActivity.InLobby + || API.Activity.Value is UserActivity.InMultiplayerGame; + } Add(difficultyRecommender); Add(externalLinkOpener = new ExternalLinkOpener()); From 46d2f305b5967ec2098fe7305f8e65b9808c9e60 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Jan 2022 17:31:55 +0900 Subject: [PATCH 356/996] Log chat polling rate changes --- osu.Game/Online/Chat/ChannelManager.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 82e042ae4e..e5acda89f2 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -90,13 +90,14 @@ namespace osu.Game.Online.Chat { // Polling will eventually be replaced with websocket, but let's avoid doing these background operations as much as possible for now. // The only loss will be delayed PM/message highlight notifications. - if (HighPollRate.Value) TimeBetweenPolls.Value = 1000; else if (!isIdle.Value) TimeBetweenPolls.Value = 60000; else TimeBetweenPolls.Value = 600000; + + Logger.Log($"Chat is now polling every {TimeBetweenPolls.Value} ms"); } /// From 4012ef7e7b583bf5f245462fd94420266bdafc07 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Jan 2022 17:33:55 +0900 Subject: [PATCH 357/996] Reduce polling rate when idle even if `HighPollRate` is requested --- osu.Game/Online/Chat/ChannelManager.cs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index e5acda89f2..77b52c34d9 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -90,14 +90,16 @@ namespace osu.Game.Online.Chat { // Polling will eventually be replaced with websocket, but let's avoid doing these background operations as much as possible for now. // The only loss will be delayed PM/message highlight notifications. - if (HighPollRate.Value) - TimeBetweenPolls.Value = 1000; - else if (!isIdle.Value) - TimeBetweenPolls.Value = 60000; - else - TimeBetweenPolls.Value = 600000; + int millisecondsBetweenPolls = HighPollRate.Value ? 1000 : 60000; - Logger.Log($"Chat is now polling every {TimeBetweenPolls.Value} ms"); + if (isIdle.Value) + millisecondsBetweenPolls *= 10; + + if (TimeBetweenPolls.Value != millisecondsBetweenPolls) + { + TimeBetweenPolls.Value = millisecondsBetweenPolls; + Logger.Log($"Chat is now polling every {TimeBetweenPolls.Value} ms"); + } } /// From 069d6d2954a437080b2fb0f456ad7a033b296253 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Jan 2022 18:02:02 +0900 Subject: [PATCH 358/996] Remove pointless compatibility parameter `BeatmapSetInfoID` --- osu.Game/Beatmaps/BeatmapInfo.cs | 3 --- osu.Game/Screens/Select/SongSelect.cs | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 1c1f81f143..078a34e50d 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -163,9 +163,6 @@ namespace osu.Game.Beatmaps } } - [Ignored] - public Guid BeatmapSetInfoID => BeatmapSet?.ID ?? Guid.Empty; - [Ignored] [IgnoreMap] public BeatmapDifficulty BaseDifficulty diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 41700bec17..b390cd225a 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -486,7 +486,7 @@ namespace osu.Game.Screens.Select { if (beatmap != null && beatmapInfoPrevious != null && Time.Current - audioFeedbackLastPlaybackTime >= 50) { - if (beatmap.BeatmapSetInfoID == beatmapInfoPrevious.BeatmapSetInfoID) + if (beatmap.BeatmapSet?.ID == beatmapInfoPrevious.BeatmapSet?.ID) sampleChangeDifficulty.Play(); else sampleChangeBeatmap.Play(); From ded1d87739543fa8370c10a94cd63dbcb09ecb79 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Jan 2022 18:23:10 +0900 Subject: [PATCH 359/996] Move `RulesetStore` construction earlier in process so rulesets are available for EF migration --- osu.Game/OsuGameBase.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index a003c6b8ab..a9538c1e8c 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -189,6 +189,10 @@ namespace osu.Game : null; dependencies.Cache(realmFactory = new RealmContextFactory(Storage, "client", efContextFactory)); + + dependencies.Cache(RulesetStore = new RulesetStore(realmFactory, Storage)); + dependencies.CacheAs(RulesetStore); + if (efContextFactory != null) new EFToRealmMigrator(efContextFactory, realmFactory, LocalConfig).Run(); @@ -219,9 +223,6 @@ namespace osu.Game var defaultBeatmap = new DummyWorkingBeatmap(Audio, Textures); - dependencies.Cache(RulesetStore = new RulesetStore(realmFactory, Storage)); - dependencies.CacheAs(RulesetStore); - // ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup() dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, realmFactory, Scheduler, Host, () => difficultyCache, LocalConfig)); dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, realmFactory, RulesetStore, API, Audio, Resources, Host, defaultBeatmap, performOnlineLookups: true)); From 45a23e5a43f90e897a4761774147540772c7d430 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Jan 2022 17:51:23 +0900 Subject: [PATCH 360/996] Add EF to realm score migration --- osu.Game/Database/EFToRealmMigrator.cs | 91 +++++++++++++++++++++++--- 1 file changed, 82 insertions(+), 9 deletions(-) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index b79a982460..1aa364ff8f 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -3,9 +3,13 @@ using System.Linq; using Microsoft.EntityFrameworkCore; +using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Models; +using osu.Game.Rulesets; +using osu.Game.Scoring; using osu.Game.Skinning; +using Realms; #nullable enable @@ -30,6 +34,70 @@ namespace osu.Game.Database { migrateSettings(db); migrateSkins(db); + + migrateScores(db); + } + } + + private void migrateScores(DatabaseWriteUsage db) + { + // can be removed 20220730. + var existingScores = db.Context.ScoreInfo + .Include(s => s.Ruleset) + .Include(s => s.BeatmapInfo) + .Include(s => s.Files) + .ThenInclude(f => f.FileInfo) + .ToList(); + + // previous entries in EF are removed post migration. + if (!existingScores.Any()) + return; + + using (var realm = realmContextFactory.CreateContext()) + using (var transaction = realm.BeginWrite()) + { + // only migrate data if the realm database is empty. + // note that this cannot be written as: `realm.All().All(s => s.Protected)`, because realm does not support `.All()`. + if (!realm.All().Any()) + { + foreach (var score in existingScores) + { + var realmScore = new ScoreInfo + { + Hash = score.Hash, + DeletePending = score.DeletePending, + OnlineID = score.OnlineID ?? -1, + ModsJson = score.ModsJson, + StatisticsJson = score.StatisticsJson, + User = score.User, + TotalScore = score.TotalScore, + MaxCombo = score.MaxCombo, + Accuracy = score.Accuracy, + HasReplay = ((IScoreInfo)score).HasReplay, + Date = score.Date, + PP = score.PP, + BeatmapInfo = realm.All().First(b => b.Hash == score.BeatmapInfo.Hash), + Ruleset = realm.Find(score.Ruleset.ShortName), + Rank = score.Rank, + HitEvents = score.HitEvents, + Passed = score.Passed, + Combo = score.Combo, + Position = score.Position, + Statistics = score.Statistics, + Mods = score.Mods, + APIMods = score.APIMods, + }; + + migrateFiles(score, realm, realmScore); + + realm.Add(realmScore); + } + } + + db.Context.RemoveRange(existingScores); + // Intentionally don't clean up the files, so they don't get purged by EF. + + transaction.Commit(); } } @@ -77,15 +145,7 @@ namespace osu.Game.Database InstantiationInfo = skin.InstantiationInfo, }; - foreach (var file in skin.Files) - { - var realmFile = realm.Find(file.FileInfo.Hash); - - if (realmFile == null) - realm.Add(realmFile = new RealmFile { Hash = file.FileInfo.Hash }); - - realmSkin.Files.Add(new RealmNamedFileUsage(realmFile, file.Filename)); - } + migrateFiles(skin, realm, realmSkin); realm.Add(realmSkin); @@ -101,6 +161,19 @@ namespace osu.Game.Database } } + private static void migrateFiles(IHasFiles fileSource, Realm realm, IHasRealmFiles realmObject) where T : INamedFileInfo + { + foreach (var file in fileSource.Files) + { + var realmFile = realm.Find(file.FileInfo.Hash); + + if (realmFile == null) + realm.Add(realmFile = new RealmFile { Hash = file.FileInfo.Hash }); + + realmObject.Files.Add(new RealmNamedFileUsage(realmFile, file.Filename)); + } + } + private void migrateSettings(DatabaseWriteUsage db) { // migrate ruleset settings. can be removed 20220315. From b610d2db124a580f233c94ff450ecef3e2fd6111 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Jan 2022 18:02:08 +0900 Subject: [PATCH 361/996] Add EF to realm beatmap migration --- osu.Game/Beatmaps/EFBeatmapInfo.cs | 25 +++--- osu.Game/Database/EFToRealmMigrator.cs | 103 ++++++++++++++++++++++++- 2 files changed, 115 insertions(+), 13 deletions(-) diff --git a/osu.Game/Beatmaps/EFBeatmapInfo.cs b/osu.Game/Beatmaps/EFBeatmapInfo.cs index 8257d0cf7d..8daeaa7030 100644 --- a/osu.Game/Beatmaps/EFBeatmapInfo.cs +++ b/osu.Game/Beatmaps/EFBeatmapInfo.cs @@ -41,7 +41,7 @@ namespace osu.Game.Beatmaps public BeatmapOnlineStatus Status { get; set; } = BeatmapOnlineStatus.None; [Required] - public EFBeatmapSetInfo BeatmapSet { get; set; } + public EFBeatmapSetInfo BeatmapSetInfo { get; set; } public EFBeatmapMetadata Metadata { get; set; } @@ -85,9 +85,10 @@ namespace osu.Game.Beatmaps public float StackLeniency { get; set; } = 0.7f; public bool SpecialStyle { get; set; } - public int RulesetID { get; set; } + [Column("RulesetID")] + public int RulesetInfoID { get; set; } - public EFRulesetInfo Ruleset { get; set; } + public EFRulesetInfo RulesetInfo { get; set; } public bool LetterboxInBreaks { get; set; } public bool WidescreenStoryboard { get; set; } @@ -145,13 +146,13 @@ namespace osu.Game.Beatmaps public bool Equals(IBeatmapInfo other) => other is EFBeatmapInfo b && Equals(b); - public bool AudioEquals(EFBeatmapInfo other) => other != null && BeatmapSet != null && other.BeatmapSet != null && - BeatmapSet.Hash == other.BeatmapSet.Hash && - (Metadata ?? BeatmapSet.Metadata).AudioFile == (other.Metadata ?? other.BeatmapSet.Metadata).AudioFile; + public bool AudioEquals(EFBeatmapInfo other) => other != null && BeatmapSetInfo != null && other.BeatmapSetInfo != null && + BeatmapSetInfo.Hash == other.BeatmapSetInfo.Hash && + (Metadata ?? BeatmapSetInfo.Metadata).AudioFile == (other.Metadata ?? other.BeatmapSetInfo.Metadata).AudioFile; - public bool BackgroundEquals(EFBeatmapInfo other) => other != null && BeatmapSet != null && other.BeatmapSet != null && - BeatmapSet.Hash == other.BeatmapSet.Hash && - (Metadata ?? BeatmapSet.Metadata).BackgroundFile == (other.Metadata ?? other.BeatmapSet.Metadata).BackgroundFile; + public bool BackgroundEquals(EFBeatmapInfo other) => other != null && BeatmapSetInfo != null && other.BeatmapSetInfo != null && + BeatmapSetInfo.Hash == other.BeatmapSetInfo.Hash && + (Metadata ?? BeatmapSetInfo.Metadata).BackgroundFile == (other.Metadata ?? other.BeatmapSetInfo.Metadata).BackgroundFile; /// /// Returns a shallow-clone of this . @@ -167,16 +168,16 @@ namespace osu.Game.Beatmaps #region Implementation of IBeatmapInfo [JsonIgnore] - IBeatmapMetadataInfo IBeatmapInfo.Metadata => Metadata ?? BeatmapSet?.Metadata ?? new EFBeatmapMetadata(); + IBeatmapMetadataInfo IBeatmapInfo.Metadata => Metadata ?? BeatmapSetInfo?.Metadata ?? new EFBeatmapMetadata(); [JsonIgnore] IBeatmapDifficultyInfo IBeatmapInfo.Difficulty => BaseDifficulty; [JsonIgnore] - IBeatmapSetInfo IBeatmapInfo.BeatmapSet => BeatmapSet; + IBeatmapSetInfo IBeatmapInfo.BeatmapSet => BeatmapSetInfo; [JsonIgnore] - IRulesetInfo IBeatmapInfo.Ruleset => Ruleset; + IRulesetInfo IBeatmapInfo.Ruleset => RulesetInfo; #endregion } diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index 1aa364ff8f..02237b0ec0 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -35,10 +35,111 @@ namespace osu.Game.Database migrateSettings(db); migrateSkins(db); + migrateBeatmaps(db); migrateScores(db); } } + private void migrateBeatmaps(DatabaseWriteUsage db) + { + // can be removed 20220730. + var existingBeatmapSets = db.Context.EFBeatmapSetInfo + .Include(s => s.Beatmaps).ThenInclude(b => b.RulesetInfo) + .Include(s => s.Beatmaps).ThenInclude(b => b.Metadata) + .Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty) + .Include(s => s.Files).ThenInclude(f => f.FileInfo) + .ToList(); + + // previous entries in EF are removed post migration. + if (!existingBeatmapSets.Any()) + return; + + using (var realm = realmContextFactory.CreateContext()) + using (var transaction = realm.BeginWrite()) + { + // only migrate data if the realm database is empty. + // note that this cannot be written as: `realm.All().All(s => s.Protected)`, because realm does not support `.All()`. + if (!realm.All().Any(s => !s.Protected)) + { + foreach (var beatmapSet in existingBeatmapSets) + { + var realmBeatmapSet = new BeatmapSetInfo + { + OnlineID = beatmapSet.OnlineID ?? -1, + DateAdded = beatmapSet.DateAdded, + Status = beatmapSet.Status, + DeletePending = beatmapSet.DeletePending, + Hash = beatmapSet.Hash, + Protected = beatmapSet.Protected, + }; + + migrateFiles(beatmapSet, realm, realmBeatmapSet); + + foreach (var beatmap in beatmapSet.Beatmaps) + { + var realmBeatmap = new BeatmapInfo + { + DifficultyName = beatmap.DifficultyName, + Status = beatmap.Status, + OnlineID = beatmap.OnlineID ?? -1, + Length = beatmap.Length, + BPM = beatmap.BPM, + Hash = beatmap.Hash, + StarRating = beatmap.StarRating, + MD5Hash = beatmap.MD5Hash, + Hidden = beatmap.Hidden, + AudioLeadIn = beatmap.AudioLeadIn, + StackLeniency = beatmap.StackLeniency, + SpecialStyle = beatmap.SpecialStyle, + LetterboxInBreaks = beatmap.LetterboxInBreaks, + WidescreenStoryboard = beatmap.WidescreenStoryboard, + EpilepsyWarning = beatmap.EpilepsyWarning, + SamplesMatchPlaybackRate = beatmap.SamplesMatchPlaybackRate, + DistanceSpacing = beatmap.DistanceSpacing, + BeatDivisor = beatmap.BeatDivisor, + GridSize = beatmap.GridSize, + TimelineZoom = beatmap.TimelineZoom, + Countdown = beatmap.Countdown, + CountdownOffset = beatmap.CountdownOffset, + MaxCombo = beatmap.MaxCombo, + Bookmarks = beatmap.Bookmarks, + Ruleset = realm.Find(beatmap.RulesetInfo.ShortName), + Difficulty = new BeatmapDifficulty(beatmap.BaseDifficulty), + Metadata = new BeatmapMetadata + { + Title = beatmap.Metadata.Title, + TitleUnicode = beatmap.Metadata.TitleUnicode, + Artist = beatmap.Metadata.Artist, + ArtistUnicode = beatmap.Metadata.ArtistUnicode, + Author = new RealmUser + { + OnlineID = beatmap.Metadata.Author.Id, + Username = beatmap.Metadata.Author.Username, + }, + Source = beatmap.Metadata.Source, + Tags = beatmap.Metadata.Tags, + PreviewTime = beatmap.Metadata.PreviewTime, + AudioFile = beatmap.Metadata.AudioFile, + BackgroundFile = beatmap.Metadata.BackgroundFile, + AuthorString = beatmap.Metadata.AuthorString, + }, + BeatmapSet = realmBeatmapSet, + }; + + realmBeatmapSet.Beatmaps.Add(realmBeatmap); + } + + realm.Add(realmBeatmapSet); + } + } + + // db.Context.RemoveRange(existingBeatmapSets); + // Intentionally don't clean up the files, so they don't get purged by EF. + + transaction.Commit(); + } + } + private void migrateScores(DatabaseWriteUsage db) { // can be removed 20220730. @@ -94,7 +195,7 @@ namespace osu.Game.Database } } - db.Context.RemoveRange(existingScores); + // db.Context.RemoveRange(existingScores); // Intentionally don't clean up the files, so they don't get purged by EF. transaction.Commit(); From 2840a71dda2f50b5c934b290e8f7ab676a227412 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Jan 2022 18:28:00 +0900 Subject: [PATCH 362/996] Uncomment EF deletion lines in migrations --- osu.Game/Database/EFToRealmMigrator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index 02237b0ec0..ad0f2c5982 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -133,7 +133,7 @@ namespace osu.Game.Database } } - // db.Context.RemoveRange(existingBeatmapSets); + db.Context.RemoveRange(existingBeatmapSets); // Intentionally don't clean up the files, so they don't get purged by EF. transaction.Commit(); @@ -195,7 +195,7 @@ namespace osu.Game.Database } } - // db.Context.RemoveRange(existingScores); + db.Context.RemoveRange(existingScores); // Intentionally don't clean up the files, so they don't get purged by EF. transaction.Commit(); From c6ae255326a07da5fc73ab088b4e4560715600f5 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 13 Jan 2022 18:31:32 +0900 Subject: [PATCH 363/996] Add volume dip to track during fail sequence --- osu.Game/Screens/Play/FailAnimation.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/Play/FailAnimation.cs b/osu.Game/Screens/Play/FailAnimation.cs index cfbfdc9966..17a3e5eb71 100644 --- a/osu.Game/Screens/Play/FailAnimation.cs +++ b/osu.Game/Screens/Play/FailAnimation.cs @@ -36,6 +36,7 @@ namespace osu.Game.Screens.Play private readonly DrawableRuleset drawableRuleset; private readonly BindableDouble trackFreq = new BindableDouble(1); + private readonly BindableDouble volumeAdjustment = new BindableDouble(0.5); private Container filters; @@ -125,6 +126,7 @@ namespace osu.Game.Screens.Play failSample.Play(); track.AddAdjustment(AdjustableProperty.Frequency, trackFreq); + track.AddAdjustment(AdjustableProperty.Volume, volumeAdjustment); applyToPlayfield(drawableRuleset.Playfield); drawableRuleset.Playfield.HitObjectContainer.FadeOut(duration / 2); @@ -154,6 +156,8 @@ namespace osu.Game.Screens.Play if (resetTrackFrequency) track?.RemoveAdjustment(AdjustableProperty.Frequency, trackFreq); + track?.RemoveAdjustment(AdjustableProperty.Volume, volumeAdjustment); + if (filters.Parent == null) return; From c33fe7bcc6961ce24ba246e8c133a21864a6fd30 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Jan 2022 18:32:59 +0900 Subject: [PATCH 364/996] Remove one more unnecessary `Detach` operation --- osu.Game/OsuGame.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index ad045d08d2..0be6a808d8 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -501,8 +501,6 @@ namespace osu.Game return; } - databasedScoreInfo = databasedScoreInfo.Detach(); - var databasedScore = ScoreManager.GetScore(databasedScoreInfo); if (databasedScore.Replay == null) From 54804ebfbdc1f39b30df11da48bb496f5c2766e6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Jan 2022 18:38:38 +0900 Subject: [PATCH 365/996] Fix delete/clear scores buttons not working --- osu.Game/Screens/Select/SongSelect.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index b390cd225a..837f30eb2b 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -807,14 +807,14 @@ namespace osu.Game.Screens.Select private void delete(BeatmapSetInfo beatmap) { - if (beatmap == null || !beatmap.IsManaged) return; + if (beatmap == null) return; dialogOverlay?.Push(new BeatmapDeleteDialog(beatmap)); } private void clearScores(BeatmapInfo beatmapInfo) { - if (beatmapInfo == null || !beatmapInfo.IsManaged) return; + if (beatmapInfo == null) return; dialogOverlay?.Push(new BeatmapClearScoresDialog(beatmapInfo, () => // schedule done here rather than inside the dialog as the dialog may fade out and never callback. From 55b027228f31722d8ab84d9c292717c97c3206e6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 14 Jan 2022 11:54:50 +0900 Subject: [PATCH 366/996] Update resources --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 92adbebf5a..4198cf2bf4 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -51,7 +51,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 1430320c87..2609f17c73 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -37,7 +37,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 3cef888447..0064a597fd 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + From 9b33fbbee52cff0d1af77cba8ac165b11c11d261 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 14 Jan 2022 13:08:20 +0900 Subject: [PATCH 367/996] Ensure detached when performing model `Clone` operations on `BeatmapInfo`/`ScoreInfo` --- osu.Game/Beatmaps/BeatmapInfo.cs | 2 +- osu.Game/Scoring/ScoreInfo.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 078a34e50d..b9dd59cfe4 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -185,7 +185,7 @@ namespace osu.Game.Beatmaps public int BeatmapVersion; - public BeatmapInfo Clone() => (BeatmapInfo)MemberwiseClone(); + public BeatmapInfo Clone() => (BeatmapInfo)this.Detach().MemberwiseClone(); public override string ToString() => this.GetDisplayTitle(); diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 9b4f4bf784..b168726283 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -131,7 +131,7 @@ namespace osu.Game.Scoring public ScoreInfo DeepClone() { - var clone = (ScoreInfo)MemberwiseClone(); + var clone = (ScoreInfo)this.Detach().MemberwiseClone(); clone.Statistics = new Dictionary(clone.Statistics); From 8424d86e9afeec9670ffb7dd9203a588ec09ce4e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 14 Jan 2022 13:19:00 +0900 Subject: [PATCH 368/996] Remove unused `cancellationToken` parameter in synchronous `BeatmapOnlineLookupQueue` flow --- osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs | 2 +- osu.Game/Stores/BeatmapImporter.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs b/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs index aecbd6f6e2..a5d7ce23ff 100644 --- a/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs +++ b/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs @@ -54,7 +54,7 @@ namespace osu.Game.Beatmaps prepareLocalCache(); } - public void Update(BeatmapSetInfo beatmapSet, CancellationToken cancellationToken) + public void Update(BeatmapSetInfo beatmapSet) { foreach (var b in beatmapSet.Beatmaps) lookup(beatmapSet, b); diff --git a/osu.Game/Stores/BeatmapImporter.cs b/osu.Game/Stores/BeatmapImporter.cs index a9cb46ca29..3ebdcde296 100644 --- a/osu.Game/Stores/BeatmapImporter.cs +++ b/osu.Game/Stores/BeatmapImporter.cs @@ -71,7 +71,7 @@ namespace osu.Game.Stores bool hadOnlineIDs = beatmapSet.Beatmaps.Any(b => b.OnlineID > 0); - onlineLookupQueue?.Update(beatmapSet, cancellationToken); + onlineLookupQueue?.Update(beatmapSet); // ensure at least one beatmap was able to retrieve or keep an online ID, else drop the set ID. if (hadOnlineIDs && !beatmapSet.Beatmaps.Any(b => b.OnlineID > 0)) From dea2e1fac0215944b3c6b863158dedbe224aa2e0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 14 Jan 2022 13:20:51 +0900 Subject: [PATCH 369/996] Return immediately on failed web request in synchronous `BeatmapOnlineLookupQueue` flow --- osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs b/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs index a5d7ce23ff..a24b6b315a 100644 --- a/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs +++ b/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs @@ -88,6 +88,7 @@ namespace osu.Game.Beatmaps { logForModel(set, $"Online retrieval failed for {beatmapInfo}"); beatmapInfo.OnlineID = -1; + return; } var res = req.Response; From c64a919a9d29207061579e4400ae6f0244f4ac1d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 14 Jan 2022 16:29:26 +0900 Subject: [PATCH 370/996] Reduce number of redundant control points displayed on summary timeline As pointed out in https://github.com/ppy/osu/discussions/16435, beatmaps with too many control points (usually added via external automation apps) could cause the lazer editor to grind to a halt. The overheads here are mostly from the GL side. An eventual goal would be to render this in a smarter way, rather than using thousands of drawables. Until that, this optimisation should help reduce the overhead by omitting control points in close proximity that are redundant for display purposes. I've tried to contain this in the display logic directly, with the goal that it can be ripped out as fast as it was added. Certainly required more changes than I hoped for, but I don't think it's too ugly. --- .../Summary/Parts/ControlPointPart.cs | 24 ++++++++++++++++++- .../Parts/ControlPointVisualisation.cs | 4 +++- .../Summary/Parts/EffectPointVisualisation.cs | 5 +++- .../Summary/Parts/GroupVisualisation.cs | 15 ++++++++---- .../IControlPointVisualisationRedundant.cs | 12 ++++++++++ 5 files changed, 52 insertions(+), 8 deletions(-) create mode 100644 osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/IControlPointVisualisationRedundant.cs diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs index 70afc1e308..f6a7353e13 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs @@ -31,7 +31,16 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts case NotifyCollectionChangedAction.Add: foreach (var group in args.NewItems.OfType()) + { + // as an optimisation, don't add a visualisation if there are already groups with the same types in close proximity. + // for newly added control points (ie. lazer editor first where group is added empty) we always skip for simplicity. + // that is fine, because cases where this is causing a performance issue are mostly where external tools were used to create an insane number of points. + // if (Children.Any(g => Math.Abs(g.Group.Time - group.Time) < 1000 && g.IsRedundant(group))) + // continue; + Add(new GroupVisualisation(group)); + } + break; case NotifyCollectionChangedAction.Remove: @@ -39,7 +48,20 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts { var matching = Children.SingleOrDefault(gv => gv.Group == group); - matching?.Expire(); + if (matching != null) + matching.Expire(); + else + { + // due to the add optimisation above, if a point is deleted which wasn't being displayed we need to recreate all points + // to guarantee an accurate representation. + // + // note that the case where control point (type) is added or removed from a non-displayed group is not handled correctly. + // this is an edge case which shouldn't affect the user too badly. we may flatted control point groups in the future + // which would allow this to be handled better. + Clear(); + foreach (var g in controlPointGroups) + Add(new GroupVisualisation(g)); + } } break; diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointVisualisation.cs index a8e41d220a..96fce1dac5 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointVisualisation.cs @@ -9,7 +9,7 @@ using osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations; namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts { - public class ControlPointVisualisation : PointVisualisation + public class ControlPointVisualisation : PointVisualisation, IControlPointVisualisationRedundant { protected readonly ControlPoint Point; @@ -26,5 +26,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts { Colour = Point.GetRepresentingColour(colours); } + + public bool IsRedundant(ControlPoint other) => other.GetType() == Point.GetType(); } } diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs index 801372305b..c08ab3728f 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs @@ -13,7 +13,7 @@ using osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations; namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts { - public class EffectPointVisualisation : CompositeDrawable + public class EffectPointVisualisation : CompositeDrawable, IControlPointVisualisationRedundant { private readonly EffectControlPoint effect; private Bindable kiai; @@ -68,5 +68,8 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts } }, true); } + + // kiai sections display duration, so are required to be visualised. + public bool IsRedundant(ControlPoint other) => (other as EffectControlPoint)?.KiaiMode == effect.KiaiMode; } } diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs index f0e643f805..b55df66a02 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -23,12 +24,8 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts Group = group; X = (float)group.Time; - } - - protected override void LoadComplete() - { - base.LoadComplete(); + // Run in constructor so IsRedundant calls can work correctly. controlPoints.BindTo(Group.ControlPoints); controlPoints.BindCollectionChanged((_, __) => { @@ -60,5 +57,13 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts } }, true); } + + /// + /// For display purposes, check whether the proposed group is made redundant by this visualisation group. + /// + /// + /// + public bool IsRedundant(ControlPointGroup other) => + other.ControlPoints.Any(c => InternalChildren.OfType().Any(c2 => c2.IsRedundant(c))); } } diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/IControlPointVisualisationRedundant.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/IControlPointVisualisationRedundant.cs new file mode 100644 index 0000000000..e4d47e4cdc --- /dev/null +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/IControlPointVisualisationRedundant.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 osu.Game.Beatmaps.ControlPoints; + +namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts +{ + public interface IControlPointVisualisationRedundant + { + bool IsRedundant(ControlPoint other); + } +} From 916b591b1b6bc7c3e00fa6e4511926ea6eee2259 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 14 Jan 2022 18:03:06 +0900 Subject: [PATCH 371/996] Fix watch replay button not working immediately after playing --- osu.Game/Online/ScoreDownloadTracker.cs | 2 +- osu.Game/Screens/Play/Player.cs | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game/Online/ScoreDownloadTracker.cs b/osu.Game/Online/ScoreDownloadTracker.cs index 08362448a3..b34586567d 100644 --- a/osu.Game/Online/ScoreDownloadTracker.cs +++ b/osu.Game/Online/ScoreDownloadTracker.cs @@ -47,7 +47,7 @@ namespace osu.Game.Online Downloader.DownloadBegan += downloadBegan; Downloader.DownloadFailed += downloadFailed; - realmSubscription = realmContextFactory.Context.All().Where(s => s.OnlineID == TrackedItem.OnlineID && !s.DeletePending).QueryAsyncWithNotifications((items, changes, ___) => + realmSubscription = realmContextFactory.Context.All().Where(s => ((s.OnlineID > 0 && s.OnlineID == TrackedItem.OnlineID) || s.Hash == TrackedItem.Hash) && !s.DeletePending).QueryAsyncWithNotifications((items, changes, ___) => { if (items.Any()) Schedule(() => UpdateState(DownloadState.LocallyAvailable)); diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 9fc0d70cc0..ff3fea51cf 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -18,6 +18,7 @@ using osu.Framework.Screens; using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Configuration; +using osu.Game.Database; using osu.Game.Graphics.Containers; using osu.Game.IO.Archives; using osu.Game.Online.API; @@ -1038,17 +1039,17 @@ namespace osu.Game.Screens.Play 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. - var importableScore = score.ScoreInfo.DeepClone(); - // For the time being, online ID responses are not really useful for anything. // In addition, the IDs provided via new (lazer) endpoints are based on a different autoincrement from legacy (stable) scores. // // Until we better define the server-side logic behind this, let's not store the online ID to avoid potential unique constraint // conflicts across various systems (ie. solo and multiplayer). - importableScore.OnlineID = -1; + score.ScoreInfo.OnlineID = -1; - await scoreManager.Import(importableScore, replayReader).ConfigureAwait(false); + var imported = await scoreManager.Import(score.ScoreInfo, replayReader).ConfigureAwait(false); + + // detach post-import as we want to keep using the score for display in results. + score.ScoreInfo = imported.PerformRead(s => s.Detach()); } /// From 7e9b5dd1506cf0f09cf4bd42a4c648dea244df20 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 14 Jan 2022 18:06:19 +0900 Subject: [PATCH 372/996] Add audio feedback for host change in multiplayer --- .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 2 + .../Multiplayer/MultiplayerRoomSounds.cs | 66 +++++++++++++++++++ .../Participants/ParticipantsList.cs | 31 --------- 3 files changed, 68 insertions(+), 31 deletions(-) create mode 100644 osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomSounds.cs diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index c31239616c..2d5225639f 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -21,6 +21,7 @@ using osu.Game.Overlays.Mods; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Screens.OnlinePlay.Match.Components; +using osu.Game.Screens.OnlinePlay.Multiplayer; namespace osu.Game.Screens.OnlinePlay.Match { @@ -101,6 +102,7 @@ namespace osu.Game.Screens.OnlinePlay.Match InternalChildren = new Drawable[] { beatmapAvailabilityTracker, + new MultiplayerRoomSounds(), new GridContainer { RelativeSizeAxes = Axes.Both, diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomSounds.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomSounds.cs new file mode 100644 index 0000000000..42e3716e5a --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomSounds.cs @@ -0,0 +1,66 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; +using osu.Framework.Bindables; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.Multiplayer; + +namespace osu.Game.Screens.OnlinePlay.Multiplayer +{ + public class MultiplayerRoomSounds : MultiplayerRoomComposite + { + private Sample hostChangedSample; + private Sample userJoinedSample; + private Sample userLeftSample; + private Sample userKickedSample; + + [BackgroundDependencyLoader] + private void load(AudioManager audio) + { + hostChangedSample = audio.Samples.Get(@"Multiplayer/host-changed"); + userJoinedSample = audio.Samples.Get(@"Multiplayer/player-joined"); + userLeftSample = audio.Samples.Get(@"Multiplayer/player-left"); + userKickedSample = audio.Samples.Get(@"Multiplayer/player-kicked"); + + Host.ValueChanged += hostChanged; + } + + private void hostChanged(ValueChangedEvent value) + { + if (value.OldValue == null) return; + + hostChangedSample?.Play(); + } + + protected override void UserJoined(MultiplayerRoomUser user) + { + base.UserJoined(user); + + userJoinedSample?.Play(); + } + + protected override void UserLeft(MultiplayerRoomUser user) + { + base.UserLeft(user); + + userLeftSample?.Play(); + } + + protected override void UserKicked(MultiplayerRoomUser user) + { + base.UserKicked(user); + + userKickedSample?.Play(); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + Host.ValueChanged -= hostChanged; + } + } +} diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantsList.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantsList.cs index d36c556fac..fe40a4bfe6 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantsList.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantsList.cs @@ -4,12 +4,10 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Audio; -using osu.Framework.Audio.Sample; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; -using osu.Game.Online.Multiplayer; using osuTK; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants @@ -18,10 +16,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants { private FillFlowContainer panels; - private Sample userJoinSample; - private Sample userLeftSample; - private Sample userKickedSample; - [BackgroundDependencyLoader] private void load(AudioManager audio) { @@ -41,31 +35,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants } } }; - - userJoinSample = audio.Samples.Get(@"Multiplayer/player-joined"); - userLeftSample = audio.Samples.Get(@"Multiplayer/player-left"); - userKickedSample = audio.Samples.Get(@"Multiplayer/player-kicked"); - } - - protected override void UserJoined(MultiplayerRoomUser user) - { - base.UserJoined(user); - - userJoinSample?.Play(); - } - - protected override void UserLeft(MultiplayerRoomUser user) - { - base.UserLeft(user); - - userLeftSample?.Play(); - } - - protected override void UserKicked(MultiplayerRoomUser user) - { - base.UserKicked(user); - - userKickedSample?.Play(); } protected override void OnRoomUpdated() From 34dbde6023512d654bc4a4ad823563165650b23f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 14 Jan 2022 18:22:52 +0900 Subject: [PATCH 373/996] Only copy across `Hash` and `ID` so statistics aren't lost --- osu.Game/Screens/Play/Player.cs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index ff3fea51cf..4d3201cd27 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -18,7 +18,6 @@ using osu.Framework.Screens; using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Configuration; -using osu.Game.Database; using osu.Game.Graphics.Containers; using osu.Game.IO.Archives; using osu.Game.Online.API; @@ -1039,17 +1038,24 @@ namespace osu.Game.Screens.Play 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. + var importableScore = score.ScoreInfo.DeepClone(); + // For the time being, online ID responses are not really useful for anything. // In addition, the IDs provided via new (lazer) endpoints are based on a different autoincrement from legacy (stable) scores. // // Until we better define the server-side logic behind this, let's not store the online ID to avoid potential unique constraint // conflicts across various systems (ie. solo and multiplayer). - score.ScoreInfo.OnlineID = -1; + importableScore.OnlineID = -1; - var imported = await scoreManager.Import(score.ScoreInfo, replayReader).ConfigureAwait(false); + var imported = await scoreManager.Import(importableScore, replayReader).ConfigureAwait(false); - // detach post-import as we want to keep using the score for display in results. - score.ScoreInfo = imported.PerformRead(s => s.Detach()); + imported.PerformRead(s => + { + // because of the clone above, it's required that we copy back the post-import hash/ID to use for availability matching. + score.ScoreInfo.Hash = s.Hash; + score.ScoreInfo.ID = s.ID; + }); } /// From 7acd1b545f748a07703a85eeeba7230c0eb9ded1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 14 Jan 2022 19:55:09 +0900 Subject: [PATCH 374/996] Remove unnecessary `Live` conversion in `IntroScreen` (handled by `GetWorkingBeatmap`) --- 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 0f5dbb2842..265ec41f96 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -109,7 +109,7 @@ namespace osu.Game.Screens.Menu if (s.Beatmaps.Count == 0) return; - initialBeatmap = beatmaps.GetWorkingBeatmap(s.Beatmaps[0].ToLive(realmContextFactory)); + initialBeatmap = beatmaps.GetWorkingBeatmap(s.Beatmaps[0]); }); } } From 3a95425a9eeb2a14bd95411129baf30880a6ab10 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 14 Jan 2022 19:55:49 +0900 Subject: [PATCH 375/996] Remove left-over methods in `MusicController` --- osu.Game/Overlays/MusicController.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 44c2916f12..70f8332295 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -134,14 +134,6 @@ namespace osu.Game.Overlays /// public bool TrackLoaded => CurrentTrack.TrackLoaded; - private void beatmapUpdated(BeatmapSetInfo set) => Schedule(() => - { - beatmapSets.Remove(set); - beatmapSets.Add(set); - }); - - private void beatmapRemoved(BeatmapSetInfo set) => Schedule(() => beatmapSets.RemoveAll(s => s.ID == set.ID)); - private ScheduledDelegate seekDelegate; public void SeekTo(double position) From a59dcccab7a4134081dfbc7351eab8b015981684 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 14 Jan 2022 19:59:21 +0900 Subject: [PATCH 376/996] Add local `ContextFactory` caching to all remaining test scenes that create local managers --- .../Visual/Collections/TestSceneManageCollectionsDialog.cs | 1 + .../Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs | 1 + osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs | 1 + .../Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs | 1 + .../Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs | 2 ++ .../Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs | 1 + .../Visual/Multiplayer/TestSceneMultiplayerQueueList.cs | 1 + .../Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs | 2 ++ .../Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs | 2 ++ .../Visual/Multiplayer/TestScenePlaylistsSongSelect.cs | 1 + osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs | 1 + .../Visual/Playlists/TestScenePlaylistsRoomCreation.cs | 1 + osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs | 1 + osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs | 1 + .../Visual/UserInterface/TestSceneDeleteLocalScore.cs | 1 + 15 files changed, 18 insertions(+) diff --git a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs index d2b0f7324b..18572ac211 100644 --- a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs +++ b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs @@ -38,6 +38,7 @@ namespace osu.Game.Tests.Visual.Collections { Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, Audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(ContextFactory); beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index 5bad36c3dd..99c867b014 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -45,6 +45,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(ContextFactory); } [Test] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index d55ca0b578..373b165acc 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -63,6 +63,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, API, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(ContextFactory); } public override void SetUpSteps() diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index c6fb418a5b..181b0c71f2 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -44,6 +44,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(ContextFactory); beatmaps = new List(); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index 98b14fbddb..012a2fd960 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -40,6 +40,8 @@ namespace osu.Game.Tests.Visual.Multiplayer { Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(ContextFactory); + beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); importedSet = beatmaps.GetAllUsableBeatmapSets().First(); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs index 813b62176b..d547b42891 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs @@ -35,6 +35,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(ContextFactory); } [SetUp] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs index b5d31c7ae9..965b142ed7 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs @@ -40,6 +40,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, API, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(ContextFactory); } public override void SetUpSteps() diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs index 0b2ab1aef3..1c346e09d5 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs @@ -42,6 +42,8 @@ namespace osu.Game.Tests.Visual.Multiplayer { Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(ContextFactory); + beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs index 6b1001f6b7..221732910b 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs @@ -43,6 +43,8 @@ namespace osu.Game.Tests.Visual.Multiplayer { Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(ContextFactory); + beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs index 08fcac125d..0b0006e437 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs @@ -36,6 +36,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(ContextFactory); var beatmapSet = TestResources.CreateTestBeatmapSetInfo(); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs index 97d17ada71..39cde0ad87 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs @@ -44,6 +44,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(ContextFactory); } public override void SetUpSteps() diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs index 2180f0f717..dfcf657218 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs @@ -41,6 +41,7 @@ namespace osu.Game.Tests.Visual.Playlists { Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, API, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(ContextFactory); } [SetUpSteps] diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 78df4d8d98..a314ed8a6e 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -46,6 +46,7 @@ namespace osu.Game.Tests.Visual.SongSelect dependencies.Cache(rulesetStore = new RulesetStore(ContextFactory)); dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesetStore, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default)); dependencies.Cache(scoreManager = new ScoreManager(dependencies.Get(), () => beatmapManager, LocalStorage, dependencies.Get(), Scheduler)); + Dependencies.Cache(ContextFactory); return dependencies; } diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs index 1ee59eccc7..ca8e9d2eff 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs @@ -38,6 +38,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, Audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(ContextFactory); beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index 63df6a6390..02746ee8e8 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -90,6 +90,7 @@ namespace osu.Game.Tests.Visual.UserInterface dependencies.Cache(rulesetStore = new RulesetStore(ContextFactory)); dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesetStore, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default)); dependencies.Cache(scoreManager = new ScoreManager(dependencies.Get(), () => beatmapManager, LocalStorage, dependencies.Get(), Scheduler)); + Dependencies.Cache(ContextFactory); var imported = beatmapManager.Import(new ImportTask(TestResources.GetQuickTestBeatmapForImport())).GetResultSafely(); From 289ae7c72f18dd22f93925437b44acb5347550d1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 14 Jan 2022 21:39:42 +0900 Subject: [PATCH 377/996] Update one more mismatched test implementation --- osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index a314ed8a6e..9ae15c387a 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -45,7 +45,7 @@ namespace osu.Game.Tests.Visual.SongSelect dependencies.Cache(rulesetStore = new RulesetStore(ContextFactory)); dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesetStore, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default)); - dependencies.Cache(scoreManager = new ScoreManager(dependencies.Get(), () => beatmapManager, LocalStorage, dependencies.Get(), Scheduler)); + dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, dependencies.Get(), Scheduler)); Dependencies.Cache(ContextFactory); return dependencies; From 9900a3f408b084dedca6cee885bebf7e2ac39073 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 14 Jan 2022 21:40:35 +0900 Subject: [PATCH 378/996] Remove outdated comment --- osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index d7e3951ac2..0f5bea10e8 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -429,7 +429,6 @@ namespace osu.Game.Tests.Visual.SongSelect for (int i = 0; i < 20; i++) { - // index + 1 because we are using OnlineID which should never be zero. var set = TestResources.CreateTestBeatmapSetInfo(); // only need to set the first as they are a shared reference. From e558fd69d22fcf17dffce8e4b2d90b5c7042a513 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 14 Jan 2022 23:26:29 +0900 Subject: [PATCH 379/996] Remove unnecessary null check and associated comment --- osu.Game/Tests/Visual/RateAdjustedBeatmapTestScene.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/RateAdjustedBeatmapTestScene.cs b/osu.Game/Tests/Visual/RateAdjustedBeatmapTestScene.cs index 5bac2635f5..1034f208a9 100644 --- a/osu.Game/Tests/Visual/RateAdjustedBeatmapTestScene.cs +++ b/osu.Game/Tests/Visual/RateAdjustedBeatmapTestScene.cs @@ -12,7 +12,7 @@ namespace osu.Game.Tests.Visual { base.Update(); - if (Beatmap.Value.TrackLoaded && Beatmap.Value.Track != null) // null check... wasn't required until now? + if (Beatmap.Value.TrackLoaded) { // note that this will override any mod rate application Beatmap.Value.Track.Tempo.Value = Clock.Rate; From 8d4a3cc56959aa0e07425bef9dd7b159a4c58d32 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 14 Jan 2022 23:27:33 +0900 Subject: [PATCH 380/996] Remove pointless `Metadata` set --- osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs index b6a08dd2ab..c5f56cae9e 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs @@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestEmptyLegacyBeatmapSkinFallsBack() { - CreateSkinTest(DefaultSkin.CreateInfo(), () => new LegacyBeatmapSkin(new BeatmapInfo { Metadata = new BeatmapMetadata() }, null, null)); + CreateSkinTest(DefaultSkin.CreateInfo(), () => new LegacyBeatmapSkin(new BeatmapInfo(), null, null)); AddUntilStep("wait for hud load", () => Player.ChildrenOfType().All(c => c.ComponentsLoaded)); AddAssert("hud from default skin", () => AssertComponentsFromExpectedSource(SkinnableTarget.MainHUDComponents, skinManager.CurrentSkin.Value)); } From 2f2c498477f4038315fcbcd0029bbc013ace3a60 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 14 Jan 2022 23:31:42 +0900 Subject: [PATCH 381/996] Fix importer not considering that some EF beatmaps have no local metadata --- osu.Game/Database/EFToRealmMigrator.cs | 44 +++++++++++++++----------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index ad0f2c5982..7683accc5c 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -48,6 +48,7 @@ namespace osu.Game.Database .Include(s => s.Beatmaps).ThenInclude(b => b.Metadata) .Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty) .Include(s => s.Files).ThenInclude(f => f.FileInfo) + .Include(s => s.Metadata) .ToList(); // previous entries in EF are removed post migration. @@ -105,24 +106,7 @@ namespace osu.Game.Database Bookmarks = beatmap.Bookmarks, Ruleset = realm.Find(beatmap.RulesetInfo.ShortName), Difficulty = new BeatmapDifficulty(beatmap.BaseDifficulty), - Metadata = new BeatmapMetadata - { - Title = beatmap.Metadata.Title, - TitleUnicode = beatmap.Metadata.TitleUnicode, - Artist = beatmap.Metadata.Artist, - ArtistUnicode = beatmap.Metadata.ArtistUnicode, - Author = new RealmUser - { - OnlineID = beatmap.Metadata.Author.Id, - Username = beatmap.Metadata.Author.Username, - }, - Source = beatmap.Metadata.Source, - Tags = beatmap.Metadata.Tags, - PreviewTime = beatmap.Metadata.PreviewTime, - AudioFile = beatmap.Metadata.AudioFile, - BackgroundFile = beatmap.Metadata.BackgroundFile, - AuthorString = beatmap.Metadata.AuthorString, - }, + Metadata = getBestMetadata(beatmap.Metadata, beatmapSet.Metadata), BeatmapSet = realmBeatmapSet, }; @@ -140,6 +124,30 @@ namespace osu.Game.Database } } + private BeatmapMetadata getBestMetadata(EFBeatmapMetadata? beatmapMetadata, EFBeatmapMetadata? beatmapSetMetadata) + { + var metadata = beatmapMetadata ?? beatmapSetMetadata ?? new EFBeatmapMetadata(); + + return new BeatmapMetadata + { + Title = metadata.Title, + TitleUnicode = metadata.TitleUnicode, + Artist = metadata.Artist, + ArtistUnicode = metadata.ArtistUnicode, + Author = new RealmUser + { + OnlineID = metadata.Author.Id, + Username = metadata.Author.Username, + }, + Source = metadata.Source, + Tags = metadata.Tags, + PreviewTime = metadata.PreviewTime, + AudioFile = metadata.AudioFile, + BackgroundFile = metadata.BackgroundFile, + AuthorString = metadata.AuthorString, + }; + } + private void migrateScores(DatabaseWriteUsage db) { // can be removed 20220730. From 2984f2f6c4e7abcf75cff17c7cc92b081e48ccb6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 15 Jan 2022 01:15:09 +0900 Subject: [PATCH 382/996] Fix custom keybindings not working due to incorrect use of `IsManaged` flag --- .../Bindings/DatabasedKeyBindingContainer.cs | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs index 9482f1c0d8..03b069d431 100644 --- a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs +++ b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs @@ -78,19 +78,20 @@ namespace osu.Game.Input.Bindings { var defaults = DefaultKeyBindings.ToList(); - if (ruleset != null && !ruleset.IsManaged) - // some tests instantiate a ruleset which is not present in the database. - // in these cases we still want key bindings to work, but matching to database instances would result in none being present, - // so let's populate the defaults directly. + List newBindings = realmKeyBindings.Detach() + // this ordering is important to ensure that we read entries from the database in the order + // enforced by DefaultKeyBindings. allow for song select to handle actions that may otherwise + // have been eaten by the music controller due to query order. + .OrderBy(b => defaults.FindIndex(d => (int)d.Action == b.ActionInt)).ToList(); + + // In the case no bindings were found in the database, presume this usage is for a non-databased ruleset. + // This actually should never be required and can be removed if it is ever deemed to cause a problem. + // See https://github.com/ppy/osu/issues/8805 for original reasoning, which is no longer valid as we use ShortName + // for lookups these days. + if (newBindings.Count == 0) KeyBindings = defaults; else - { - KeyBindings = realmKeyBindings.Detach() - // this ordering is important to ensure that we read entries from the database in the order - // enforced by DefaultKeyBindings. allow for song select to handle actions that may otherwise - // have been eaten by the music controller due to query order. - .OrderBy(b => defaults.FindIndex(d => (int)d.Action == b.ActionInt)).ToList(); - } + KeyBindings = newBindings; } } } From 9af9155e66beecc6c344f87f9563747f64e45e91 Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Fri, 14 Jan 2022 17:37:58 +0100 Subject: [PATCH 383/996] Fix `osu.Game.Tests.Android` not building --- osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index 666cbf02b5..8a0ed2e108 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs @@ -203,7 +203,7 @@ namespace osu.Game.Tests.Visual.Ranking { DelayedFetchResultsScreen screen = null; - var tcs = new TaskCompletionSource(); + var tcs = new TaskCompletionSource(); AddStep("load results", () => Child = new TestResultsContainer(screen = new DelayedFetchResultsScreen(TestResources.CreateTestScoreInfo(), tcs.Task))); @@ -218,7 +218,7 @@ namespace osu.Game.Tests.Visual.Ranking AddAssert("no fetch yet", () => !screen.FetchCompleted); - AddStep("allow fetch", () => tcs.SetResult()); + AddStep("allow fetch", () => tcs.SetResult(true)); AddUntilStep("wait for fetch", () => screen.FetchCompleted); AddAssert("expanded panel still on screen", () => this.ChildrenOfType().Single(p => p.State == PanelState.Expanded).ScreenSpaceDrawQuad.TopLeft.X > 0); From 19467e58c1b65148984c2e38f467bfd240490bb1 Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Sat, 15 Jan 2022 01:06:39 +0100 Subject: [PATCH 384/996] Remove unused params from BDL methods --- .../osu.Game.Rulesets.EmptyFreeform.Tests/TestSceneOsuGame.cs | 3 +-- .../osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuGame.cs | 3 +-- .../TestSceneOsuGame.cs | 3 +-- .../osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuGame.cs | 3 +-- .../osu.Game.Rulesets.Pippidon/UI/PippidonPlayfield.cs | 3 +-- osu.Desktop/Security/ElevatedPrivilegesChecker.cs | 2 +- .../Edit/Blueprints/HoldNoteSelectionBlueprint.cs | 3 +-- .../Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs | 3 +-- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs | 3 +-- .../Skinning/Default/SpinnerBackgroundLayer.cs | 4 +--- .../Skinning/Legacy/LegacyCursorParticles.cs | 2 +- osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs | 2 +- .../Skinning/Default/CentreHitCirclePiece.cs | 3 +-- osu.Game.Rulesets.Taiko/Skinning/Default/RimHitCirclePiece.cs | 3 +-- osu.Game.Rulesets.Taiko/UI/DrawableTaikoMascot.cs | 3 +-- .../Visual/Beatmaps/TestSceneBeatmapCardDifficultyList.cs | 3 +-- osu.Game.Tests/Visual/Gameplay/TestSceneAllRulesetPlayers.cs | 2 +- .../TestSceneUpdateableBeatmapBackgroundSprite.cs | 3 +-- osu.Game.Tournament.Tests/TestSceneTournamentSceneManager.cs | 3 +-- osu.Game.Tournament/Components/DrawableTeamTitle.cs | 3 +-- osu.Game.Tournament/Components/DrawableTournamentTeam.cs | 3 +-- osu.Game.Tournament/Components/TournamentBeatmapPanel.cs | 3 +-- osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs | 3 +-- osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs | 3 +-- .../Gameplay/Components/TournamentMatchScoreDisplay.cs | 3 +-- osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs | 3 +-- .../Screens/Ladder/Components/DrawableMatchTeam.cs | 2 +- osu.Game.Tournament/Screens/Ladder/LadderScreen.cs | 4 +--- osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs | 3 +-- osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs | 3 +-- osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs | 3 +-- osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs | 3 +-- osu.Game.Tournament/TournamentSceneManager.cs | 4 +--- osu.Game/Collections/DrawableCollectionListItem.cs | 2 +- osu.Game/Graphics/Sprites/LogoAnimation.cs | 3 +-- osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs | 3 +-- .../Graphics/UserInterface/HoverSampleDebounceComponent.cs | 3 +-- osu.Game/Graphics/UserInterface/OsuContextMenu.cs | 3 +-- osu.Game/Graphics/UserInterface/OsuContextMenuSamples.cs | 2 +- osu.Game/Overlays/AccountCreation/ScreenEntry.cs | 2 +- osu.Game/Overlays/AccountCreationOverlay.cs | 3 +-- osu.Game/Overlays/Changelog/ChangelogBuild.cs | 2 +- osu.Game/Overlays/ChangelogOverlay.cs | 3 +-- osu.Game/Overlays/Comments/CommentsContainer.cs | 2 +- osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs | 3 +-- .../Settings/Sections/DebugSettings/MemorySettings.cs | 3 +-- osu.Game/Overlays/Settings/SettingsFooter.cs | 2 +- osu.Game/Overlays/Settings/SettingsSection.cs | 2 +- osu.Game/Rulesets/UI/FrameStabilityContainer.cs | 2 +- osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs | 3 +-- .../Edit/Compose/Components/Timeline/SamplePointPiece.cs | 3 +-- .../Edit/Compose/Components/Timeline/TimingPointPiece.cs | 3 +-- osu.Game/Screens/Edit/EditorRoundedScreenSettingsSection.cs | 3 +-- osu.Game/Screens/Edit/Verify/VisibilitySection.cs | 3 +-- osu.Game/Screens/Import/FileImportScreen.cs | 3 +-- osu.Game/Screens/Menu/ButtonSystem.cs | 3 +-- osu.Game/Screens/Menu/IntroScreen.cs | 3 +-- osu.Game/Screens/Menu/IntroTriangles.cs | 2 +- osu.Game/Screens/Menu/MainMenu.cs | 2 +- osu.Game/Screens/OsuScreen.cs | 2 +- osu.Game/Screens/Play/EpilepsyWarning.cs | 4 +--- osu.Game/Screens/Play/HUD/HoldForMenuButton.cs | 2 +- osu.Game/Screens/Play/HUD/UnstableRateCounter.cs | 3 +-- osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs | 3 +-- osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs | 3 +-- osu.Game/Tests/Visual/SkinnableTestScene.cs | 2 +- osu.Game/Users/Drawables/ClickableAvatar.cs | 3 +-- 67 files changed, 67 insertions(+), 120 deletions(-) diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/TestSceneOsuGame.cs b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/TestSceneOsuGame.cs index 536fdfc6df..5973db908c 100644 --- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/TestSceneOsuGame.cs +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/TestSceneOsuGame.cs @@ -4,7 +4,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; -using osu.Framework.Platform; using osu.Game.Tests.Visual; using osuTK.Graphics; @@ -13,7 +12,7 @@ namespace osu.Game.Rulesets.EmptyFreeform.Tests public class TestSceneOsuGame : OsuTestScene { [BackgroundDependencyLoader] - private void load(GameHost host, OsuGameBase gameBase) + private void load() { Children = new Drawable[] { diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuGame.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuGame.cs index 3cdf44e6f1..b75a5ec187 100644 --- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuGame.cs +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuGame.cs @@ -4,7 +4,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; -using osu.Framework.Platform; using osu.Game.Tests.Visual; using osuTK.Graphics; @@ -13,7 +12,7 @@ namespace osu.Game.Rulesets.Pippidon.Tests public class TestSceneOsuGame : OsuTestScene { [BackgroundDependencyLoader] - private void load(GameHost host, OsuGameBase gameBase) + private void load() { Children = new Drawable[] { diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/TestSceneOsuGame.cs b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/TestSceneOsuGame.cs index 4d3f5086d9..ffe921b54c 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/TestSceneOsuGame.cs +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/TestSceneOsuGame.cs @@ -4,7 +4,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; -using osu.Framework.Platform; using osu.Game.Tests.Visual; using osuTK.Graphics; @@ -13,7 +12,7 @@ namespace osu.Game.Rulesets.EmptyScrolling.Tests public class TestSceneOsuGame : OsuTestScene { [BackgroundDependencyLoader] - private void load(GameHost host, OsuGameBase gameBase) + private void load() { Children = new Drawable[] { diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuGame.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuGame.cs index 3cdf44e6f1..b75a5ec187 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuGame.cs +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuGame.cs @@ -4,7 +4,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; -using osu.Framework.Platform; using osu.Game.Tests.Visual; using osuTK.Graphics; @@ -13,7 +12,7 @@ namespace osu.Game.Rulesets.Pippidon.Tests public class TestSceneOsuGame : OsuTestScene { [BackgroundDependencyLoader] - private void load(GameHost host, OsuGameBase gameBase) + private void load() { Children = new Drawable[] { diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/UI/PippidonPlayfield.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/UI/PippidonPlayfield.cs index 0e50030162..ab8c6bb2e9 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/UI/PippidonPlayfield.cs +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/UI/PippidonPlayfield.cs @@ -7,7 +7,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Textures; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -28,7 +27,7 @@ namespace osu.Game.Rulesets.Pippidon.UI private PippidonCharacter pippidon; [BackgroundDependencyLoader] - private void load(TextureStore textures) + private void load() { AddRangeInternal(new Drawable[] { diff --git a/osu.Desktop/Security/ElevatedPrivilegesChecker.cs b/osu.Desktop/Security/ElevatedPrivilegesChecker.cs index 62ea3e0399..8f3ad853dc 100644 --- a/osu.Desktop/Security/ElevatedPrivilegesChecker.cs +++ b/osu.Desktop/Security/ElevatedPrivilegesChecker.cs @@ -73,7 +73,7 @@ namespace osu.Desktop.Security } [BackgroundDependencyLoader] - private void load(OsuColour colours, NotificationOverlay notificationOverlay) + private void load(OsuColour colours) { Icon = FontAwesome.Solid.ShieldAlt; IconBackground.Colour = colours.YellowDark; diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs index 5259fcbd5f..35889aea0c 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Rulesets.Mania.Edit.Blueprints.Components; using osu.Game.Rulesets.Mania.Objects; -using osu.Game.Rulesets.UI.Scrolling; using osuTK; namespace osu.Game.Rulesets.Mania.Edit.Blueprints @@ -28,7 +27,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints } [BackgroundDependencyLoader] - private void load(IScrollingInfo scrollingInfo) + private void load() { InternalChildren = new Drawable[] { diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 07b6a1bdc2..b868c9a7ee 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics; using osu.Framework.Input; using osu.Framework.Input.Events; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Graphics; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; @@ -50,7 +49,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load() { InternalChildren = new Drawable[] { diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index ec87d3bfdf..c6db02ee02 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -10,7 +10,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Audio; -using osu.Game.Graphics; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; @@ -69,7 +68,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load() { Origin = Anchor.Centre; RelativeSizeAxes = Axes.Both; diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerBackgroundLayer.cs b/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerBackgroundLayer.cs index f8a6e1d3c9..a1184a15cd 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerBackgroundLayer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerBackgroundLayer.cs @@ -3,15 +3,13 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Game.Graphics; -using osu.Game.Rulesets.Objects.Drawables; namespace osu.Game.Rulesets.Osu.Skinning.Default { public class SpinnerBackgroundLayer : SpinnerFill { [BackgroundDependencyLoader] - private void load(OsuColour colours, DrawableHitObject drawableHitObject) + private void load() { Disc.Alpha = 0; Anchor = Anchor.Centre; diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs index 611ddd08eb..b511444c44 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy private GameplayState gameplayState { get; set; } [BackgroundDependencyLoader] - private void load(ISkinSource skin, OsuColour colours) + private void load(ISkinSource skin) { var texture = skin.GetTexture("star2"); var starBreakAdditive = skin.GetConfig(OsuSkinColour.StarBreakAdditive)?.Value ?? new Color4(255, 182, 193, 255); diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs index d1d9ee9f4d..b60ea5da21 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs @@ -58,7 +58,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor private OsuConfigManager config { get; set; } [BackgroundDependencyLoader(true)] - private void load(OsuConfigManager config, OsuRulesetConfigManager rulesetConfig) + private void load(OsuRulesetConfigManager rulesetConfig) { rulesetConfig?.BindWith(OsuRulesetSetting.ShowCursorTrail, showTrail); } diff --git a/osu.Game.Rulesets.Taiko/Skinning/Default/CentreHitCirclePiece.cs b/osu.Game.Rulesets.Taiko/Skinning/Default/CentreHitCirclePiece.cs index 455b2fc596..25f895708f 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Default/CentreHitCirclePiece.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Default/CentreHitCirclePiece.cs @@ -5,7 +5,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; using osu.Game.Rulesets.Taiko.Objects; using osuTK; @@ -19,7 +18,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Default } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load() { AccentColour = Hit.COLOUR_CENTRE; } diff --git a/osu.Game.Rulesets.Taiko/Skinning/Default/RimHitCirclePiece.cs b/osu.Game.Rulesets.Taiko/Skinning/Default/RimHitCirclePiece.cs index bd21d511b1..c6165495d8 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Default/RimHitCirclePiece.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Default/RimHitCirclePiece.cs @@ -5,7 +5,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; using osu.Game.Rulesets.Taiko.Objects; using osuTK; using osuTK.Graphics; @@ -20,7 +19,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Default } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load() { AccentColour = Hit.COLOUR_RIM; } diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoMascot.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoMascot.cs index e1063e1071..7ba2618a63 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoMascot.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoMascot.cs @@ -7,7 +7,6 @@ using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Textures; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.Containers; using osu.Game.Rulesets.Judgements; @@ -39,7 +38,7 @@ namespace osu.Game.Rulesets.Taiko.UI } [BackgroundDependencyLoader(true)] - private void load(TextureStore textures, GameplayState gameplayState) + private void load(GameplayState gameplayState) { InternalChildren = new[] { diff --git a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardDifficultyList.cs b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardDifficultyList.cs index aec75884d6..e6fb4372ff 100644 --- a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardDifficultyList.cs +++ b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardDifficultyList.cs @@ -6,7 +6,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps.Drawables.Cards; -using osu.Game.Graphics; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; @@ -18,7 +17,7 @@ namespace osu.Game.Tests.Visual.Beatmaps private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load() { var beatmapSet = new APIBeatmapSet { diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneAllRulesetPlayers.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneAllRulesetPlayers.cs index c5ab3974a4..e10ef57a25 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneAllRulesetPlayers.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneAllRulesetPlayers.cs @@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual.Gameplay protected OsuConfigManager Config { get; private set; } [BackgroundDependencyLoader] - private void load(RulesetStore rulesets) + private void load() { Dependencies.Cache(Config = new OsuConfigManager(LocalStorage)); Config.GetBindable(OsuSetting.DimLevel).Value = 1.0; diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs index 6fe1ccc037..736df7dec1 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs @@ -14,7 +14,6 @@ using osu.Game.Graphics.Containers; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; -using osu.Game.Rulesets; using osu.Game.Tests.Beatmaps.IO; using osuTK; @@ -28,7 +27,7 @@ namespace osu.Game.Tests.Visual.UserInterface private IAPIProvider api; [BackgroundDependencyLoader] - private void load(OsuGameBase osu, IAPIProvider api, RulesetStore rulesets) + private void load(OsuGameBase osu, IAPIProvider api) { this.api = api; diff --git a/osu.Game.Tournament.Tests/TestSceneTournamentSceneManager.cs b/osu.Game.Tournament.Tests/TestSceneTournamentSceneManager.cs index 4d134ce4af..53591da07b 100644 --- a/osu.Game.Tournament.Tests/TestSceneTournamentSceneManager.cs +++ b/osu.Game.Tournament.Tests/TestSceneTournamentSceneManager.cs @@ -2,14 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Platform; namespace osu.Game.Tournament.Tests { public class TestSceneTournamentSceneManager : TournamentTestScene { [BackgroundDependencyLoader] - private void load(Storage storage) + private void load() { Add(new TournamentSceneManager()); } diff --git a/osu.Game.Tournament/Components/DrawableTeamTitle.cs b/osu.Game.Tournament/Components/DrawableTeamTitle.cs index 5aac37259f..6732eb152f 100644 --- a/osu.Game.Tournament/Components/DrawableTeamTitle.cs +++ b/osu.Game.Tournament/Components/DrawableTeamTitle.cs @@ -4,7 +4,6 @@ using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Graphics.Textures; using osu.Game.Tournament.Models; namespace osu.Game.Tournament.Components @@ -22,7 +21,7 @@ namespace osu.Game.Tournament.Components } [BackgroundDependencyLoader] - private void load(TextureStore textures) + private void load() { if (team == null) return; diff --git a/osu.Game.Tournament/Components/DrawableTournamentTeam.cs b/osu.Game.Tournament/Components/DrawableTournamentTeam.cs index b9442a67f5..367e447947 100644 --- a/osu.Game.Tournament/Components/DrawableTournamentTeam.cs +++ b/osu.Game.Tournament/Components/DrawableTournamentTeam.cs @@ -5,7 +5,6 @@ using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Textures; using osu.Game.Graphics; using osu.Game.Tournament.Models; @@ -33,7 +32,7 @@ namespace osu.Game.Tournament.Components } [BackgroundDependencyLoader] - private void load(TextureStore textures) + private void load() { if (Team == null) return; diff --git a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs index 364cccd076..4189f3ccb5 100644 --- a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs +++ b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs @@ -9,7 +9,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Textures; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; @@ -45,7 +44,7 @@ namespace osu.Game.Tournament.Components } [BackgroundDependencyLoader] - private void load(LadderInfo ladder, TextureStore textures) + private void load(LadderInfo ladder) { currentMatch.BindValueChanged(matchChanged); currentMatch.BindTo(ladder.CurrentMatch); diff --git a/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs index f6bc607447..5c12d83d1c 100644 --- a/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs @@ -12,7 +12,6 @@ using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.Settings; -using osu.Game.Rulesets; using osu.Game.Tournament.Components; using osu.Game.Tournament.Models; using osuTK; @@ -218,7 +217,7 @@ namespace osu.Game.Tournament.Screens.Editors } [BackgroundDependencyLoader] - private void load(RulesetStore rulesets) + private void load() { beatmapId.Value = Model.ID; beatmapId.BindValueChanged(id => diff --git a/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs index 5d2fddffd9..5cdfe7dc08 100644 --- a/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs @@ -12,7 +12,6 @@ using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.Settings; -using osu.Game.Rulesets; using osu.Game.Tournament.Components; using osu.Game.Tournament.Models; using osuTK; @@ -220,7 +219,7 @@ namespace osu.Game.Tournament.Screens.Editors } [BackgroundDependencyLoader] - private void load(RulesetStore rulesets) + private void load() { beatmapId.Value = Model.ID; beatmapId.BindValueChanged(id => diff --git a/osu.Game.Tournament/Screens/Gameplay/Components/TournamentMatchScoreDisplay.cs b/osu.Game.Tournament/Screens/Gameplay/Components/TournamentMatchScoreDisplay.cs index 813bed86ae..db15a46fc8 100644 --- a/osu.Game.Tournament/Screens/Gameplay/Components/TournamentMatchScoreDisplay.cs +++ b/osu.Game.Tournament/Screens/Gameplay/Components/TournamentMatchScoreDisplay.cs @@ -11,7 +11,6 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Tournament.IPC; -using osu.Game.Tournament.Models; using osuTK; namespace osu.Game.Tournament.Screens.Gameplay.Components @@ -91,7 +90,7 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components } [BackgroundDependencyLoader] - private void load(LadderInfo ladder, MatchIPCInfo ipc) + private void load(MatchIPCInfo ipc) { score1.BindValueChanged(_ => updateScores()); score1.BindTo(ipc.Score1); diff --git a/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs b/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs index 7e7c719152..f900dd7eac 100644 --- a/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs +++ b/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs @@ -6,7 +6,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Platform; using osu.Framework.Threading; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Settings; @@ -37,7 +36,7 @@ namespace osu.Game.Tournament.Screens.Gameplay private Drawable chroma; [BackgroundDependencyLoader] - private void load(LadderInfo ladder, MatchIPCInfo ipc, Storage storage) + private void load(LadderInfo ladder, MatchIPCInfo ipc) { this.ipc = ipc; diff --git a/osu.Game.Tournament/Screens/Ladder/Components/DrawableMatchTeam.cs b/osu.Game.Tournament/Screens/Ladder/Components/DrawableMatchTeam.cs index bb1e4d2eff..ea453a53ca 100644 --- a/osu.Game.Tournament/Screens/Ladder/Components/DrawableMatchTeam.cs +++ b/osu.Game.Tournament/Screens/Ladder/Components/DrawableMatchTeam.cs @@ -81,7 +81,7 @@ namespace osu.Game.Tournament.Screens.Ladder.Components } [BackgroundDependencyLoader(true)] - private void load(OsuColour colours, LadderEditorScreen ladderEditor) + private void load(LadderEditorScreen ladderEditor) { this.ladderEditor = ladderEditor; diff --git a/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs b/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs index 534c402f6c..ad6e304c80 100644 --- a/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs +++ b/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs @@ -9,8 +9,6 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Lines; -using osu.Framework.Platform; -using osu.Game.Graphics; using osu.Game.Tournament.Components; using osu.Game.Tournament.Models; using osu.Game.Tournament.Screens.Editors; @@ -30,7 +28,7 @@ namespace osu.Game.Tournament.Screens.Ladder protected Container Content; [BackgroundDependencyLoader] - private void load(OsuColour colours, Storage storage) + private void load() { normalPathColour = Color4Extensions.FromHex("#66D1FF"); losersPathColour = Color4Extensions.FromHex("#FFC700"); diff --git a/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs b/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs index e08be65465..84f38170ea 100644 --- a/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs +++ b/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs @@ -8,7 +8,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Platform; using osu.Game.Graphics; using osu.Game.Tournament.Components; using osu.Game.Tournament.Models; @@ -25,7 +24,7 @@ namespace osu.Game.Tournament.Screens.Schedule private LadderInfo ladder; [BackgroundDependencyLoader] - private void load(LadderInfo ladder, Storage storage) + private void load(LadderInfo ladder) { this.ladder = ladder; diff --git a/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs b/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs index cd74a75b10..0003e213e7 100644 --- a/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs +++ b/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; -using osu.Framework.Platform; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Tournament.Components; @@ -25,7 +24,7 @@ namespace osu.Game.Tournament.Screens.TeamIntro private readonly Bindable currentTeam = new Bindable(); [BackgroundDependencyLoader] - private void load(Storage storage) + private void load() { RelativeSizeAxes = Axes.Both; diff --git a/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs b/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs index 74957cbca5..ef6f0b32ff 100644 --- a/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs +++ b/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs @@ -5,7 +5,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Platform; using osu.Game.Tournament.Components; using osu.Game.Tournament.Models; using osuTK; @@ -17,7 +16,7 @@ namespace osu.Game.Tournament.Screens.TeamIntro private Container mainContainer; [BackgroundDependencyLoader] - private void load(Storage storage) + private void load() { RelativeSizeAxes = Axes.Both; diff --git a/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs b/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs index ebe2908b74..11db7bfad9 100644 --- a/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs +++ b/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs @@ -5,7 +5,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Platform; using osu.Game.Graphics; using osu.Game.Tournament.Components; using osu.Game.Tournament.Models; @@ -23,7 +22,7 @@ namespace osu.Game.Tournament.Screens.TeamWin private TourneyVideo redWinVideo; [BackgroundDependencyLoader] - private void load(LadderInfo ladder, Storage storage) + private void load() { RelativeSizeAxes = Axes.Both; diff --git a/osu.Game.Tournament/TournamentSceneManager.cs b/osu.Game.Tournament/TournamentSceneManager.cs index 914d1163ad..80a9c07cde 100644 --- a/osu.Game.Tournament/TournamentSceneManager.cs +++ b/osu.Game.Tournament/TournamentSceneManager.cs @@ -7,11 +7,9 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Platform; using osu.Framework.Threading; using osu.Game.Graphics; using osu.Game.Tournament.Components; -using osu.Game.Tournament.Models; using osu.Game.Tournament.Screens; using osu.Game.Tournament.Screens.Drawings; using osu.Game.Tournament.Screens.Editors; @@ -52,7 +50,7 @@ namespace osu.Game.Tournament } [BackgroundDependencyLoader] - private void load(LadderInfo ladder, Storage storage) + private void load() { InternalChildren = new Drawable[] { diff --git a/osu.Game/Collections/DrawableCollectionListItem.cs b/osu.Game/Collections/DrawableCollectionListItem.cs index 909595bd1c..c4cb040b52 100644 --- a/osu.Game/Collections/DrawableCollectionListItem.cs +++ b/osu.Game/Collections/DrawableCollectionListItem.cs @@ -80,7 +80,7 @@ namespace osu.Game.Collections } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load() { Children = new Drawable[] { diff --git a/osu.Game/Graphics/Sprites/LogoAnimation.cs b/osu.Game/Graphics/Sprites/LogoAnimation.cs index b1383065fe..36fcd39b54 100644 --- a/osu.Game/Graphics/Sprites/LogoAnimation.cs +++ b/osu.Game/Graphics/Sprites/LogoAnimation.cs @@ -7,14 +7,13 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.OpenGL.Vertices; using osu.Framework.Graphics.Shaders; using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; namespace osu.Game.Graphics.Sprites { public class LogoAnimation : Sprite { [BackgroundDependencyLoader] - private void load(ShaderManager shaders, TextureStore textures) + private void load(ShaderManager shaders) { TextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, @"LogoAnimation"); RoundedTextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, @"LogoAnimation"); // Masking isn't supported for now diff --git a/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs b/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs index fea84998cf..4267b82bb7 100644 --- a/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs +++ b/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Audio; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -30,7 +29,7 @@ namespace osu.Game.Graphics.UserInterface } [BackgroundDependencyLoader] - private void load(AudioManager audio) + private void load() { BackgroundColour = Color4.Transparent; BackgroundColourHover = Color4Extensions.FromHex(@"172023"); diff --git a/osu.Game/Graphics/UserInterface/HoverSampleDebounceComponent.cs b/osu.Game/Graphics/UserInterface/HoverSampleDebounceComponent.cs index 1fd03a34e7..34ab7626c9 100644 --- a/osu.Game/Graphics/UserInterface/HoverSampleDebounceComponent.cs +++ b/osu.Game/Graphics/UserInterface/HoverSampleDebounceComponent.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; @@ -18,7 +17,7 @@ namespace osu.Game.Graphics.UserInterface private Bindable lastPlaybackTime; [BackgroundDependencyLoader] - private void load(AudioManager audio, SessionStatics statics) + private void load(SessionStatics statics) { lastPlaybackTime = statics.GetBindable(Static.LastHoverSoundPlaybackTime); } diff --git a/osu.Game/Graphics/UserInterface/OsuContextMenu.cs b/osu.Game/Graphics/UserInterface/OsuContextMenu.cs index cf201b18b4..e0946fd9e1 100644 --- a/osu.Game/Graphics/UserInterface/OsuContextMenu.cs +++ b/osu.Game/Graphics/UserInterface/OsuContextMenu.cs @@ -3,7 +3,6 @@ using osuTK.Graphics; using osu.Framework.Allocation; -using osu.Framework.Audio; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Effects; @@ -41,7 +40,7 @@ namespace osu.Game.Graphics.UserInterface } [BackgroundDependencyLoader] - private void load(OsuColour colours, AudioManager audio) + private void load(OsuColour colours) { BackgroundColour = colours.ContextMenuGray; } diff --git a/osu.Game/Graphics/UserInterface/OsuContextMenuSamples.cs b/osu.Game/Graphics/UserInterface/OsuContextMenuSamples.cs index d67ea499e5..921fef7951 100644 --- a/osu.Game/Graphics/UserInterface/OsuContextMenuSamples.cs +++ b/osu.Game/Graphics/UserInterface/OsuContextMenuSamples.cs @@ -16,7 +16,7 @@ namespace osu.Game.Graphics.UserInterface private Sample sampleClose; [BackgroundDependencyLoader] - private void load(OsuColour colours, AudioManager audio) + private void load(AudioManager audio) { sampleClick = audio.Samples.Get($@"UI/{HoverSampleSet.Default.GetDescription()}-select"); sampleOpen = audio.Samples.Get(@"UI/dropdown-open"); diff --git a/osu.Game/Overlays/AccountCreation/ScreenEntry.cs b/osu.Game/Overlays/AccountCreation/ScreenEntry.cs index a11b234cb1..a2c04c6989 100644 --- a/osu.Game/Overlays/AccountCreation/ScreenEntry.cs +++ b/osu.Game/Overlays/AccountCreation/ScreenEntry.cs @@ -44,7 +44,7 @@ namespace osu.Game.Overlays.AccountCreation private GameHost host { get; set; } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load() { InternalChildren = new Drawable[] { diff --git a/osu.Game/Overlays/AccountCreationOverlay.cs b/osu.Game/Overlays/AccountCreationOverlay.cs index 3084c7475a..a96aff2a5d 100644 --- a/osu.Game/Overlays/AccountCreationOverlay.cs +++ b/osu.Game/Overlays/AccountCreationOverlay.cs @@ -10,7 +10,6 @@ using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Screens; using osu.Framework.Threading; -using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Online.API; using osu.Game.Overlays.AccountCreation; @@ -35,7 +34,7 @@ namespace osu.Game.Overlays private readonly IBindable apiState = new Bindable(); [BackgroundDependencyLoader] - private void load(OsuColour colours, IAPIProvider api) + private void load(IAPIProvider api) { apiState.BindTo(api.State); apiState.BindValueChanged(apiStateChanged, true); diff --git a/osu.Game/Overlays/Changelog/ChangelogBuild.cs b/osu.Game/Overlays/Changelog/ChangelogBuild.cs index 2d071b7345..c65eefdee4 100644 --- a/osu.Game/Overlays/Changelog/ChangelogBuild.cs +++ b/osu.Game/Overlays/Changelog/ChangelogBuild.cs @@ -46,7 +46,7 @@ namespace osu.Game.Overlays.Changelog } [BackgroundDependencyLoader] - private void load(OsuColour colours, OverlayColourProvider colourProvider) + private void load() { foreach (var categoryEntries in Build.ChangelogEntries.GroupBy(b => b.Category).OrderBy(c => c.Key)) { diff --git a/osu.Game/Overlays/ChangelogOverlay.cs b/osu.Game/Overlays/ChangelogOverlay.cs index fe611d0134..ab97ae950d 100644 --- a/osu.Game/Overlays/ChangelogOverlay.cs +++ b/osu.Game/Overlays/ChangelogOverlay.cs @@ -7,7 +7,6 @@ using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; using osu.Framework.Allocation; -using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Input.Events; @@ -35,7 +34,7 @@ namespace osu.Game.Overlays } [BackgroundDependencyLoader] - private void load(AudioManager audio) + private void load() { Header.Build.BindTarget = Current; diff --git a/osu.Game/Overlays/Comments/CommentsContainer.cs b/osu.Game/Overlays/Comments/CommentsContainer.cs index 970fc5ccef..6a5734b553 100644 --- a/osu.Game/Overlays/Comments/CommentsContainer.cs +++ b/osu.Game/Overlays/Comments/CommentsContainer.cs @@ -317,7 +317,7 @@ namespace osu.Game.Overlays.Comments private class NoCommentsPlaceholder : CompositeDrawable { [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) + private void load() { Height = 80; RelativeSizeAxes = Axes.X; diff --git a/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs index a13f5ed6ce..00a866f1f4 100644 --- a/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs @@ -7,7 +7,6 @@ using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Textures; using osu.Framework.Localisation; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.Profile.Header.Components; @@ -30,7 +29,7 @@ namespace osu.Game.Overlays.Profile.Header } [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider, TextureStore textures) + private void load(OverlayColourProvider colourProvider) { Container hiddenDetailContainer; Container expandedDetailContainer; diff --git a/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs b/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs index eb6e48dfbf..8d4fc5fc9f 100644 --- a/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs +++ b/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Localisation; using osu.Framework.Platform; @@ -16,7 +15,7 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings protected override LocalisableString Header => DebugSettingsStrings.MemoryHeader; [BackgroundDependencyLoader] - private void load(FrameworkDebugConfigManager config, GameHost host, RealmContextFactory realmFactory) + private void load(GameHost host, RealmContextFactory realmFactory) { Children = new Drawable[] { diff --git a/osu.Game/Overlays/Settings/SettingsFooter.cs b/osu.Game/Overlays/Settings/SettingsFooter.cs index ed49ce2b63..263f2f4829 100644 --- a/osu.Game/Overlays/Settings/SettingsFooter.cs +++ b/osu.Game/Overlays/Settings/SettingsFooter.cs @@ -19,7 +19,7 @@ namespace osu.Game.Overlays.Settings public class SettingsFooter : FillFlowContainer { [BackgroundDependencyLoader] - private void load(OsuGameBase game, OsuColour colours, RulesetStore rulesets) + private void load(OsuGameBase game, RulesetStore rulesets) { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; diff --git a/osu.Game/Overlays/Settings/SettingsSection.cs b/osu.Game/Overlays/Settings/SettingsSection.cs index 0ae353602e..2539c32806 100644 --- a/osu.Game/Overlays/Settings/SettingsSection.cs +++ b/osu.Game/Overlays/Settings/SettingsSection.cs @@ -65,7 +65,7 @@ namespace osu.Game.Overlays.Settings } [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider, OsuColour colours) + private void load(OverlayColourProvider colourProvider) { AddRangeInternal(new Drawable[] { diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index c0b339a231..f2dbb1a23f 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -61,7 +61,7 @@ namespace osu.Game.Rulesets.UI private int direction = 1; [BackgroundDependencyLoader(true)] - private void load(GameplayClock clock, ISamplePlaybackDisabler sampleDisabler) + private void load(GameplayClock clock) { if (clock != null) { diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 39de13899d..9d5d8013b7 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -15,7 +15,6 @@ using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Utils; -using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Rulesets.Edit; @@ -57,7 +56,7 @@ namespace osu.Game.Screens.Edit.Compose.Components } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load() { InternalChild = SelectionBox = CreateSelectionBox(); diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index 2cbfe88519..7d52645aa1 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -15,7 +15,6 @@ using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Graphics; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Rulesets.Objects; using osu.Game.Screens.Edit.Timing; @@ -39,7 +38,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load() { volume.BindValueChanged(volume => updateText()); bank.BindValueChanged(bank => updateText(), true); diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimingPointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimingPointPiece.cs index fa51281c55..2df4ef001c 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimingPointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimingPointPiece.cs @@ -4,7 +4,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Graphics; namespace osu.Game.Screens.Edit.Compose.Components.Timeline { @@ -19,7 +18,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load() { beatLength.BindValueChanged(beatLength => { diff --git a/osu.Game/Screens/Edit/EditorRoundedScreenSettingsSection.cs b/osu.Game/Screens/Edit/EditorRoundedScreenSettingsSection.cs index e17114ebcb..25d7dfbb4a 100644 --- a/osu.Game/Screens/Edit/EditorRoundedScreenSettingsSection.cs +++ b/osu.Game/Screens/Edit/EditorRoundedScreenSettingsSection.cs @@ -6,7 +6,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics.Sprites; -using osu.Game.Overlays; using osuTK; namespace osu.Game.Screens.Edit @@ -20,7 +19,7 @@ namespace osu.Game.Screens.Edit protected FillFlowContainer Flow { get; private set; } [BackgroundDependencyLoader] - private void load(OverlayColourProvider colours) + private void load() { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; diff --git a/osu.Game/Screens/Edit/Verify/VisibilitySection.cs b/osu.Game/Screens/Edit/Verify/VisibilitySection.cs index d049436376..0bdc8c0efd 100644 --- a/osu.Game/Screens/Edit/Verify/VisibilitySection.cs +++ b/osu.Game/Screens/Edit/Verify/VisibilitySection.cs @@ -4,7 +4,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Game.Overlays; using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Edit.Checks.Components; @@ -24,7 +23,7 @@ namespace osu.Game.Screens.Edit.Verify protected override string HeaderText => "Visibility"; [BackgroundDependencyLoader] - private void load(OverlayColourProvider colours, VerifyScreen verify) + private void load(VerifyScreen verify) { hiddenIssueTypes = verify.HiddenIssueTypes.GetBoundCopy(); diff --git a/osu.Game/Screens/Import/FileImportScreen.cs b/osu.Game/Screens/Import/FileImportScreen.cs index c32e230e11..09870e0bab 100644 --- a/osu.Game/Screens/Import/FileImportScreen.cs +++ b/osu.Game/Screens/Import/FileImportScreen.cs @@ -9,7 +9,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Platform; using osu.Framework.Screens; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -40,7 +39,7 @@ namespace osu.Game.Screens.Import private OsuColour colours { get; set; } [BackgroundDependencyLoader(true)] - private void load(Storage storage) + private void load() { InternalChild = contentContainer = new Container { diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index feb6f6c92a..32731407fd 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -15,7 +15,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; -using osu.Framework.Localisation; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Threading; @@ -123,7 +122,7 @@ namespace osu.Game.Screens.Menu private LoginOverlay loginOverlay { get; set; } [BackgroundDependencyLoader(true)] - private void load(AudioManager audio, IdleTracker idleTracker, GameHost host, LocalisationManager strings) + private void load(AudioManager audio, IdleTracker idleTracker, GameHost host) { buttonsPlay.Add(new Button(ButtonSystemStrings.Solo, @"button-solo-select", FontAwesome.Solid.User, new Color4(102, 68, 204, 255), () => OnSolo?.Invoke(), WEDGE_WIDTH, Key.P)); buttonsPlay.Add(new Button(ButtonSystemStrings.Multi, @"button-generic-select", FontAwesome.Solid.Users, new Color4(94, 63, 186, 255), onMultiplayer, 0, Key.M)); diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index 948e3a7d88..59bf1785d5 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -18,7 +18,6 @@ using osu.Game.Configuration; using osu.Game.IO.Archives; using osu.Game.Overlays; using osu.Game.Screens.Backgrounds; -using osu.Game.Skinning; using osuTK; using osuTK.Graphics; @@ -81,7 +80,7 @@ namespace osu.Game.Screens.Menu } [BackgroundDependencyLoader] - private void load(OsuConfigManager config, SkinManager skinManager, BeatmapManager beatmaps, Framework.Game game) + private void load(OsuConfigManager config, BeatmapManager beatmaps, Framework.Game game) { // prevent user from changing beatmap while the intro is still runnning. beatmap = Beatmap.BeginLease(false); diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs index d171e481b1..10f940e9de 100644 --- a/osu.Game/Screens/Menu/IntroTriangles.cs +++ b/osu.Game/Screens/Menu/IntroTriangles.cs @@ -133,7 +133,7 @@ namespace osu.Game.Screens.Menu private OsuGameBase game { get; set; } [BackgroundDependencyLoader] - private void load(TextureStore textures) + private void load() { InternalChildren = new Drawable[] { diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 3da740b85d..8b1bab52b3 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -71,7 +71,7 @@ namespace osu.Game.Screens.Menu private SongTicker songTicker; [BackgroundDependencyLoader(true)] - private void load(BeatmapListingOverlay beatmapListing, SettingsOverlay settings, RankingsOverlay rankings, OsuConfigManager config, SessionStatics statics) + private void load(BeatmapListingOverlay beatmapListing, SettingsOverlay settings, OsuConfigManager config, SessionStatics statics) { holdDelay = config.GetBindable(OsuSetting.UIHoldActivationDelay); loginDisplayed = statics.GetBindable(Static.LoginOverlayDisplayed); diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs index ccc891d3bf..ed4901e1fa 100644 --- a/osu.Game/Screens/OsuScreen.cs +++ b/osu.Game/Screens/OsuScreen.cs @@ -145,7 +145,7 @@ namespace osu.Game.Screens } [BackgroundDependencyLoader(true)] - private void load(OsuGame osu, AudioManager audio) + private void load(AudioManager audio) { sampleExit = audio.Samples.Get(@"UI/screen-back"); } diff --git a/osu.Game/Screens/Play/EpilepsyWarning.cs b/osu.Game/Screens/Play/EpilepsyWarning.cs index 89e25d849f..ccb2870d78 100644 --- a/osu.Game/Screens/Play/EpilepsyWarning.cs +++ b/osu.Game/Screens/Play/EpilepsyWarning.cs @@ -2,11 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; -using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Screens.Backgrounds; @@ -39,7 +37,7 @@ namespace osu.Game.Screens.Play } [BackgroundDependencyLoader] - private void load(OsuColour colours, IBindable beatmap) + private void load(OsuColour colours) { Children = new Drawable[] { diff --git a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs index 8e0a38aa1f..5a7ef786d3 100644 --- a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs +++ b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs @@ -116,7 +116,7 @@ namespace osu.Game.Screens.Play.HUD public Action HoverLost; [BackgroundDependencyLoader] - private void load(OsuColour colours, Framework.Game game) + private void load(OsuColour colours) { Size = new Vector2(60); diff --git a/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs b/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs index 235f0f01fd..a71b661965 100644 --- a/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs +++ b/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; -using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; @@ -37,7 +36,7 @@ namespace osu.Game.Screens.Play.HUD } [BackgroundDependencyLoader] - private void load(OsuColour colours, BeatmapDifficultyCache difficultyCache) + private void load(OsuColour colours) { Colour = colours.BlueLighter; valid.BindValueChanged(e => diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs index 635be60549..e50520e0ca 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs @@ -10,7 +10,6 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; -using osu.Framework.Platform; using osu.Framework.Utils; using osu.Game.Audio; using osu.Game.Graphics; @@ -104,7 +103,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy } [BackgroundDependencyLoader] - private void load(GameHost host) + private void load() { InternalChildren = new Drawable[] { diff --git a/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs b/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs index df8c68a0dd..0fd39db97c 100644 --- a/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs +++ b/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs @@ -9,7 +9,6 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osu.Framework.Graphics.Shapes; @@ -83,7 +82,7 @@ namespace osu.Game.Screens.Select } [BackgroundDependencyLoader] - private void load(OsuColour colour, OsuConfigManager config) + private void load(OsuColour colour) { modsCheckbox.AccentColour = tabs.AccentColour = colour.YellowLight; } diff --git a/osu.Game/Tests/Visual/SkinnableTestScene.cs b/osu.Game/Tests/Visual/SkinnableTestScene.cs index 22aac79056..a080f47d66 100644 --- a/osu.Game/Tests/Visual/SkinnableTestScene.cs +++ b/osu.Game/Tests/Visual/SkinnableTestScene.cs @@ -41,7 +41,7 @@ namespace osu.Game.Tests.Visual } [BackgroundDependencyLoader] - private void load(AudioManager audio, SkinManager skinManager) + private void load() { var dllStore = new DllResourceStore(DynamicCompilationOriginal.GetType().Assembly); diff --git a/osu.Game/Users/Drawables/ClickableAvatar.cs b/osu.Game/Users/Drawables/ClickableAvatar.cs index 2825c41ef6..e3cfaf1d14 100644 --- a/osu.Game/Users/Drawables/ClickableAvatar.cs +++ b/osu.Game/Users/Drawables/ClickableAvatar.cs @@ -4,7 +4,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Textures; using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Graphics.Containers; @@ -58,7 +57,7 @@ namespace osu.Game.Users.Drawables } [BackgroundDependencyLoader] - private void load(LargeTextureStore textures) + private void load() { LoadComponentAsync(new DrawableAvatar(user), clickableArea.Add); } From 11580a2e716f9ac153b493e8acb21b6719e189e8 Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Sat, 15 Jan 2022 01:24:59 +0100 Subject: [PATCH 385/996] Update rider .idea files for `osu.sln` --- .idea/.idea.osu/.idea/misc.xml | 6 ++++++ .idea/.idea.osu/.idea/modules.xml | 8 -------- .idea/.idea.osu/.idea/projectSettingsUpdater.xml | 2 +- 3 files changed, 7 insertions(+), 9 deletions(-) create mode 100644 .idea/.idea.osu/.idea/misc.xml delete mode 100644 .idea/.idea.osu/.idea/modules.xml diff --git a/.idea/.idea.osu/.idea/misc.xml b/.idea/.idea.osu/.idea/misc.xml new file mode 100644 index 0000000000..1d8c84d0af --- /dev/null +++ b/.idea/.idea.osu/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/.idea.osu/.idea/modules.xml b/.idea/.idea.osu/.idea/modules.xml deleted file mode 100644 index 0360fdbc5e..0000000000 --- a/.idea/.idea.osu/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/.idea.osu/.idea/projectSettingsUpdater.xml b/.idea/.idea.osu/.idea/projectSettingsUpdater.xml index 7515e76054..4bb9f4d2a0 100644 --- a/.idea/.idea.osu/.idea/projectSettingsUpdater.xml +++ b/.idea/.idea.osu/.idea/projectSettingsUpdater.xml @@ -1,6 +1,6 @@ - \ No newline at end of file From 9e173f7fcd7f0861432ad56632d28e2aa494309e Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Sat, 15 Jan 2022 01:28:11 +0100 Subject: [PATCH 386/996] Update rider .idea files for Android .slnf --- .idea/.idea.osu.Android/.idea/.name | 1 + .idea/.idea.osu.Android/.idea/indexLayout.xml | 8 ++++++++ .idea/.idea.osu.Android/.idea/misc.xml | 6 ++++++ .idea/.idea.osu.Android/.idea/projectSettingsUpdater.xml | 6 ++++++ 4 files changed, 21 insertions(+) create mode 100644 .idea/.idea.osu.Android/.idea/.name create mode 100644 .idea/.idea.osu.Android/.idea/indexLayout.xml create mode 100644 .idea/.idea.osu.Android/.idea/misc.xml create mode 100644 .idea/.idea.osu.Android/.idea/projectSettingsUpdater.xml diff --git a/.idea/.idea.osu.Android/.idea/.name b/.idea/.idea.osu.Android/.idea/.name new file mode 100644 index 0000000000..86363b495c --- /dev/null +++ b/.idea/.idea.osu.Android/.idea/.name @@ -0,0 +1 @@ +osu.Android \ No newline at end of file diff --git a/.idea/.idea.osu.Android/.idea/indexLayout.xml b/.idea/.idea.osu.Android/.idea/indexLayout.xml new file mode 100644 index 0000000000..7b08163ceb --- /dev/null +++ b/.idea/.idea.osu.Android/.idea/indexLayout.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/.idea.osu.Android/.idea/misc.xml b/.idea/.idea.osu.Android/.idea/misc.xml new file mode 100644 index 0000000000..1d8c84d0af --- /dev/null +++ b/.idea/.idea.osu.Android/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/.idea.osu.Android/.idea/projectSettingsUpdater.xml b/.idea/.idea.osu.Android/.idea/projectSettingsUpdater.xml new file mode 100644 index 0000000000..4bb9f4d2a0 --- /dev/null +++ b/.idea/.idea.osu.Android/.idea/projectSettingsUpdater.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file From bc64fd20786c5af87c3c9a4fdb0198664a224912 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 15 Jan 2022 08:01:42 +0300 Subject: [PATCH 387/996] Add missing `vcs.xml` file to the android solution Present in both `.idea.osu` and `.idea.osu.Desktop`. --- .idea/.idea.osu.Android/.idea/vcs.xml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .idea/.idea.osu.Android/.idea/vcs.xml diff --git a/.idea/.idea.osu.Android/.idea/vcs.xml b/.idea/.idea.osu.Android/.idea/vcs.xml new file mode 100644 index 0000000000..94a25f7f4c --- /dev/null +++ b/.idea/.idea.osu.Android/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file From 64c499d9d6e6169e7f3984994288ddd9f759d6eb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 15 Jan 2022 15:24:30 +0900 Subject: [PATCH 388/996] Revert unintended temporary commenting (was used during benchmarking) --- .../Components/Timelines/Summary/Parts/ControlPointPart.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs index f6a7353e13..2dab3b0e59 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Specialized; using System.Linq; using osu.Framework.Bindables; @@ -35,8 +36,8 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts // as an optimisation, don't add a visualisation if there are already groups with the same types in close proximity. // for newly added control points (ie. lazer editor first where group is added empty) we always skip for simplicity. // that is fine, because cases where this is causing a performance issue are mostly where external tools were used to create an insane number of points. - // if (Children.Any(g => Math.Abs(g.Group.Time - group.Time) < 1000 && g.IsRedundant(group))) - // continue; + if (Children.Any(g => Math.Abs(g.Group.Time - group.Time) < 1000 && g.IsRedundant(group))) + continue; Add(new GroupVisualisation(group)); } From c4976e9db83fccd34534515523a77eb9168f6fa1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 15 Jan 2022 20:08:08 +0900 Subject: [PATCH 389/996] Update resources --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 4198cf2bf4..b2e3b32916 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -51,7 +51,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 2609f17c73..2e5c9f4548 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -37,7 +37,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 0064a597fd..897be33c18 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + From 3ab13dd78c8330ade055a674725f9d3a5c04494c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 15 Jan 2022 14:24:52 +0300 Subject: [PATCH 390/996] Assign position to spinner ticks for correct positional playback --- osu.Game.Rulesets.Osu/Objects/Spinner.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Spinner.cs b/osu.Game.Rulesets.Osu/Objects/Spinner.cs index 0ad8e4ea68..1eddfb7fef 100644 --- a/osu.Game.Rulesets.Osu/Objects/Spinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Spinner.cs @@ -65,8 +65,8 @@ namespace osu.Game.Rulesets.Osu.Objects double startTime = StartTime + (float)(i + 1) / totalSpins * Duration; AddNested(i < SpinsRequired - ? new SpinnerTick { StartTime = startTime } - : new SpinnerBonusTick { StartTime = startTime }); + ? new SpinnerTick { StartTime = startTime, Position = Position } + : new SpinnerBonusTick { StartTime = startTime, Position = Position }); } } From ea8e49c543ab5e516d2f41ff9de6185f7760c0f0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 15 Jan 2022 21:19:02 +0900 Subject: [PATCH 391/996] Reorder private/protected methods --- .../Multiplayer/MultiplayerRoomSounds.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomSounds.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomSounds.cs index 42e3716e5a..e673bbcc8c 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomSounds.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomSounds.cs @@ -28,13 +28,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer Host.ValueChanged += hostChanged; } - private void hostChanged(ValueChangedEvent value) - { - if (value.OldValue == null) return; - - hostChangedSample?.Play(); - } - protected override void UserJoined(MultiplayerRoomUser user) { base.UserJoined(user); @@ -56,6 +49,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer userKickedSample?.Play(); } + private void hostChanged(ValueChangedEvent value) + { + // only play sound when the host changes from an already-existing host. + if (value.OldValue == null) return; + + hostChangedSample?.Play(); + } + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); From 671a3d47b8a131e97ebfd5800c3194667a0f956e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 15 Jan 2022 21:20:27 +0900 Subject: [PATCH 392/996] Move bindable binding to `LoadComplete` and remove unnecessary unbind --- .../Multiplayer/MultiplayerRoomSounds.cs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomSounds.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomSounds.cs index e673bbcc8c..d467a32acb 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomSounds.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomSounds.cs @@ -24,8 +24,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer userJoinedSample = audio.Samples.Get(@"Multiplayer/player-joined"); userLeftSample = audio.Samples.Get(@"Multiplayer/player-left"); userKickedSample = audio.Samples.Get(@"Multiplayer/player-kicked"); + } - Host.ValueChanged += hostChanged; + protected override void LoadComplete() + { + base.LoadComplete(); + + Host.BindValueChanged(hostChanged); } protected override void UserJoined(MultiplayerRoomUser user) @@ -56,12 +61,5 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer hostChangedSample?.Play(); } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - Host.ValueChanged -= hostChanged; - } } } From e75d21507c238ff6a6f5b374a301a7ccbe1f7a9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 15 Jan 2022 15:26:41 +0100 Subject: [PATCH 393/996] Fix `GetDisplayTitleRomanisable()` relying on `ToString()` implementation --- osu.Game/Beatmaps/BeatmapMetadataInfoExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapMetadataInfoExtensions.cs b/osu.Game/Beatmaps/BeatmapMetadataInfoExtensions.cs index 7aab6a7a9b..7e7d1babf0 100644 --- a/osu.Game/Beatmaps/BeatmapMetadataInfoExtensions.cs +++ b/osu.Game/Beatmaps/BeatmapMetadataInfoExtensions.cs @@ -40,7 +40,7 @@ namespace osu.Game.Beatmaps /// public static RomanisableString GetDisplayTitleRomanisable(this IBeatmapMetadataInfo metadataInfo, bool includeCreator = true) { - string author = !includeCreator || string.IsNullOrEmpty(metadataInfo.Author.Username) ? string.Empty : $"({metadataInfo.Author})"; + string author = !includeCreator || string.IsNullOrEmpty(metadataInfo.Author.Username) ? string.Empty : $"({metadataInfo.Author.Username})"; string artistUnicode = string.IsNullOrEmpty(metadataInfo.ArtistUnicode) ? metadataInfo.Artist : metadataInfo.ArtistUnicode; string titleUnicode = string.IsNullOrEmpty(metadataInfo.TitleUnicode) ? metadataInfo.Title : metadataInfo.TitleUnicode; From c5cae4e3eebe1e6695c47e47d1e7f87d09646727 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 15 Jan 2022 23:55:11 +0900 Subject: [PATCH 394/996] Rename methods and add xmldoc --- .../Components/Timelines/Summary/Parts/ControlPointPart.cs | 2 +- .../Timelines/Summary/Parts/ControlPointVisualisation.cs | 2 +- .../Timelines/Summary/Parts/EffectPointVisualisation.cs | 2 +- .../Timelines/Summary/Parts/GroupVisualisation.cs | 6 ++---- .../Summary/Parts/IControlPointVisualisationRedundant.cs | 5 ++++- 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs index 2dab3b0e59..b72ff18434 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs @@ -36,7 +36,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts // as an optimisation, don't add a visualisation if there are already groups with the same types in close proximity. // for newly added control points (ie. lazer editor first where group is added empty) we always skip for simplicity. // that is fine, because cases where this is causing a performance issue are mostly where external tools were used to create an insane number of points. - if (Children.Any(g => Math.Abs(g.Group.Time - group.Time) < 1000 && g.IsRedundant(group))) + if (Children.Any(g => Math.Abs(g.Group.Time - group.Time) < 1000 && g.IsVisuallyRedundant(group))) continue; Add(new GroupVisualisation(group)); diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointVisualisation.cs index 96fce1dac5..aaa3838be5 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointVisualisation.cs @@ -27,6 +27,6 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts Colour = Point.GetRepresentingColour(colours); } - public bool IsRedundant(ControlPoint other) => other.GetType() == Point.GetType(); + public bool IsVisuallyRedundant(ControlPoint other) => other.GetType() == Point.GetType(); } } diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs index c08ab3728f..b4f048f29b 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs @@ -70,6 +70,6 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts } // kiai sections display duration, so are required to be visualised. - public bool IsRedundant(ControlPoint other) => (other as EffectControlPoint)?.KiaiMode == effect.KiaiMode; + public bool IsVisuallyRedundant(ControlPoint other) => (other as EffectControlPoint)?.KiaiMode == effect.KiaiMode; } } diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs index b55df66a02..fc39f83e6a 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs @@ -61,9 +61,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts /// /// For display purposes, check whether the proposed group is made redundant by this visualisation group. /// - /// - /// - public bool IsRedundant(ControlPointGroup other) => - other.ControlPoints.Any(c => InternalChildren.OfType().Any(c2 => c2.IsRedundant(c))); + public bool IsVisuallyRedundant(ControlPointGroup other) => + other.ControlPoints.Any(c => InternalChildren.OfType().Any(c2 => c2.IsVisuallyRedundant(c))); } } diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/IControlPointVisualisationRedundant.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/IControlPointVisualisationRedundant.cs index e4d47e4cdc..fc4facdd47 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/IControlPointVisualisationRedundant.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/IControlPointVisualisationRedundant.cs @@ -7,6 +7,9 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts { public interface IControlPointVisualisationRedundant { - bool IsRedundant(ControlPoint other); + /// + /// For display purposes, check whether the proposed point is made redundant by this visualisation. + /// + bool IsVisuallyRedundant(ControlPoint other); } } From 236fa6da7e4187a4112f39a26817d40801a7fc64 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 15 Jan 2022 23:56:00 +0900 Subject: [PATCH 395/996] Rename `ControlPointVisualisation` interface type to be less specific --- .../Timelines/Summary/Parts/ControlPointVisualisation.cs | 2 +- .../Timelines/Summary/Parts/EffectPointVisualisation.cs | 2 +- .../Components/Timelines/Summary/Parts/GroupVisualisation.cs | 2 +- ...tVisualisationRedundant.cs => IControlPointVisualisation.cs} | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) rename osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/{IControlPointVisualisationRedundant.cs => IControlPointVisualisation.cs} (89%) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointVisualisation.cs index aaa3838be5..41716f9c23 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointVisualisation.cs @@ -9,7 +9,7 @@ using osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations; namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts { - public class ControlPointVisualisation : PointVisualisation, IControlPointVisualisationRedundant + public class ControlPointVisualisation : PointVisualisation, IControlPointVisualisation { protected readonly ControlPoint Point; diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs index b4f048f29b..4db65c4250 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs @@ -13,7 +13,7 @@ using osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations; namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts { - public class EffectPointVisualisation : CompositeDrawable, IControlPointVisualisationRedundant + public class EffectPointVisualisation : CompositeDrawable, IControlPointVisualisation { private readonly EffectControlPoint effect; private Bindable kiai; diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs index fc39f83e6a..83759bbc2b 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs @@ -62,6 +62,6 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts /// For display purposes, check whether the proposed group is made redundant by this visualisation group. /// public bool IsVisuallyRedundant(ControlPointGroup other) => - other.ControlPoints.Any(c => InternalChildren.OfType().Any(c2 => c2.IsVisuallyRedundant(c))); + other.ControlPoints.Any(c => InternalChildren.OfType().Any(c2 => c2.IsVisuallyRedundant(c))); } } diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/IControlPointVisualisationRedundant.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/IControlPointVisualisation.cs similarity index 89% rename from osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/IControlPointVisualisationRedundant.cs rename to osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/IControlPointVisualisation.cs index fc4facdd47..c81f1828f7 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/IControlPointVisualisationRedundant.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/IControlPointVisualisation.cs @@ -5,7 +5,7 @@ using osu.Game.Beatmaps.ControlPoints; namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts { - public interface IControlPointVisualisationRedundant + public interface IControlPointVisualisation { /// /// For display purposes, check whether the proposed point is made redundant by this visualisation. From 565611ee00c16a86a2f9640682a346154431dc26 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 15 Jan 2022 23:57:20 +0900 Subject: [PATCH 396/996] Fix typo in inline comment --- .../Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs index b72ff18434..a33bb73681 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs @@ -57,7 +57,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts // to guarantee an accurate representation. // // note that the case where control point (type) is added or removed from a non-displayed group is not handled correctly. - // this is an edge case which shouldn't affect the user too badly. we may flatted control point groups in the future + // this is an edge case which shouldn't affect the user too badly. we may flatten control point groups in the future // which would allow this to be handled better. Clear(); foreach (var g in controlPointGroups) From 6e4214de4d945bb17459d4708f303f018d87a623 Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Sat, 15 Jan 2022 19:42:38 +0100 Subject: [PATCH 397/996] Move `VersionManager` from `osu.Desktop` to `osu.Game` --- osu.Desktop/OsuGameDesktop.cs | 27 ------------------- osu.Game/OsuGame.cs | 11 ++++++++ .../Overlays/VersionManager.cs | 3 +-- 3 files changed, 12 insertions(+), 29 deletions(-) rename {osu.Desktop => osu.Game}/Overlays/VersionManager.cs (98%) diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index b234207848..cd3fb7eb61 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -10,14 +10,11 @@ using System.Runtime.Versioning; using System.Threading.Tasks; using Microsoft.Win32; using osu.Desktop.Security; -using osu.Desktop.Overlays; using osu.Framework.Platform; using osu.Game; using osu.Desktop.Updater; using osu.Framework; using osu.Framework.Logging; -using osu.Framework.Screens; -using osu.Game.Screens.Menu; using osu.Game.Updater; using osu.Desktop.Windows; using osu.Framework.Threading; @@ -27,13 +24,9 @@ namespace osu.Desktop { internal class OsuGameDesktop : OsuGame { - private readonly bool noVersionOverlay; - private VersionManager versionManager; - public OsuGameDesktop(string[] args = null) : base(args) { - noVersionOverlay = args?.Any(a => a == "--no-version-overlay") ?? false; } public override StableStorage GetStorageForStableInstall() @@ -114,9 +107,6 @@ namespace osu.Desktop { base.LoadComplete(); - if (!noVersionOverlay) - LoadComponentAsync(versionManager = new VersionManager { Depth = int.MinValue }, ScreenContainer.Add); - LoadComponentAsync(new DiscordRichPresence(), Add); if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows) @@ -125,23 +115,6 @@ namespace osu.Desktop LoadComponentAsync(new ElevatedPrivilegesChecker(), Add); } - protected override void ScreenChanged(IScreen lastScreen, IScreen newScreen) - { - base.ScreenChanged(lastScreen, newScreen); - - switch (newScreen) - { - case IntroScreen _: - case MainMenu _: - versionManager?.Show(); - break; - - default: - versionManager?.Hide(); - break; - } - } - public override void SetHost(GameHost host) { base.SetHost(host); diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 21d84a365b..90af80e702 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -154,6 +154,8 @@ namespace osu.Game private MainMenu menuScreen; + private VersionManager versionManager; + [CanBeNull] private IntroScreen introScreen; @@ -803,6 +805,9 @@ namespace osu.Game loadComponentSingleFile(wikiOverlay = new WikiOverlay(), overlayContent.Add, true); loadComponentSingleFile(skinEditor = new SkinEditorOverlay(ScreenContainer), overlayContent.Add, true); + if (!args?.Any(a => a == @"--no-version-overlay") ?? true) + loadComponentSingleFile(versionManager = new VersionManager { Depth = int.MinValue }, ScreenContainer.Add); + loadComponentSingleFile(new LoginOverlay { Anchor = Anchor.TopRight, @@ -1126,10 +1131,16 @@ namespace osu.Game { case IntroScreen intro: introScreen = intro; + versionManager?.Show(); break; case MainMenu menu: menuScreen = menu; + versionManager?.Show(); + break; + + default: + versionManager?.Hide(); break; } diff --git a/osu.Desktop/Overlays/VersionManager.cs b/osu.Game/Overlays/VersionManager.cs similarity index 98% rename from osu.Desktop/Overlays/VersionManager.cs rename to osu.Game/Overlays/VersionManager.cs index e4a3451651..fe6613fba2 100644 --- a/osu.Desktop/Overlays/VersionManager.cs +++ b/osu.Game/Overlays/VersionManager.cs @@ -7,13 +7,12 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; -using osu.Game; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osuTK; using osuTK.Graphics; -namespace osu.Desktop.Overlays +namespace osu.Game.Overlays { public class VersionManager : VisibilityContainer { From 2a59735525abb6d481fadc044808f497ace7f2a4 Mon Sep 17 00:00:00 2001 From: mk-56 Date: Sat, 15 Jan 2022 21:43:28 +0100 Subject: [PATCH 398/996] Initial commit --- .../Mods/CatchModFlashlight.cs | 21 +++------- .../Mods/ManiaModFlashlight.cs | 13 ++++-- .../Mods/OsuModFlashlight.cs | 42 ++++++++++--------- .../Mods/TaikoModFlashlight.cs | 20 ++++----- osu.Game/Rulesets/Mods/ModFlashlight.cs | 42 +++++++++++++++++++ 5 files changed, 89 insertions(+), 49 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs index f399f48ebd..e5da168dc6 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs @@ -15,9 +15,10 @@ namespace osu.Game.Rulesets.Catch.Mods { public override double ScoreMultiplier => 1.12; - private const float default_flashlight_size = 350; + public override bool DefaultComboDependency => true; + public override float DefaultRadius => 350; - public override Flashlight CreateFlashlight() => new CatchFlashlight(playfield); + public override Flashlight CreateFlashlight() => new CatchFlashlight(playfield, ChangeRadius.Value, InitialRadius.Value); private CatchPlayfield playfield; @@ -31,10 +32,10 @@ namespace osu.Game.Rulesets.Catch.Mods { private readonly CatchPlayfield playfield; - public CatchFlashlight(CatchPlayfield playfield) + public CatchFlashlight(CatchPlayfield playfield, bool isRadiusBasedOnCombo, float initialRadius) : base(isRadiusBasedOnCombo, initialRadius) { this.playfield = playfield; - FlashlightSize = new Vector2(0, getSizeFor(0)); + FlashlightSize = new Vector2(0, GetRadiusFor(0)); } protected override void Update() @@ -44,19 +45,9 @@ namespace osu.Game.Rulesets.Catch.Mods FlashlightPosition = playfield.CatcherArea.ToSpaceOfOtherDrawable(playfield.Catcher.DrawPosition, this); } - private float getSizeFor(int combo) - { - if (combo > 200) - return default_flashlight_size * 0.8f; - else if (combo > 100) - return default_flashlight_size * 0.9f; - else - return default_flashlight_size; - } - protected override void OnComboChange(ValueChangedEvent e) { - this.TransformTo(nameof(FlashlightSize), new Vector2(0, getSizeFor(e.NewValue)), FLASHLIGHT_FADE_DURATION); + this.TransformTo(nameof(FlashlightSize), new Vector2(0, GetRadiusFor(e.NewValue)), FLASHLIGHT_FADE_DURATION); } protected override string FragmentShader => "CircularFlashlight"; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs index 86a00271e9..676b5f3842 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs @@ -16,17 +16,19 @@ namespace osu.Game.Rulesets.Mania.Mods public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(ModHidden) }; - private const float default_flashlight_size = 180; + public override bool DefaultComboDependency => false; + public override float DefaultRadius => 180; - public override Flashlight CreateFlashlight() => new ManiaFlashlight(); + public override Flashlight CreateFlashlight() => new ManiaFlashlight(ChangeRadius.Value, InitialRadius.Value); private class ManiaFlashlight : Flashlight { private readonly LayoutValue flashlightProperties = new LayoutValue(Invalidation.DrawSize); - public ManiaFlashlight() + public ManiaFlashlight(bool isRadiusBasedOnCombo, float initialRadius) + : base(isRadiusBasedOnCombo, initialRadius) { - FlashlightSize = new Vector2(0, default_flashlight_size); + FlashlightSize = new Vector2(DrawWidth, GetRadiusFor(0)); AddLayout(flashlightProperties); } @@ -46,9 +48,12 @@ namespace osu.Game.Rulesets.Mania.Mods protected override void OnComboChange(ValueChangedEvent e) { + this.TransformTo(nameof(FlashlightSize), new Vector2(DrawWidth, GetRadiusFor(e.NewValue)), FLASHLIGHT_FADE_DURATION); } protected override string FragmentShader => "RectangularFlashlight"; } } } + + diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs index 300a9d48aa..f15527460c 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs @@ -12,7 +12,6 @@ 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.UI; using osuTK; namespace osu.Game.Rulesets.Osu.Mods @@ -21,13 +20,16 @@ namespace osu.Game.Rulesets.Osu.Mods { public override double ScoreMultiplier => 1.12; - private const float default_flashlight_size = 180; + public override bool DefaultComboDependency => true; + + //private const float default_flashlight_size = 180; + public override float DefaultRadius => 180; private const double default_follow_delay = 120; private OsuFlashlight flashlight; - public override Flashlight CreateFlashlight() => flashlight = new OsuFlashlight(); + public override Flashlight CreateFlashlight() => flashlight = new OsuFlashlight(ChangeRadius.Value, InitialRadius.Value, FollowDelay.Value); public void ApplyToDrawableHitObject(DrawableHitObject drawable) { @@ -35,13 +37,6 @@ namespace osu.Game.Rulesets.Osu.Mods s.Tracking.ValueChanged += flashlight.OnSliderTrackingChange; } - public override void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) - { - base.ApplyToDrawableRuleset(drawableRuleset); - - flashlight.FollowDelay = FollowDelay.Value; - } - [SettingSource("Follow delay", "Milliseconds until the flashlight reaches the cursor")] public BindableNumber FollowDelay { get; } = new BindableDouble(default_follow_delay) { @@ -54,9 +49,15 @@ namespace osu.Game.Rulesets.Osu.Mods { public double FollowDelay { private get; set; } - public OsuFlashlight() + //public float InitialRadius { private get; set; } + public bool ChangeRadius { private get; set; } + + public OsuFlashlight(bool isRadiusBasedOnCombo, float initialRadius, double followDelay) + : base(isRadiusBasedOnCombo, initialRadius) { - FlashlightSize = new Vector2(0, getSizeFor(0)); + FollowDelay = followDelay; + + FlashlightSize = new Vector2(0, GetRadiusFor(0)); } public void OnSliderTrackingChange(ValueChangedEvent e) @@ -78,17 +79,20 @@ namespace osu.Game.Rulesets.Osu.Mods private float getSizeFor(int combo) { - if (combo > 200) - return default_flashlight_size * 0.8f; - else if (combo > 100) - return default_flashlight_size * 0.9f; - else - return default_flashlight_size; + if (ChangeRadius) + { + if (combo > 200) + return InitialRadius * 0.8f; + else if (combo > 100) + return InitialRadius * 0.9f; + } + + return InitialRadius; } protected override void OnComboChange(ValueChangedEvent e) { - this.TransformTo(nameof(FlashlightSize), new Vector2(0, getSizeFor(e.NewValue)), FLASHLIGHT_FADE_DURATION); + this.TransformTo(nameof(FlashlightSize), new Vector2(0, GetRadiusFor(e.NewValue)), FLASHLIGHT_FADE_DURATION); } protected override string FragmentShader => "CircularFlashlight"; diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs index 0a325f174e..29f29863c0 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs @@ -16,9 +16,12 @@ namespace osu.Game.Rulesets.Taiko.Mods { public override double ScoreMultiplier => 1.12; - private const float default_flashlight_size = 250; + public override bool DefaultComboDependency => true; - public override Flashlight CreateFlashlight() => new TaikoFlashlight(playfield); + //private const float default_flashlight_size = 250; + public override float DefaultRadius => 250; + + public override Flashlight CreateFlashlight() => new TaikoFlashlight(playfield, ChangeRadius.Value, InitialRadius.Value); private TaikoPlayfield playfield; @@ -33,7 +36,8 @@ namespace osu.Game.Rulesets.Taiko.Mods private readonly LayoutValue flashlightProperties = new LayoutValue(Invalidation.DrawSize); private readonly TaikoPlayfield taikoPlayfield; - public TaikoFlashlight(TaikoPlayfield taikoPlayfield) + public TaikoFlashlight(TaikoPlayfield taikoPlayfield, bool isRadiusBasedOnCombo, float initialRadius) + : base(isRadiusBasedOnCombo, initialRadius) { this.taikoPlayfield = taikoPlayfield; FlashlightSize = getSizeFor(0); @@ -43,15 +47,9 @@ namespace osu.Game.Rulesets.Taiko.Mods private Vector2 getSizeFor(int combo) { - float size = default_flashlight_size; - - if (combo > 200) - size *= 0.8f; - else if (combo > 100) - size *= 0.9f; - // Preserve flashlight size through the playfield's aspect adjustment. - return new Vector2(0, size * taikoPlayfield.DrawHeight / TaikoPlayfield.DEFAULT_HEIGHT); + // return new Vector2(0, size * taikoPlayfield.DrawHeight / TaikoPlayfield.DEFAULT_HEIGHT); + return new Vector2(0, GetRadiusFor(combo) * taikoPlayfield.DrawHeight / TaikoPlayfield.DEFAULT_HEIGHT); } protected override void OnComboChange(ValueChangedEvent e) diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index a77a83b36c..bff0e2f12d 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -13,6 +13,7 @@ using osu.Framework.Graphics.Shaders; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Game.Beatmaps.Timing; +using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.OpenGL.Vertices; using osu.Game.Rulesets.Objects; @@ -32,8 +33,26 @@ namespace osu.Game.Rulesets.Mods public override ModType Type => ModType.DifficultyIncrease; public override string Description => "Restricted view area."; + [SettingSource("Change radius based on combo", "Decrease the flashlight radius as combo increases.")] + public Bindable ChangeRadius { get; private set; } + + [SettingSource("Initial radius", "Initial radius of the flashlight area.")] + public BindableNumber InitialRadius { get; private set; } + + public abstract float DefaultRadius { get; } + + public abstract bool DefaultComboDependency { get; } + internal ModFlashlight() { + InitialRadius = new BindableFloat + { + MinValue = 90f, + MaxValue = 250f, + Precision = 5f, + }; + + ChangeRadius = new BindableBool(DefaultComboDependency); } } @@ -93,6 +112,16 @@ namespace osu.Game.Rulesets.Mods public List Breaks; + public readonly bool IsRadiusBasedOnCombo; + + public readonly float InitialRadius; + + protected Flashlight(bool isRadiusBasedOnCombo, float initialRadius) + { + IsRadiusBasedOnCombo = isRadiusBasedOnCombo; + InitialRadius = initialRadius; + } + [BackgroundDependencyLoader] private void load(ShaderManager shaderManager) { @@ -124,6 +153,19 @@ namespace osu.Game.Rulesets.Mods protected abstract string FragmentShader { get; } + protected float GetRadiusFor(int combo) + { + if (IsRadiusBasedOnCombo) + { + if (combo > 200) + return InitialRadius * 0.8f; + else if (combo > 100) + return InitialRadius * 0.9f; + } + + return InitialRadius; + } + private Vector2 flashlightPosition; protected Vector2 FlashlightPosition From 566d341b1e526184693cb80cc9113d80894ba8ba Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 16 Jan 2022 22:04:29 +0900 Subject: [PATCH 399/996] Split conditions out for readability --- .../Timelines/Summary/Parts/EffectPointVisualisation.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs index 4db65c4250..7c14152b3d 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs @@ -70,6 +70,6 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts } // kiai sections display duration, so are required to be visualised. - public bool IsVisuallyRedundant(ControlPoint other) => (other as EffectControlPoint)?.KiaiMode == effect.KiaiMode; + public bool IsVisuallyRedundant(ControlPoint other) => other is EffectControlPoint otherEffect && effect.KiaiMode == otherEffect.KiaiMode; } } From 57cc2f78933fb89c3226c7ab28bc1a3fe6c0e8da Mon Sep 17 00:00:00 2001 From: mk-56 Date: Sun, 16 Jan 2022 14:26:26 +0100 Subject: [PATCH 400/996] Adjustment to size values of FL per mode --- osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs | 3 ++- osu.Game/Rulesets/Mods/ModFlashlight.cs | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs index e5da168dc6..9d9fa5aed4 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs @@ -32,7 +32,8 @@ namespace osu.Game.Rulesets.Catch.Mods { private readonly CatchPlayfield playfield; - public CatchFlashlight(CatchPlayfield playfield, bool isRadiusBasedOnCombo, float initialRadius) : base(isRadiusBasedOnCombo, initialRadius) + public CatchFlashlight(CatchPlayfield playfield, bool isRadiusBasedOnCombo, float initialRadius) + : base(isRadiusBasedOnCombo, initialRadius) { this.playfield = playfield; FlashlightSize = new Vector2(0, GetRadiusFor(0)); diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index bff0e2f12d..c218ab45fe 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -45,10 +45,10 @@ namespace osu.Game.Rulesets.Mods internal ModFlashlight() { - InitialRadius = new BindableFloat + InitialRadius = new BindableFloat(DefaultRadius) { - MinValue = 90f, - MaxValue = 250f, + MinValue = DefaultRadius * .5f, + MaxValue = DefaultRadius * 1.5f, Precision = 5f, }; From 084b1fb4704b2bd2f7b30b60f766d507e3778594 Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Sun, 16 Jan 2022 15:20:22 +0100 Subject: [PATCH 401/996] Load the VersionManager earlier --- osu.Game/OsuGame.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 90af80e702..6730f2cf65 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -745,6 +745,9 @@ namespace osu.Game ScreenStack.ScreenPushed += screenPushed; ScreenStack.ScreenExited += screenExited; + if (!args?.Any(a => a == @"--no-version-overlay") ?? true) + loadComponentSingleFile(versionManager = new VersionManager { Depth = int.MinValue }, ScreenContainer.Add); + loadComponentSingleFile(osuLogo, logo => { logoContainer.Add(logo); @@ -805,9 +808,6 @@ namespace osu.Game loadComponentSingleFile(wikiOverlay = new WikiOverlay(), overlayContent.Add, true); loadComponentSingleFile(skinEditor = new SkinEditorOverlay(ScreenContainer), overlayContent.Add, true); - if (!args?.Any(a => a == @"--no-version-overlay") ?? true) - loadComponentSingleFile(versionManager = new VersionManager { Depth = int.MinValue }, ScreenContainer.Add); - loadComponentSingleFile(new LoginOverlay { Anchor = Anchor.TopRight, From 632246a3b31eed8c7a52378268a9b814c69a00e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 16 Jan 2022 17:04:59 +0100 Subject: [PATCH 402/996] Add failing test scene --- .../TestSceneDrumRollJudgements.cs | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 osu.Game.Rulesets.Taiko.Tests/TestSceneDrumRollJudgements.cs diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumRollJudgements.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumRollJudgements.cs new file mode 100644 index 0000000000..fc4fd286f4 --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumRollJudgements.cs @@ -0,0 +1,36 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Taiko.Objects; + +namespace osu.Game.Rulesets.Taiko.Tests +{ + public class TestSceneDrumRollJudgements : TestSceneTaikoPlayer + { + [Test] + public void TestStrongDrumRollFullyJudgedOnKilled() + { + AddUntilStep("gameplay finished", () => Player.ScoreProcessor.HasCompleted.Value); + AddAssert("all judgements are misses", () => Player.Results.All(r => r.Type == r.Judgement.MinResult)); + } + + protected override bool Autoplay => false; + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new Beatmap() + { + BeatmapInfo = { Ruleset = new TaikoRuleset().RulesetInfo }, + HitObjects = + { + new DrumRoll + { + StartTime = 1000, + Duration = 1000, + IsStrong = true + } + } + }; + } +} From c6adbdd46f105071c344f48aa8ab997a6a8cad2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 16 Jan 2022 17:15:01 +0100 Subject: [PATCH 403/996] Fix drum rolls nested objects not applying min result on kill --- .../Objects/Drawables/DrawableDrumRoll.cs | 8 ++++++++ .../Objects/Drawables/DrawableDrumRollTick.cs | 17 +++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs index 521189d36c..b84db513f7 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs @@ -197,6 +197,14 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables ApplyResult(r => r.Type = ParentHitObject.IsHit ? r.Judgement.MaxResult : r.Judgement.MinResult); } + public override void OnKilled() + { + base.OnKilled(); + + if (Time.Current > ParentHitObject.HitObject.GetEndTime() && !Judged) + ApplyResult(r => r.Type = r.Judgement.MinResult); + } + public override bool OnPressed(KeyBindingPressEvent e) => false; } } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs index dc2ed200a1..e24923e482 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs @@ -5,6 +5,7 @@ using System; using JetBrains.Annotations; using osu.Framework.Graphics; using osu.Framework.Input.Events; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Taiko.Skinning.Default; using osu.Game.Skinning; @@ -52,6 +53,14 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables ApplyResult(r => r.Type = r.Judgement.MaxResult); } + public override void OnKilled() + { + base.OnKilled(); + + if (Time.Current > HitObject.GetEndTime() && !Judged) + ApplyResult(r => r.Type = r.Judgement.MinResult); + } + protected override void UpdateHitStateTransforms(ArmedState state) { switch (state) @@ -92,6 +101,14 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables ApplyResult(r => r.Type = ParentHitObject.IsHit ? r.Judgement.MaxResult : r.Judgement.MinResult); } + public override void OnKilled() + { + base.OnKilled(); + + if (Time.Current > ParentHitObject.HitObject.GetEndTime() && !Judged) + ApplyResult(r => r.Type = r.Judgement.MinResult); + } + public override bool OnPressed(KeyBindingPressEvent e) => false; } } From 093b76e0ff59662353cd04ee865ccd54f5ba755d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 16 Jan 2022 18:56:21 +0100 Subject: [PATCH 404/996] Fix drawable mania judgement scene looking broken --- .../Skinning/TestSceneDrawableJudgement.cs | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs index 75a5495078..d033676ec7 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs @@ -5,8 +5,10 @@ using System; using System.Linq; using osu.Framework.Extensions; using osu.Framework.Graphics; +using osu.Framework.Testing; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mania.Scoring; +using osu.Game.Rulesets.Mania.Skinning.Legacy; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; @@ -23,15 +25,24 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning { if (hitWindows.IsHitResultAllowed(result)) { - AddStep("Show " + result.GetDescription(), () => SetContents(_ => - new DrawableManiaJudgement(new JudgementResult(new HitObject { StartTime = Time.Current }, new Judgement()) - { - Type = result - }, null) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - })); + AddStep("Show " + result.GetDescription(), () => + { + SetContents(_ => + new DrawableManiaJudgement(new JudgementResult(new HitObject { StartTime = Time.Current }, new Judgement()) + { + Type = result + }, null) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }); + + // for test purposes, undo the Y adjustment related to the `ScorePosition` legacy positioning config value + // (see `LegacyManiaJudgementPiece.load()`). + // this prevents the judgements showing somewhere below or above the bounding box of the judgement. + foreach (var legacyPiece in this.ChildrenOfType()) + legacyPiece.Y = 0; + }); } } } From 8e5ff201a139e90fb37703b642a1042bb7ff3353 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 16 Jan 2022 18:59:26 +0100 Subject: [PATCH 405/996] Modify mania special skin to demonstrate failure case --- .../Resources/special-skin/skin.ini | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/skin.ini b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/skin.ini index 36765d61bf..9c987efc60 100644 --- a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/skin.ini +++ b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/skin.ini @@ -4,11 +4,14 @@ Version: 2.5 [Mania] Keys: 4 ColumnLineWidth: 3,1,3,1,1 -Hit0: mania/hit0 -Hit50: mania/hit50 -Hit100: mania/hit100 -Hit200: mania/hit200 -Hit300: mania/hit300 -Hit300g: mania/hit300g +// some skins found in the wild had configuration keys where the @2x suffix was included in the values. +// the expected compatibility behaviour is that the presence of the @2x suffix shouldn't change anything +// if @2x assets are present. +Hit0: mania/hit0@2x +Hit50: mania/hit50@2x +Hit100: mania/hit100@2x +Hit200: mania/hit200@2x +Hit300: mania/hit300@2x +Hit300g: mania/hit300g@2x StageLeft: mania/stage-left StageRight: mania/stage-right \ No newline at end of file From cbaa3de548736c6bfbc1f08c527f5ebceb3c7b32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 16 Jan 2022 19:44:04 +0100 Subject: [PATCH 406/996] Strip `@2x` scale modifiers when looking up legacy skin textures --- osu.Game/Skinning/LegacySkin.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index e677e2c01b..e64011941f 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -474,13 +474,18 @@ namespace osu.Game.Skinning { foreach (string name in getFallbackNames(componentName)) { + // some component names (especially user-controlled ones, like `HitX` in mania) + // may contain `@2x` scale specifications. + // stable happens to check for that and strip them, so do the same to match stable behaviour. + string lookupName = name.Replace(@"@2x", string.Empty); + float ratio = 2; - var texture = Textures?.Get($"{name}@2x", wrapModeS, wrapModeT); + var texture = Textures?.Get(@$"{lookupName}@2x", wrapModeS, wrapModeT); if (texture == null) { ratio = 1; - texture = Textures?.Get(name, wrapModeS, wrapModeT); + texture = Textures?.Get(lookupName, wrapModeS, wrapModeT); } if (texture == null) From 88bf406c225c3a074f2d9756b815338f42910bd9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 17 Jan 2022 12:56:43 +0900 Subject: [PATCH 407/996] Fix null reference in equality comparison causing beatmap set overlay to crash --- .../Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs index d659df21c6..1f3f73a60a 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs @@ -107,7 +107,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { set { - if (score.Equals(value)) + if (score?.Equals(value) == true) return; score = value; From a3806f44a51adb35ad97299aa2250d1138b6ce18 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 17 Jan 2022 13:11:43 +0900 Subject: [PATCH 408/996] Add back `null` beatmap allowance to `GetTotalScore` flow to fix playlist aggregate scores --- osu.Game/Scoring/ScoreManager.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index ad2be7d813..dc90e819d4 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -132,9 +132,10 @@ namespace osu.Game.Scoring /// The total score. public async Task GetTotalScoreAsync([NotNull] ScoreInfo score, ScoringMode mode = ScoringMode.Standardised, CancellationToken cancellationToken = default) { - // TODO: ?? - // if (score.Beatmap == null) - // return score.TotalScore; + // TODO: This is required for playlist aggregate scores. They should likely not be getting here in the first place. + // ReSharper disable once ConditionIsAlwaysTrueOrFalse + if (score.BeatmapInfo == null) + return score.TotalScore; int beatmapMaxCombo; double accuracy = score.Accuracy; From d27ee2c9fb62348c0bf24f52f7dd4b5608670080 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 17 Jan 2022 13:37:42 +0900 Subject: [PATCH 409/996] Remove incorrect `IsManaged` check in `ScoreManager` --- osu.Game/Scoring/ScoreManager.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index dc90e819d4..52180d7ff2 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -159,11 +159,8 @@ namespace osu.Game.Scoring beatmapMaxCombo = score.BeatmapInfo.MaxCombo.Value; else { - if (!score.BeatmapInfo.IsManaged || difficulties == null) - { - // We don't have enough information (max combo) to compute the score, so use the provided score. + if (difficulties == null) return score.TotalScore; - } // We can compute the max combo locally after the async beatmap difficulty computation. var difficulty = await difficulties().GetDifficultyAsync(score.BeatmapInfo, score.Ruleset, score.Mods, cancellationToken).ConfigureAwait(false); From 11ca1b6e7b042f0fdf3c6c4d4691a19fe9d0ae84 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 17 Jan 2022 13:40:27 +0900 Subject: [PATCH 410/996] Remove one more usage of `IsManaged` which could potentially go wrong --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 13ece08144..dc67a4eb45 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -117,7 +117,7 @@ namespace osu.Game.Screens.Select newRoot.AddChildren(beatmapSets.Select(createCarouselSet).Where(g => g != null)); root = newRoot; - if (selectedBeatmapSet != null && (!selectedBeatmapSet.BeatmapSet.IsManaged || !beatmapSets.Contains(selectedBeatmapSet.BeatmapSet))) + if (selectedBeatmapSet != null && !beatmapSets.Contains(selectedBeatmapSet.BeatmapSet)) selectedBeatmapSet = null; Scroll.Clear(false); From 744084b41870cb5238263dab0a10743d241034a9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 17 Jan 2022 13:51:30 +0900 Subject: [PATCH 411/996] Initialise all parameters is paramaterless constructor for now for added safety --- osu.Game/Beatmaps/BeatmapInfo.cs | 9 +++++--- osu.Game/Scoring/ScoreInfo.cs | 39 +++++++++++++++++--------------- 2 files changed, 27 insertions(+), 21 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index b9dd59cfe4..e14e945865 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -31,11 +31,11 @@ namespace osu.Game.Beatmaps public string DifficultyName { get; set; } = string.Empty; - public RulesetInfo Ruleset { get; set; } = null!; + public RulesetInfo Ruleset { get; set; } - public BeatmapDifficulty Difficulty { get; set; } = new BeatmapDifficulty(); + public BeatmapDifficulty Difficulty { get; set; } - public BeatmapMetadata Metadata { get; set; } = new BeatmapMetadata(); + public BeatmapMetadata Metadata { get; set; } [IgnoreMap] [Backlink(nameof(ScoreInfo.BeatmapInfo))] @@ -51,6 +51,9 @@ namespace osu.Game.Beatmaps [UsedImplicitly] public BeatmapInfo() // TODO: consider removing this and migrating all usages to ctor with parameters. { + Ruleset = new RulesetInfo(); + Difficulty = new BeatmapDifficulty(); + Metadata = new BeatmapMetadata(); } public BeatmapSetInfo? BeatmapSet { get; set; } diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index b168726283..fd0f14266a 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -32,19 +32,33 @@ namespace osu.Game.Scoring [PrimaryKey] public Guid ID { get; set; } = Guid.NewGuid(); + public BeatmapInfo BeatmapInfo { get; set; } + + public RulesetInfo Ruleset { get; set; } + public IList Files { get; } = null!; public string Hash { get; set; } = string.Empty; public bool DeletePending { get; set; } - public bool Equals(ScoreInfo other) => other.ID == ID; + public long TotalScore { get; set; } + + public int MaxCombo { get; set; } + + public double Accuracy { get; set; } + + public bool HasReplay { get; set; } + + public DateTimeOffset Date { get; set; } + + public double? PP { get; set; } [Indexed] public long OnlineID { get; set; } = -1; [MapTo("User")] - public RealmUser RealmUser { get; set; } = new RealmUser(); + public RealmUser RealmUser { get; set; } [MapTo("Mods")] public string ModsJson { get; set; } = string.Empty; @@ -62,6 +76,9 @@ namespace osu.Game.Scoring [UsedImplicitly] public ScoreInfo() // TODO: consider removing this and migrating all usages to ctor with parameters. { + Ruleset = new RulesetInfo(); + RealmUser = new RealmUser(); + BeatmapInfo = new BeatmapInfo(); } // TODO: this is a bit temporary to account for the fact that this class is used to ferry API user data to certain UI components. @@ -88,22 +105,6 @@ namespace osu.Game.Scoring } } - public long TotalScore { get; set; } - - public int MaxCombo { get; set; } - - public double Accuracy { get; set; } - - public bool HasReplay { get; set; } - - public DateTimeOffset Date { get; set; } - - public double? PP { get; set; } - - public BeatmapInfo BeatmapInfo { get; set; } = null!; - - public RulesetInfo Ruleset { get; set; } = null!; - public ScoreRank Rank { get => (ScoreRank)RankInt; @@ -275,6 +276,8 @@ namespace osu.Game.Scoring #endregion + public bool Equals(ScoreInfo other) => other.ID == ID; + public override string ToString() => this.GetDisplayTitle(); } } From a0e210646863b0d72a443f45a691adcf0726bf88 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 17 Jan 2022 14:02:15 +0900 Subject: [PATCH 412/996] Guard against null values getting inserted into database during score/beatmap imports --- osu.Game/Scoring/ScoreModelManager.cs | 5 +++++ osu.Game/Stores/BeatmapImporter.cs | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Scoring/ScoreModelManager.cs b/osu.Game/Scoring/ScoreModelManager.cs index fcf7244226..5ba152fad3 100644 --- a/osu.Game/Scoring/ScoreModelManager.cs +++ b/osu.Game/Scoring/ScoreModelManager.cs @@ -63,6 +63,11 @@ namespace osu.Game.Scoring if (!model.Ruleset.IsManaged) model.Ruleset = realm.Find(model.Ruleset.ShortName); + // These properties are known to be non-null, but these final checks ensure a null hasn't come from somewhere (or the refetch has failed). + // Under no circumstance do we want these to be written to realm as null. + if (model.BeatmapInfo == null) throw new ArgumentNullException(nameof(model.BeatmapInfo)); + if (model.Ruleset == null) throw new ArgumentNullException(nameof(model.Ruleset)); + if (string.IsNullOrEmpty(model.StatisticsJson)) model.StatisticsJson = JsonConvert.SerializeObject(model.Statistics); } diff --git a/osu.Game/Stores/BeatmapImporter.cs b/osu.Game/Stores/BeatmapImporter.cs index 3ebdcde296..5303bd6fba 100644 --- a/osu.Game/Stores/BeatmapImporter.cs +++ b/osu.Game/Stores/BeatmapImporter.cs @@ -64,7 +64,7 @@ namespace osu.Game.Stores // ensure we aren't trying to add a new ruleset to the database // this can happen in tests, mostly if (!b.Ruleset.IsManaged) - b.Ruleset = realm.Find(b.Ruleset.ShortName); + b.Ruleset = realm.Find(b.Ruleset.ShortName) ?? throw new ArgumentNullException(nameof(b.Ruleset)); } validateOnlineIds(beatmapSet, realm); From 34e99968d098d1a86deb3422bd34c754f1e20d02 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 17 Jan 2022 14:06:48 +0900 Subject: [PATCH 413/996] Resolve inspection --- osu.Game.Rulesets.Taiko.Tests/TestSceneDrumRollJudgements.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumRollJudgements.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumRollJudgements.cs index fc4fd286f4..060c3c9443 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumRollJudgements.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumRollJudgements.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Taiko.Tests protected override bool Autoplay => false; - protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new Beatmap() + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new Beatmap { BeatmapInfo = { Ruleset = new TaikoRuleset().RulesetInfo }, HitObjects = From 381174e48248d13c241e0b9623c4a2eb199a8f4f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 17 Jan 2022 14:40:00 +0900 Subject: [PATCH 414/996] Give the placeholder ruleset better defaults to allow tests to work again --- osu.Game/Beatmaps/BeatmapInfo.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index e14e945865..224cf60c49 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -51,7 +51,12 @@ namespace osu.Game.Beatmaps [UsedImplicitly] public BeatmapInfo() // TODO: consider removing this and migrating all usages to ctor with parameters. { - Ruleset = new RulesetInfo(); + Ruleset = new RulesetInfo + { + OnlineID = 0, + ShortName = @"osu", + Name = @"null placeholder ruleset" + }; Difficulty = new BeatmapDifficulty(); Metadata = new BeatmapMetadata(); } @@ -149,20 +154,17 @@ namespace osu.Game.Beatmaps #region Compatibility properties - private int rulesetID; - [Ignored] [IgnoreMap] public int RulesetID { - // ReSharper disable once ConstantConditionalAccessQualifier - get => Ruleset?.OnlineID ?? rulesetID; + get => Ruleset.OnlineID; set { - if (Ruleset != null) - throw new InvalidOperationException($"Cannot set a {nameof(RulesetID)} when {nameof(Ruleset)} is non-null"); + if (!string.IsNullOrEmpty(Ruleset.InstantiationInfo)) + throw new InvalidOperationException($"Cannot set a {nameof(RulesetID)} when {nameof(Ruleset)} is already set to an actual ruleset."); - rulesetID = value; + Ruleset.OnlineID = value; } } From 90166829c3d6128c8d91d24963d51a0cb011ecd8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 17 Jan 2022 14:40:06 +0900 Subject: [PATCH 415/996] Update tests which were importing scores without a valid beatmap --- osu.Game.Tests/Scores/IO/ImportScoreTest.cs | 22 ++++++++++++++----- .../Gameplay/TestSceneReplayDownloadButton.cs | 21 +++++++++++++----- 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs index 0e6d560167..dd12c94855 100644 --- a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs +++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs @@ -8,8 +8,8 @@ using System.Linq; using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Extensions; using osu.Framework.Platform; -using osu.Game.Beatmaps; using osu.Game.IO.Archives; using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets.Mods; @@ -17,6 +17,8 @@ using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; +using osu.Game.Tests.Beatmaps.IO; +using osu.Game.Tests.Resources; namespace osu.Game.Tests.Scores.IO { @@ -31,6 +33,8 @@ namespace osu.Game.Tests.Scores.IO { var osu = LoadOsuIntoHost(host, true); + var beatmap = BeatmapImportHelper.LoadOszIntoOsu(osu, TestResources.GetQuickTestBeatmapForImport()).GetResultSafely(); + var toImport = new ScoreInfo { Rank = ScoreRank.B, @@ -42,7 +46,7 @@ namespace osu.Game.Tests.Scores.IO Date = DateTimeOffset.Now, OnlineID = 12345, Ruleset = new OsuRuleset().RulesetInfo, - BeatmapInfo = new BeatmapInfo() + BeatmapInfo = beatmap.Beatmaps.First() }; var imported = await LoadScoreIntoOsu(osu, toImport); @@ -71,10 +75,12 @@ namespace osu.Game.Tests.Scores.IO { var osu = LoadOsuIntoHost(host, true); + var beatmap = BeatmapImportHelper.LoadOszIntoOsu(osu, TestResources.GetQuickTestBeatmapForImport()).GetResultSafely(); + var toImport = new ScoreInfo { User = new APIUser { Username = "Test user" }, - BeatmapInfo = new BeatmapInfo(), + BeatmapInfo = beatmap.Beatmaps.First(), Ruleset = new OsuRuleset().RulesetInfo, Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() }, }; @@ -100,10 +106,12 @@ namespace osu.Game.Tests.Scores.IO { var osu = LoadOsuIntoHost(host, true); + var beatmap = BeatmapImportHelper.LoadOszIntoOsu(osu, TestResources.GetQuickTestBeatmapForImport()).GetResultSafely(); + var toImport = new ScoreInfo { User = new APIUser { Username = "Test user" }, - BeatmapInfo = new BeatmapInfo(), + BeatmapInfo = beatmap.Beatmaps.First(), Ruleset = new OsuRuleset().RulesetInfo, Statistics = new Dictionary { @@ -133,10 +141,12 @@ namespace osu.Game.Tests.Scores.IO { var osu = LoadOsuIntoHost(host, true); + var beatmap = BeatmapImportHelper.LoadOszIntoOsu(osu, TestResources.GetQuickTestBeatmapForImport()).GetResultSafely(); + await LoadScoreIntoOsu(osu, new ScoreInfo { User = new APIUser { Username = "Test user" }, - BeatmapInfo = new BeatmapInfo(), + BeatmapInfo = beatmap.Beatmaps.First(), Ruleset = new OsuRuleset().RulesetInfo, OnlineID = 2 }, new TestArchiveReader()); @@ -147,7 +157,7 @@ namespace osu.Game.Tests.Scores.IO Assert.That(scoreManager.IsAvailableLocally(new ScoreInfo { User = new APIUser { Username = "Test user" }, - BeatmapInfo = new BeatmapInfo(), + BeatmapInfo = beatmap.Beatmaps.First(), OnlineID = 2 })); } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs index 3168c4b94e..8199389b36 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs @@ -6,16 +6,18 @@ using NUnit.Framework; using osu.Framework.Graphics; using osu.Game.Online; using osu.Game.Online.API.Requests.Responses; -using osu.Game.Scoring; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Testing; +using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; +using osu.Game.Scoring; using osu.Game.Screens.Ranking; +using osu.Game.Tests.Resources; using osuTK.Input; using APIUser = osu.Game.Online.API.Requests.Responses.APIUser; @@ -29,6 +31,18 @@ namespace osu.Game.Tests.Visual.Gameplay private TestReplayDownloadButton downloadButton; + [Resolved] + private BeatmapManager beatmapManager { get; set; } + + [Resolved] + private ScoreManager scoreManager { get; set; } + + [BackgroundDependencyLoader] + private void load() + { + beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); + } + [Test] public void TestDisplayStates() { @@ -115,9 +129,6 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("button is not enabled", () => !downloadButton.ChildrenOfType().First().Enabled.Value); } - [Resolved] - private ScoreManager scoreManager { get; set; } - [Test] public void TestScoreImportThenDelete() { @@ -176,7 +187,7 @@ namespace osu.Game.Tests.Visual.Gameplay Id = 39828, Username = @"WubWoofWolf", } - }.CreateScoreInfo(rulesets, CreateBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo); + }.CreateScoreInfo(rulesets, beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps.First()); } private class TestReplayDownloadButton : ReplayDownloadButton From 5dc7425b2cd4c44802ee8b94002790c2a08befc6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 17 Jan 2022 14:54:03 +0900 Subject: [PATCH 416/996] Fix incorrect realm (non-isolated instance) being used in two test scenes --- .../Visual/SongSelect/TestSceneBeatmapLeaderboard.cs | 3 +-- .../Visual/UserInterface/TestSceneDeleteLocalScore.cs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 9ae15c387a..2e1a66be5f 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -11,7 +11,6 @@ using osu.Framework.Graphics; using osu.Framework.Platform; using osu.Framework.Testing; using osu.Game.Beatmaps; -using osu.Game.Database; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Leaderboards; using osu.Game.Overlays; @@ -45,7 +44,7 @@ namespace osu.Game.Tests.Visual.SongSelect dependencies.Cache(rulesetStore = new RulesetStore(ContextFactory)); dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesetStore, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default)); - dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, dependencies.Get(), Scheduler)); + dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, ContextFactory, Scheduler)); Dependencies.Cache(ContextFactory); return dependencies; diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index 02746ee8e8..1e14e4b3e5 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -89,7 +89,7 @@ namespace osu.Game.Tests.Visual.UserInterface dependencies.Cache(rulesetStore = new RulesetStore(ContextFactory)); dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesetStore, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default)); - dependencies.Cache(scoreManager = new ScoreManager(dependencies.Get(), () => beatmapManager, LocalStorage, dependencies.Get(), Scheduler)); + dependencies.Cache(scoreManager = new ScoreManager(dependencies.Get(), () => beatmapManager, LocalStorage, ContextFactory, Scheduler)); Dependencies.Cache(ContextFactory); var imported = beatmapManager.Import(new ImportTask(TestResources.GetQuickTestBeatmapForImport())).GetResultSafely(); From 51ade3251d68c9222a897852ce20e99b0d676077 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 17 Jan 2022 15:15:20 +0900 Subject: [PATCH 417/996] Improve `ScoresContainer` loading overlay logic to work better with tests --- osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index a40f29abf2..00dedc892b 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -65,6 +65,9 @@ namespace osu.Game.Overlays.BeatmapSet.Scores scoreTable.ClearScores(); scoreTable.Hide(); + loading.Hide(); + loading.FinishTransforms(); + if (value?.Scores.Any() != true) return; @@ -258,9 +261,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { Scores = null; notSupporterPlaceholder.Show(); - - loading.Hide(); - loading.FinishTransforms(); return; } @@ -272,9 +272,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores getScoresRequest = new GetScoresRequest(Beatmap.Value, Beatmap.Value.Ruleset, scope.Value, modSelector.SelectedMods); getScoresRequest.Success += scores => { - loading.Hide(); - loading.FinishTransforms(); - Scores = scores; if (!scores.Scores.Any()) From 6436b6186f6e3a1ad2262748138576b23334f750 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 17 Jan 2022 15:15:34 +0900 Subject: [PATCH 418/996] Rewrite `TestScenScoresContainer` to work again --- .../Visual/Online/TestSceneScoresContainer.cs | 440 +++++++++--------- 1 file changed, 231 insertions(+), 209 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs index be2db9a8a0..71b60d8842 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs @@ -1,11 +1,14 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; +using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; @@ -24,224 +27,38 @@ namespace osu.Game.Tests.Visual.Online [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); - public TestSceneScoresContainer() + private TestScoresContainer scoresContainer; + + [SetUpSteps] + public void SetUpSteps() { - TestScoresContainer scoresContainer; - - Child = new Container + AddStep("Create container", () => { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.Both, - Width = 0.8f, - Children = new Drawable[] + Child = new Container { - new Box + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Both, + Width = 0.8f, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - }, - scoresContainer = new TestScoresContainer(), - } - }; - - var allScores = new APIScoresCollection - { - Scores = new List - { - new APIScore - { - User = new APIUser + new Box { - Id = 6602580, - Username = @"waaiiru", - Country = new Country - { - FullName = @"Spain", - FlagName = @"ES", - }, + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, }, - Mods = new[] + scoresContainer = new TestScoresContainer { - new APIMod { Acronym = new OsuModDoubleTime().Acronym }, - new APIMod { Acronym = new OsuModHidden().Acronym }, - new APIMod { Acronym = new OsuModFlashlight().Acronym }, - new APIMod { Acronym = new OsuModHardRock().Acronym }, - }, - Rank = ScoreRank.XH, - PP = 200, - MaxCombo = 1234, - TotalScore = 1234567890, - Accuracy = 1, - }, - new APIScore - { - User = new APIUser - { - Id = 4608074, - Username = @"Skycries", - Country = new Country - { - FullName = @"Brazil", - FlagName = @"BR", - }, - }, - Mods = new[] - { - new APIMod { Acronym = new OsuModDoubleTime().Acronym }, - new APIMod { Acronym = new OsuModHidden().Acronym }, - new APIMod { Acronym = new OsuModFlashlight().Acronym }, - }, - Rank = ScoreRank.S, - PP = 190, - MaxCombo = 1234, - TotalScore = 1234789, - Accuracy = 0.9997, - }, - new APIScore - { - User = new APIUser - { - Id = 1014222, - Username = @"eLy", - Country = new Country - { - FullName = @"Japan", - FlagName = @"JP", - }, - }, - Mods = new[] - { - new APIMod { Acronym = new OsuModDoubleTime().Acronym }, - new APIMod { Acronym = new OsuModHidden().Acronym }, - }, - Rank = ScoreRank.B, - PP = 180, - MaxCombo = 1234, - TotalScore = 12345678, - Accuracy = 0.9854, - }, - new APIScore - { - User = new APIUser - { - Id = 1541390, - Username = @"Toukai", - Country = new Country - { - FullName = @"Canada", - FlagName = @"CA", - }, - }, - Mods = new[] - { - new APIMod { Acronym = new OsuModDoubleTime().Acronym }, - }, - Rank = ScoreRank.C, - PP = 170, - MaxCombo = 1234, - TotalScore = 1234567, - Accuracy = 0.8765, - }, - new APIScore - { - User = new APIUser - { - Id = 7151382, - Username = @"Mayuri Hana", - Country = new Country - { - FullName = @"Thailand", - FlagName = @"TH", - }, - }, - Rank = ScoreRank.D, - PP = 160, - MaxCombo = 1234, - TotalScore = 123456, - Accuracy = 0.6543, - }, - } - }; - - var myBestScore = new APIScoreWithPosition - { - Score = new APIScore - { - User = new APIUser - { - Id = 7151382, - Username = @"Mayuri Hana", - Country = new Country - { - FullName = @"Thailand", - FlagName = @"TH", - }, - }, - Rank = ScoreRank.D, - PP = 160, - MaxCombo = 1234, - TotalScore = 123456, - Accuracy = 0.6543, - }, - Position = 1337, - }; - - var myBestScoreWithNullPosition = new APIScoreWithPosition - { - Score = new APIScore - { - User = new APIUser - { - Id = 7151382, - Username = @"Mayuri Hana", - Country = new Country - { - FullName = @"Thailand", - FlagName = @"TH", - }, - }, - Rank = ScoreRank.D, - PP = 160, - MaxCombo = 1234, - TotalScore = 123456, - Accuracy = 0.6543, - }, - Position = null, - }; - - var oneScore = new APIScoresCollection - { - Scores = new List - { - new APIScore - { - User = new APIUser - { - Id = 6602580, - Username = @"waaiiru", - Country = new Country - { - FullName = @"Spain", - FlagName = @"ES", - }, - }, - Mods = new[] - { - new APIMod { Acronym = new OsuModDoubleTime().Acronym }, - new APIMod { Acronym = new OsuModHidden().Acronym }, - new APIMod { Acronym = new OsuModFlashlight().Acronym }, - new APIMod { Acronym = new OsuModHardRock().Acronym }, - }, - Rank = ScoreRank.XH, - PP = 200, - MaxCombo = 1234, - TotalScore = 1234567890, - Accuracy = 1, + Beatmap = { Value = CreateAPIBeatmap() } + } } - } - }; + }; + }); + } + [Test] + public void TestDisplay() + { foreach (var s in allScores.Scores) { s.Statistics = new Dictionary @@ -273,6 +90,211 @@ namespace osu.Game.Tests.Visual.Online }); } + private readonly APIScoresCollection allScores = new APIScoresCollection + { + Scores = new List + { + new APIScore + { + Date = DateTimeOffset.Now, + User = new APIUser + { + Id = 6602580, + Username = @"waaiiru", + Country = new Country + { + FullName = @"Spain", + FlagName = @"ES", + }, + }, + Mods = new[] + { + new APIMod { Acronym = new OsuModDoubleTime().Acronym }, + new APIMod { Acronym = new OsuModHidden().Acronym }, + new APIMod { Acronym = new OsuModFlashlight().Acronym }, + new APIMod { Acronym = new OsuModHardRock().Acronym }, + }, + Rank = ScoreRank.XH, + PP = 200, + MaxCombo = 1234, + TotalScore = 1234567890, + Accuracy = 1, + }, + new APIScore + { + Date = DateTimeOffset.Now, + User = new APIUser + { + Id = 4608074, + Username = @"Skycries", + Country = new Country + { + FullName = @"Brazil", + FlagName = @"BR", + }, + }, + Mods = new[] + { + new APIMod { Acronym = new OsuModDoubleTime().Acronym }, + new APIMod { Acronym = new OsuModHidden().Acronym }, + new APIMod { Acronym = new OsuModFlashlight().Acronym }, + }, + Rank = ScoreRank.S, + PP = 190, + MaxCombo = 1234, + TotalScore = 1234789, + Accuracy = 0.9997, + }, + new APIScore + { + Date = DateTimeOffset.Now, + User = new APIUser + { + Id = 1014222, + Username = @"eLy", + Country = new Country + { + FullName = @"Japan", + FlagName = @"JP", + }, + }, + Mods = new[] + { + new APIMod { Acronym = new OsuModDoubleTime().Acronym }, + new APIMod { Acronym = new OsuModHidden().Acronym }, + }, + Rank = ScoreRank.B, + PP = 180, + MaxCombo = 1234, + TotalScore = 12345678, + Accuracy = 0.9854, + }, + new APIScore + { + Date = DateTimeOffset.Now, + User = new APIUser + { + Id = 1541390, + Username = @"Toukai", + Country = new Country + { + FullName = @"Canada", + FlagName = @"CA", + }, + }, + Mods = new[] + { + new APIMod { Acronym = new OsuModDoubleTime().Acronym }, + }, + Rank = ScoreRank.C, + PP = 170, + MaxCombo = 1234, + TotalScore = 1234567, + Accuracy = 0.8765, + }, + new APIScore + { + Date = DateTimeOffset.Now, + User = new APIUser + { + Id = 7151382, + Username = @"Mayuri Hana", + Country = new Country + { + FullName = @"Thailand", + FlagName = @"TH", + }, + }, + Rank = ScoreRank.D, + PP = 160, + MaxCombo = 1234, + TotalScore = 123456, + Accuracy = 0.6543, + }, + } + }; + + private readonly APIScoreWithPosition myBestScore = new APIScoreWithPosition + { + Score = new APIScore + { + Date = DateTimeOffset.Now, + User = new APIUser + { + Id = 7151382, + Username = @"Mayuri Hana", + Country = new Country + { + FullName = @"Thailand", + FlagName = @"TH", + }, + }, + Rank = ScoreRank.D, + PP = 160, + MaxCombo = 1234, + TotalScore = 123456, + Accuracy = 0.6543, + }, + Position = 1337, + }; + + private readonly APIScoreWithPosition myBestScoreWithNullPosition = new APIScoreWithPosition + { + Score = new APIScore + { + Date = DateTimeOffset.Now, + User = new APIUser + { + Id = 7151382, + Username = @"Mayuri Hana", + Country = new Country + { + FullName = @"Thailand", + FlagName = @"TH", + }, + }, + Rank = ScoreRank.D, + PP = 160, + MaxCombo = 1234, + TotalScore = 123456, + Accuracy = 0.6543, + }, + Position = null, + }; + + private readonly APIScoresCollection oneScore = new APIScoresCollection + { + Scores = new List + { + new APIScore + { + Date = DateTimeOffset.Now, + User = new APIUser + { + Id = 6602580, + Username = @"waaiiru", + Country = new Country + { + FullName = @"Spain", + FlagName = @"ES", + }, + }, + Mods = new[] + { + new APIMod { Acronym = new OsuModDoubleTime().Acronym }, + new APIMod { Acronym = new OsuModHidden().Acronym }, + new APIMod { Acronym = new OsuModFlashlight().Acronym }, + new APIMod { Acronym = new OsuModHardRock().Acronym }, + }, + Rank = ScoreRank.XH, + PP = 200, + MaxCombo = 1234, + TotalScore = 1234567890, + Accuracy = 1, + } + } + }; + private class TestScoresContainer : ScoresContainer { public new APIScoresCollection Scores From 12fd279b7df1b3dbac8f1ede8fac3306a3320156 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 17 Jan 2022 15:59:14 +0900 Subject: [PATCH 419/996] Add test to check full flow of rebinding gameplay key bindings Addresses a regression found in realm PR that was not covered by tests. --- .../TestSceneChangeAndUseGameplayBindings.cs | 89 +++++++++++++++++++ .../Sections/Input/KeyBindingsSubsection.cs | 2 +- 2 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Tests/Visual/Navigation/TestSceneChangeAndUseGameplayBindings.cs diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneChangeAndUseGameplayBindings.cs b/osu.Game.Tests/Visual/Navigation/TestSceneChangeAndUseGameplayBindings.cs new file mode 100644 index 0000000000..7dda158ee1 --- /dev/null +++ b/osu.Game.Tests/Visual/Navigation/TestSceneChangeAndUseGameplayBindings.cs @@ -0,0 +1,89 @@ +// 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.Allocation; +using osu.Framework.Extensions; +using osu.Framework.Input.Bindings; +using osu.Framework.Testing; +using osu.Game.Database; +using osu.Game.Graphics.UserInterface; +using osu.Game.Input.Bindings; +using osu.Game.Overlays.Settings.Sections.Input; +using osu.Game.Screens.Play; +using osu.Game.Screens.Select; +using osu.Game.Tests.Beatmaps.IO; +using osu.Game.Tests.Database; +using osuTK.Input; + +namespace osu.Game.Tests.Visual.Navigation +{ + public class TestSceneChangeAndUseGameplayBindings : OsuGameTestScene + { + [Test] + public void TestGameplayKeyBindings() + { + AddAssert("databased key is default", () => firstOsuRulesetKeyBindings.KeyCombination.Keys.SequenceEqual(new[] { InputKey.Z })); + + AddStep("open settings", () => { Game.Settings.Show(); }); + + // Until step requires as settings has a delayed load. + AddUntilStep("wait for button", () => configureBindingsButton?.Enabled.Value == true); + AddStep("scroll to section", () => Game.Settings.SectionsContainer.ScrollTo(configureBindingsButton)); + AddStep("press button", () => configureBindingsButton.TriggerClick()); + AddUntilStep("wait for panel", () => keyBindingPanel?.IsLoaded == true); + AddUntilStep("wait for osu subsection", () => osuBindingSubsection?.IsLoaded == true); + AddStep("scroll to section", () => keyBindingPanel.SectionsContainer.ScrollTo(osuBindingSubsection)); + AddWaitStep("wait for scroll to end", 3); + AddStep("start rebinding first osu! key", () => + { + var button = osuBindingSubsection.ChildrenOfType().First(); + + InputManager.MoveMouseTo(button); + InputManager.Click(MouseButton.Left); + }); + + AddStep("Press 's'", () => InputManager.Key(Key.S)); + + AddUntilStep("wait for database updated", () => firstOsuRulesetKeyBindings.KeyCombination.Keys.SequenceEqual(new[] { InputKey.S })); + + AddStep("close settings", () => Game.Settings.Hide()); + + AddStep("import beatmap", () => ImportBeatmapTest.LoadQuickOszIntoOsu(Game).WaitSafely()); + PushAndConfirm(() => new PlaySongSelect()); + + AddStep("enter gameplay", () => InputManager.Key(Key.Enter)); + + AddUntilStep("wait for gameplay", () => player?.IsBreakTime.Value == false); + + AddStep("press 'z'", () => InputManager.Key(Key.Z)); + AddAssert("key counter didn't increase", () => keyCounter.CountPresses == 0); + + AddStep("press 's'", () => InputManager.Key(Key.S)); + AddAssert("key counter did increase", () => keyCounter.CountPresses == 1); + } + + private KeyBindingsSubsection osuBindingSubsection => keyBindingPanel + .ChildrenOfType() + .FirstOrDefault(s => s.Ruleset.ShortName == "osu"); + + private OsuButton configureBindingsButton => Game.Settings + .ChildrenOfType().SingleOrDefault()? + .ChildrenOfType()? + .First(b => b.Text.ToString() == "Configure"); + + private KeyBindingPanel keyBindingPanel => Game.Settings + .ChildrenOfType().SingleOrDefault(); + + private RealmKeyBinding firstOsuRulesetKeyBindings => Game.Dependencies + .Get().Context + .All() + .AsEnumerable() + .First(k => k.RulesetName == "osu" && k.ActionInt == 0); + + private Player player => Game.ScreenStack.CurrentScreen as Player; + + private KeyCounter keyCounter => player.ChildrenOfType().First(); + } +} diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs index 115a7bdc79..94c7c66538 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs @@ -18,7 +18,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input { protected IEnumerable Defaults; - protected RulesetInfo Ruleset; + public RulesetInfo Ruleset { get; protected set; } private readonly int? variant; From a80e461000737994c24d84ea20fabe34494d5c6d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 17 Jan 2022 16:23:58 +0900 Subject: [PATCH 420/996] Add better test coverage of user top scores --- .../Visual/Online/TestSceneScoresContainer.cs | 455 +++++++++--------- 1 file changed, 235 insertions(+), 220 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs index 71b60d8842..d4af01f772 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -30,36 +31,247 @@ namespace osu.Game.Tests.Visual.Online private TestScoresContainer scoresContainer; [SetUpSteps] - public void SetUpSteps() + public void SetUp() => Schedule(() => { - AddStep("Create container", () => + Child = new Container { - Child = new Container + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Both, + Width = 0.8f, + Children = new Drawable[] { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.Both, - Width = 0.8f, - Children = new Drawable[] + new Box { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - }, - scoresContainer = new TestScoresContainer - { - Beatmap = { Value = CreateAPIBeatmap() } - } + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + }, + scoresContainer = new TestScoresContainer + { + Beatmap = { Value = CreateAPIBeatmap() } } - }; + } + }; + }); + + [Test] + public void TestNoUserBest() + { + AddStep("Scores with no user best", () => + { + var allScores = createScores(); + + allScores.UserScore = null; + + scoresContainer.Scores = allScores; }); + + AddUntilStep("wait for scores displayed", () => scoresContainer.ChildrenOfType().Any()); + AddAssert("no user best displayed", () => scoresContainer.ChildrenOfType().Count() == 1); + + AddStep("Load null scores", () => scoresContainer.Scores = null); + + AddUntilStep("wait for scores not displayed", () => !scoresContainer.ChildrenOfType().Any()); + AddAssert("no best score displayed", () => !scoresContainer.ChildrenOfType().Any()); + + AddStep("Load only one score", () => + { + var allScores = createScores(); + + allScores.Scores.RemoveRange(1, allScores.Scores.Count - 1); + + scoresContainer.Scores = allScores; + }); + + AddUntilStep("wait for scores not displayed", () => scoresContainer.ChildrenOfType().Count() == 1); + AddAssert("no best score displayed", () => scoresContainer.ChildrenOfType().Count() == 1); } [Test] - public void TestDisplay() + public void TestUserBest() { - foreach (var s in allScores.Scores) + AddStep("Load scores with personal best", () => + { + var allScores = createScores(); + allScores.UserScore = createUserBest(); + scoresContainer.Scores = allScores; + }); + + AddUntilStep("wait for scores displayed", () => scoresContainer.ChildrenOfType().Any()); + AddAssert("best score displayed", () => scoresContainer.ChildrenOfType().Count() == 2); + + AddStep("Load scores with personal best (null position)", () => + { + var allScores = createScores(); + var userBest = createUserBest(); + userBest.Position = null; + allScores.UserScore = userBest; + scoresContainer.Scores = allScores; + }); + + AddUntilStep("wait for scores displayed", () => scoresContainer.ChildrenOfType().Any()); + AddAssert("best score displayed", () => scoresContainer.ChildrenOfType().Count() == 2); + + AddStep("Load scores with personal best (first place)", () => + { + var allScores = createScores(); + allScores.UserScore = new APIScoreWithPosition + { + Score = allScores.Scores.First(), + Position = 1, + }; + scoresContainer.Scores = allScores; + }); + + AddUntilStep("wait for scores displayed", () => scoresContainer.ChildrenOfType().Any()); + AddAssert("best score displayed", () => scoresContainer.ChildrenOfType().Count() == 1); + + AddStep("Scores with no user best", () => + { + var allScores = createScores(); + + allScores.UserScore = null; + + scoresContainer.Scores = allScores; + }); + + AddAssert("best score not displayed", () => scoresContainer.ChildrenOfType().Count() == 1); + } + + private int onlineID = 1; + + private APIScoresCollection createScores() + { + var scores = new APIScoresCollection + { + Scores = new List + { + new APIScore + { + Date = DateTimeOffset.Now, + OnlineID = onlineID++, + User = new APIUser + { + Id = 6602580, + Username = @"waaiiru", + Country = new Country + { + FullName = @"Spain", + FlagName = @"ES", + }, + }, + Mods = new[] + { + new APIMod { Acronym = new OsuModDoubleTime().Acronym }, + new APIMod { Acronym = new OsuModHidden().Acronym }, + new APIMod { Acronym = new OsuModFlashlight().Acronym }, + new APIMod { Acronym = new OsuModHardRock().Acronym }, + }, + Rank = ScoreRank.XH, + PP = 200, + MaxCombo = 1234, + TotalScore = 1234567890, + Accuracy = 1, + }, + new APIScore + { + Date = DateTimeOffset.Now, + OnlineID = onlineID++, + User = new APIUser + { + Id = 4608074, + Username = @"Skycries", + Country = new Country + { + FullName = @"Brazil", + FlagName = @"BR", + }, + }, + Mods = new[] + { + new APIMod { Acronym = new OsuModDoubleTime().Acronym }, + new APIMod { Acronym = new OsuModHidden().Acronym }, + new APIMod { Acronym = new OsuModFlashlight().Acronym }, + }, + Rank = ScoreRank.S, + PP = 190, + MaxCombo = 1234, + TotalScore = 1234789, + Accuracy = 0.9997, + }, + new APIScore + { + Date = DateTimeOffset.Now, + OnlineID = onlineID++, + User = new APIUser + { + Id = 1014222, + Username = @"eLy", + Country = new Country + { + FullName = @"Japan", + FlagName = @"JP", + }, + }, + Mods = new[] + { + new APIMod { Acronym = new OsuModDoubleTime().Acronym }, + new APIMod { Acronym = new OsuModHidden().Acronym }, + }, + Rank = ScoreRank.B, + PP = 180, + MaxCombo = 1234, + TotalScore = 12345678, + Accuracy = 0.9854, + }, + new APIScore + { + Date = DateTimeOffset.Now, + OnlineID = onlineID++, + User = new APIUser + { + Id = 1541390, + Username = @"Toukai", + Country = new Country + { + FullName = @"Canada", + FlagName = @"CA", + }, + }, + Mods = new[] + { + new APIMod { Acronym = new OsuModDoubleTime().Acronym }, + }, + Rank = ScoreRank.C, + PP = 170, + MaxCombo = 1234, + TotalScore = 1234567, + Accuracy = 0.8765, + }, + new APIScore + { + Date = DateTimeOffset.Now, + OnlineID = onlineID++, + User = new APIUser + { + Id = 7151382, + Username = @"Mayuri Hana", + Country = new Country + { + FullName = @"Thailand", + FlagName = @"TH", + }, + }, + Rank = ScoreRank.D, + PP = 160, + MaxCombo = 1234, + TotalScore = 123456, + Accuracy = 0.6543, + }, + } + }; + + foreach (var s in scores.Scores) { s.Statistics = new Dictionary { @@ -70,155 +282,15 @@ namespace osu.Game.Tests.Visual.Online }; } - AddStep("Load all scores", () => - { - allScores.UserScore = null; - scoresContainer.Scores = allScores; - }); - AddStep("Load null scores", () => scoresContainer.Scores = null); - AddStep("Load only one score", () => scoresContainer.Scores = oneScore); - AddStep("Load scores with my best", () => - { - allScores.UserScore = myBestScore; - scoresContainer.Scores = allScores; - }); - - AddStep("Load scores with null my best position", () => - { - allScores.UserScore = myBestScoreWithNullPosition; - scoresContainer.Scores = allScores; - }); + return scores; } - private readonly APIScoresCollection allScores = new APIScoresCollection - { - Scores = new List - { - new APIScore - { - Date = DateTimeOffset.Now, - User = new APIUser - { - Id = 6602580, - Username = @"waaiiru", - Country = new Country - { - FullName = @"Spain", - FlagName = @"ES", - }, - }, - Mods = new[] - { - new APIMod { Acronym = new OsuModDoubleTime().Acronym }, - new APIMod { Acronym = new OsuModHidden().Acronym }, - new APIMod { Acronym = new OsuModFlashlight().Acronym }, - new APIMod { Acronym = new OsuModHardRock().Acronym }, - }, - Rank = ScoreRank.XH, - PP = 200, - MaxCombo = 1234, - TotalScore = 1234567890, - Accuracy = 1, - }, - new APIScore - { - Date = DateTimeOffset.Now, - User = new APIUser - { - Id = 4608074, - Username = @"Skycries", - Country = new Country - { - FullName = @"Brazil", - FlagName = @"BR", - }, - }, - Mods = new[] - { - new APIMod { Acronym = new OsuModDoubleTime().Acronym }, - new APIMod { Acronym = new OsuModHidden().Acronym }, - new APIMod { Acronym = new OsuModFlashlight().Acronym }, - }, - Rank = ScoreRank.S, - PP = 190, - MaxCombo = 1234, - TotalScore = 1234789, - Accuracy = 0.9997, - }, - new APIScore - { - Date = DateTimeOffset.Now, - User = new APIUser - { - Id = 1014222, - Username = @"eLy", - Country = new Country - { - FullName = @"Japan", - FlagName = @"JP", - }, - }, - Mods = new[] - { - new APIMod { Acronym = new OsuModDoubleTime().Acronym }, - new APIMod { Acronym = new OsuModHidden().Acronym }, - }, - Rank = ScoreRank.B, - PP = 180, - MaxCombo = 1234, - TotalScore = 12345678, - Accuracy = 0.9854, - }, - new APIScore - { - Date = DateTimeOffset.Now, - User = new APIUser - { - Id = 1541390, - Username = @"Toukai", - Country = new Country - { - FullName = @"Canada", - FlagName = @"CA", - }, - }, - Mods = new[] - { - new APIMod { Acronym = new OsuModDoubleTime().Acronym }, - }, - Rank = ScoreRank.C, - PP = 170, - MaxCombo = 1234, - TotalScore = 1234567, - Accuracy = 0.8765, - }, - new APIScore - { - Date = DateTimeOffset.Now, - User = new APIUser - { - Id = 7151382, - Username = @"Mayuri Hana", - Country = new Country - { - FullName = @"Thailand", - FlagName = @"TH", - }, - }, - Rank = ScoreRank.D, - PP = 160, - MaxCombo = 1234, - TotalScore = 123456, - Accuracy = 0.6543, - }, - } - }; - - private readonly APIScoreWithPosition myBestScore = new APIScoreWithPosition + private APIScoreWithPosition createUserBest() => new APIScoreWithPosition { Score = new APIScore { Date = DateTimeOffset.Now, + OnlineID = onlineID++, User = new APIUser { Id = 7151382, @@ -238,63 +310,6 @@ namespace osu.Game.Tests.Visual.Online Position = 1337, }; - private readonly APIScoreWithPosition myBestScoreWithNullPosition = new APIScoreWithPosition - { - Score = new APIScore - { - Date = DateTimeOffset.Now, - User = new APIUser - { - Id = 7151382, - Username = @"Mayuri Hana", - Country = new Country - { - FullName = @"Thailand", - FlagName = @"TH", - }, - }, - Rank = ScoreRank.D, - PP = 160, - MaxCombo = 1234, - TotalScore = 123456, - Accuracy = 0.6543, - }, - Position = null, - }; - - private readonly APIScoresCollection oneScore = new APIScoresCollection - { - Scores = new List - { - new APIScore - { - Date = DateTimeOffset.Now, - User = new APIUser - { - Id = 6602580, - Username = @"waaiiru", - Country = new Country - { - FullName = @"Spain", - FlagName = @"ES", - }, - }, - Mods = new[] - { - new APIMod { Acronym = new OsuModDoubleTime().Acronym }, - new APIMod { Acronym = new OsuModHidden().Acronym }, - new APIMod { Acronym = new OsuModFlashlight().Acronym }, - new APIMod { Acronym = new OsuModHardRock().Acronym }, - }, - Rank = ScoreRank.XH, - PP = 200, - MaxCombo = 1234, - TotalScore = 1234567890, - Accuracy = 1, - } - } - }; - private class TestScoresContainer : ScoresContainer { public new APIScoresCollection Scores From f6b9d36acffcbce19314e52bfb1eac45bbb1fc8a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 17 Jan 2022 17:06:04 +0900 Subject: [PATCH 421/996] Remove unused using --- .../Visual/Navigation/TestSceneChangeAndUseGameplayBindings.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneChangeAndUseGameplayBindings.cs b/osu.Game.Tests/Visual/Navigation/TestSceneChangeAndUseGameplayBindings.cs index 7dda158ee1..7a44a7643d 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneChangeAndUseGameplayBindings.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneChangeAndUseGameplayBindings.cs @@ -14,7 +14,6 @@ using osu.Game.Overlays.Settings.Sections.Input; using osu.Game.Screens.Play; using osu.Game.Screens.Select; using osu.Game.Tests.Beatmaps.IO; -using osu.Game.Tests.Database; using osuTK.Input; namespace osu.Game.Tests.Visual.Navigation From 511a607599215e8981321e16df99c4a32918f67c Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Mon, 17 Jan 2022 18:28:17 +0800 Subject: [PATCH 422/996] Display performance breakdown in a tooltip --- .../Difficulty/ManiaPerformanceAttributes.cs | 11 +++ .../Difficulty/OsuPerformanceAttributes.cs | 13 +++ .../Difficulty/TaikoPerformanceAttributes.cs | 10 +++ .../Difficulty/PerformanceAttributes.cs | 10 +++ .../Difficulty/PerformanceDisplayAttribute.cs | 26 ++++++ osu.Game/Scoring/ScorePerformanceCache.cs | 9 ++- .../Statistics/PerformanceStatistic.cs | 30 ++++--- .../Statistics/PerformanceStatisticTooltip.cs | 80 +++++++++++++++++++ 8 files changed, 172 insertions(+), 17 deletions(-) create mode 100644 osu.Game/Rulesets/Difficulty/PerformanceDisplayAttribute.cs create mode 100644 osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceAttributes.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceAttributes.cs index da9634ba47..0d3a53f3f3 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceAttributes.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceAttributes.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.Collections.Generic; using Newtonsoft.Json; using osu.Game.Rulesets.Difficulty; @@ -16,5 +17,15 @@ namespace osu.Game.Rulesets.Mania.Difficulty [JsonProperty("scaled_score")] public double ScaledScore { get; set; } + + public override IEnumerable GetAttributesForDisplay() + { + foreach (var attribute in base.GetAttributesForDisplay()) + yield return attribute; + + yield return new PerformanceDisplayAttribute("Difficulty", Difficulty); + yield return new PerformanceDisplayAttribute("Accuracy", Accuracy); + yield return new PerformanceDisplayAttribute("Scaled Score", ScaledScore); + } } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs index 6c7760d144..db7ca6af88 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.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.Collections.Generic; using Newtonsoft.Json; using osu.Game.Rulesets.Difficulty; @@ -22,5 +23,17 @@ namespace osu.Game.Rulesets.Osu.Difficulty [JsonProperty("effective_miss_count")] public double EffectiveMissCount { get; set; } + + public override IEnumerable GetAttributesForDisplay() + { + foreach (var attribute in base.GetAttributesForDisplay()) + yield return attribute; + + yield return new PerformanceDisplayAttribute("Aim", Aim); + yield return new PerformanceDisplayAttribute("Speed", Speed); + yield return new PerformanceDisplayAttribute("Accuracy", Accuracy); + yield return new PerformanceDisplayAttribute("Flashlight Bonus", Flashlight); + yield return new PerformanceDisplayAttribute("Effective Miss Count", EffectiveMissCount); + } } } diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs index 80552880ea..efbcdd3703 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.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.Collections.Generic; using Newtonsoft.Json; using osu.Game.Rulesets.Difficulty; @@ -13,5 +14,14 @@ namespace osu.Game.Rulesets.Taiko.Difficulty [JsonProperty("accuracy")] public double Accuracy { get; set; } + + public override IEnumerable GetAttributesForDisplay() + { + foreach (var attribute in base.GetAttributesForDisplay()) + yield return attribute; + + yield return new PerformanceDisplayAttribute("Difficulty", Difficulty); + yield return new PerformanceDisplayAttribute("Accuracy", Accuracy); + } } } diff --git a/osu.Game/Rulesets/Difficulty/PerformanceAttributes.cs b/osu.Game/Rulesets/Difficulty/PerformanceAttributes.cs index 025b38257c..98c6c75f6c 100644 --- a/osu.Game/Rulesets/Difficulty/PerformanceAttributes.cs +++ b/osu.Game/Rulesets/Difficulty/PerformanceAttributes.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.Collections.Generic; using Newtonsoft.Json; namespace osu.Game.Rulesets.Difficulty @@ -12,5 +13,14 @@ namespace osu.Game.Rulesets.Difficulty /// [JsonProperty("pp")] public double Total { get; set; } + + /// + /// Return a for each attribute so that a performance breakdown can be displayed. + /// + /// + public virtual IEnumerable GetAttributesForDisplay() + { + yield return new PerformanceDisplayAttribute("Total", Total); + } } } diff --git a/osu.Game/Rulesets/Difficulty/PerformanceDisplayAttribute.cs b/osu.Game/Rulesets/Difficulty/PerformanceDisplayAttribute.cs new file mode 100644 index 0000000000..5bb505847e --- /dev/null +++ b/osu.Game/Rulesets/Difficulty/PerformanceDisplayAttribute.cs @@ -0,0 +1,26 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +namespace osu.Game.Rulesets.Difficulty +{ + /// + /// Data for displaying a performance attribute to user. Includes a display name for clarity. + /// + public class PerformanceDisplayAttribute + { + /// + /// A custom display name for the attribute. + /// + public string DisplayName { get; } + + /// + /// The associated attribute value. + /// + public double Value { get; } + + public PerformanceDisplayAttribute(string displayName, double value) + { + DisplayName = displayName; + Value = value; + } + } +} diff --git a/osu.Game/Scoring/ScorePerformanceCache.cs b/osu.Game/Scoring/ScorePerformanceCache.cs index b855343505..a428a66aae 100644 --- a/osu.Game/Scoring/ScorePerformanceCache.cs +++ b/osu.Game/Scoring/ScorePerformanceCache.cs @@ -8,6 +8,7 @@ using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Game.Beatmaps; using osu.Game.Database; +using osu.Game.Rulesets.Difficulty; namespace osu.Game.Scoring { @@ -15,7 +16,7 @@ namespace osu.Game.Scoring /// A component which performs and acts as a central cache for performance calculations of locally databased scores. /// Currently not persisted between game sessions. /// - public class ScorePerformanceCache : MemoryCachingComponent + public class ScorePerformanceCache : MemoryCachingComponent { [Resolved] private BeatmapDifficultyCache difficultyCache { get; set; } @@ -27,10 +28,10 @@ namespace osu.Game.Scoring /// /// The score to do the calculation on. /// An optional to cancel the operation. - public Task CalculatePerformanceAsync([NotNull] ScoreInfo score, CancellationToken token = default) => + public Task CalculatePerformanceAsync([NotNull] ScoreInfo score, CancellationToken token = default) => GetAsync(new PerformanceCacheLookup(score), token); - protected override async Task ComputeValueAsync(PerformanceCacheLookup lookup, CancellationToken token = default) + protected override async Task ComputeValueAsync(PerformanceCacheLookup lookup, CancellationToken token = default) { var score = lookup.ScoreInfo; @@ -44,7 +45,7 @@ namespace osu.Game.Scoring var calculator = score.Ruleset.CreateInstance().CreatePerformanceCalculator(attributes.Value.Attributes, score); - return calculator?.Calculate().Total; + return calculator?.Calculate(); } public readonly struct PerformanceCacheLookup diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs index d6e4cfbe51..888552e568 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs @@ -7,12 +7,14 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Cursor; using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets.Difficulty; using osu.Game.Scoring; namespace osu.Game.Screens.Ranking.Expanded.Statistics { - public class PerformanceStatistic : StatisticDisplay + public class PerformanceStatistic : StatisticDisplay, IHasCustomTooltip { private readonly ScoreInfo score; @@ -22,6 +24,8 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics private RollingCounter counter; + private PerformanceAttributes attributes; + public PerformanceStatistic(ScoreInfo score) : base("PP") { @@ -31,21 +35,17 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics [BackgroundDependencyLoader] private void load(ScorePerformanceCache performanceCache) { - if (score.PP.HasValue) - { - setPerformanceValue(score.PP.Value); - } - else - { - performanceCache.CalculatePerformanceAsync(score, cancellationTokenSource.Token) - .ContinueWith(t => Schedule(() => setPerformanceValue(t.GetResultSafely())), cancellationTokenSource.Token); - } + performanceCache.CalculatePerformanceAsync(score, cancellationTokenSource.Token) + .ContinueWith(t => Schedule(() => setPerformanceValue(t.GetResultSafely())), cancellationTokenSource.Token); } - private void setPerformanceValue(double? pp) + private void setPerformanceValue(PerformanceAttributes pp) { - if (pp.HasValue) - performance.Value = (int)Math.Round(pp.Value, MidpointRounding.AwayFromZero); + if (pp != null) + { + attributes = pp; + performance.Value = (int)Math.Round(pp.Total, MidpointRounding.AwayFromZero); + } } public override void Appear() @@ -65,5 +65,9 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre }; + + public ITooltip GetCustomTooltip() => new PerformanceStatisticTooltip(); + + public PerformanceAttributes TooltipContent => attributes; } } diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs new file mode 100644 index 0000000000..32ba231e42 --- /dev/null +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs @@ -0,0 +1,80 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +using System; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Difficulty; +using osuTK; + +namespace osu.Game.Screens.Ranking.Expanded.Statistics +{ + public class PerformanceStatisticTooltip : VisibilityContainer, ITooltip + { + private readonly Box background; + + protected override Container Content { get; } + + public PerformanceStatisticTooltip() + { + AutoSizeAxes = Axes.Both; + Masking = true; + CornerRadius = 5; + + InternalChildren = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both + }, + Content = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Padding = new MarginPadding { Left = 10, Right = 10, Top = 5, Bottom = 5 } + }, + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + background.Colour = colours.Gray3; + } + + protected override void PopIn() => this.FadeIn(200, Easing.OutQuint); + protected override void PopOut() => this.FadeOut(200, Easing.OutQuint); + + private PerformanceAttributes lastAttributes; + + public void SetContent(PerformanceAttributes attributes) + { + if (attributes == lastAttributes) + return; + + lastAttributes = attributes; + + UpdateDisplay(attributes); + } + + protected virtual void UpdateDisplay(PerformanceAttributes attributes) + { + Content.Clear(); + + foreach (PerformanceDisplayAttribute attr in attributes.GetAttributesForDisplay()) + { + Content.Add(new OsuSpriteText + { + Font = OsuFont.GetFont(weight: FontWeight.Regular), + Text = $"{attr.DisplayName}: {(int)Math.Round(attr.Value, MidpointRounding.AwayFromZero)}" + }); + } + } + + public void Move(Vector2 pos) => Position = pos; + } +} From 0ba0b5f11abb083bcdf29f41b8b229fc160b5e4b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 17 Jan 2022 19:45:32 +0900 Subject: [PATCH 423/996] Fix test step not being `until` when it needs to be --- osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs index d4af01f772..8a304110dd 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs @@ -135,7 +135,7 @@ namespace osu.Game.Tests.Visual.Online scoresContainer.Scores = allScores; }); - AddAssert("best score not displayed", () => scoresContainer.ChildrenOfType().Count() == 1); + AddUntilStep("best score not displayed", () => scoresContainer.ChildrenOfType().Count() == 1); } private int onlineID = 1; From 85c60bfc2d50b1c5b27348c7a8e821fbeca3b340 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Mon, 17 Jan 2022 20:36:18 +0800 Subject: [PATCH 424/996] Improve tooltip design --- .../Statistics/PerformanceStatisticTooltip.cs | 34 +++++++++++++++++-- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs index 32ba231e42..618a94a309 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.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.Globalization; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -16,6 +17,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics public class PerformanceStatisticTooltip : VisibilityContainer, ITooltip { private readonly Box background; + private Colour4 textColor; protected override Container Content { get; } @@ -44,6 +46,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics private void load(OsuColour colours) { background.Colour = colours.Gray3; + textColor = colours.BlueLighter; } protected override void PopIn() => this.FadeIn(200, Easing.OutQuint); @@ -67,10 +70,35 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics foreach (PerformanceDisplayAttribute attr in attributes.GetAttributesForDisplay()) { - Content.Add(new OsuSpriteText + Content.Add(new GridContainer { - Font = OsuFont.GetFont(weight: FontWeight.Regular), - Text = $"{attr.DisplayName}: {(int)Math.Round(attr.Value, MidpointRounding.AwayFromZero)}" + AutoSizeAxes = Axes.Both, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.Absolute, 140), + new Dimension(GridSizeMode.AutoSize) + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize) + }, + Content = new[] + { + new Drawable[] + { + new OsuSpriteText + { + Font = OsuFont.GetFont(weight: FontWeight.Regular), + Text = attr.DisplayName, + Colour = textColor + }, + new OsuSpriteText + { + Font = OsuFont.GetFont(weight: FontWeight.SemiBold), + Text = ((int)Math.Round(attr.Value, MidpointRounding.AwayFromZero)).ToString(CultureInfo.CurrentCulture) + } + } + } }); } } From d014fef179a9248afc4f4b09237fa84fb16beeaf Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Mon, 17 Jan 2022 20:36:36 +0800 Subject: [PATCH 425/996] Hide confusing attributes --- osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceAttributes.cs | 1 - osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs | 1 - osu.Game/Rulesets/Difficulty/PerformanceAttributes.cs | 1 + 3 files changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceAttributes.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceAttributes.cs index 0d3a53f3f3..48895cd389 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceAttributes.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceAttributes.cs @@ -25,7 +25,6 @@ namespace osu.Game.Rulesets.Mania.Difficulty yield return new PerformanceDisplayAttribute("Difficulty", Difficulty); yield return new PerformanceDisplayAttribute("Accuracy", Accuracy); - yield return new PerformanceDisplayAttribute("Scaled Score", ScaledScore); } } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs index db7ca6af88..0a685b7cd6 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs @@ -33,7 +33,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty yield return new PerformanceDisplayAttribute("Speed", Speed); yield return new PerformanceDisplayAttribute("Accuracy", Accuracy); yield return new PerformanceDisplayAttribute("Flashlight Bonus", Flashlight); - yield return new PerformanceDisplayAttribute("Effective Miss Count", EffectiveMissCount); } } } diff --git a/osu.Game/Rulesets/Difficulty/PerformanceAttributes.cs b/osu.Game/Rulesets/Difficulty/PerformanceAttributes.cs index 98c6c75f6c..ee1868ecff 100644 --- a/osu.Game/Rulesets/Difficulty/PerformanceAttributes.cs +++ b/osu.Game/Rulesets/Difficulty/PerformanceAttributes.cs @@ -16,6 +16,7 @@ namespace osu.Game.Rulesets.Difficulty /// /// Return a for each attribute so that a performance breakdown can be displayed. + /// Some attributes may be omitted if they are not meant for display. /// /// public virtual IEnumerable GetAttributesForDisplay() From b81fc675e89b6df6161e18a45f6ae392fdbd183a Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Mon, 17 Jan 2022 20:45:25 +0800 Subject: [PATCH 426/996] Include PropertyName in PerformanceDisplayAttribute --- .../Difficulty/ManiaPerformanceAttributes.cs | 4 ++-- .../Difficulty/OsuPerformanceAttributes.cs | 8 ++++---- .../Difficulty/TaikoPerformanceAttributes.cs | 4 ++-- osu.Game/Rulesets/Difficulty/PerformanceAttributes.cs | 2 +- .../Rulesets/Difficulty/PerformanceDisplayAttribute.cs | 8 +++++++- 5 files changed, 16 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceAttributes.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceAttributes.cs index 48895cd389..17c864a268 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceAttributes.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceAttributes.cs @@ -23,8 +23,8 @@ namespace osu.Game.Rulesets.Mania.Difficulty foreach (var attribute in base.GetAttributesForDisplay()) yield return attribute; - yield return new PerformanceDisplayAttribute("Difficulty", Difficulty); - yield return new PerformanceDisplayAttribute("Accuracy", Accuracy); + yield return new PerformanceDisplayAttribute(nameof(Difficulty), "Difficulty", Difficulty); + yield return new PerformanceDisplayAttribute(nameof(Accuracy), "Accuracy", Accuracy); } } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs index 0a685b7cd6..0aeaf7669f 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs @@ -29,10 +29,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty foreach (var attribute in base.GetAttributesForDisplay()) yield return attribute; - yield return new PerformanceDisplayAttribute("Aim", Aim); - yield return new PerformanceDisplayAttribute("Speed", Speed); - yield return new PerformanceDisplayAttribute("Accuracy", Accuracy); - yield return new PerformanceDisplayAttribute("Flashlight Bonus", Flashlight); + yield return new PerformanceDisplayAttribute(nameof(Aim), "Aim", Aim); + yield return new PerformanceDisplayAttribute(nameof(Speed), "Speed", Speed); + yield return new PerformanceDisplayAttribute(nameof(Accuracy), "Accuracy", Accuracy); + yield return new PerformanceDisplayAttribute(nameof(Flashlight), "Flashlight Bonus", Flashlight); } } } diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs index efbcdd3703..fa5c0202dd 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs @@ -20,8 +20,8 @@ namespace osu.Game.Rulesets.Taiko.Difficulty foreach (var attribute in base.GetAttributesForDisplay()) yield return attribute; - yield return new PerformanceDisplayAttribute("Difficulty", Difficulty); - yield return new PerformanceDisplayAttribute("Accuracy", Accuracy); + yield return new PerformanceDisplayAttribute(nameof(Difficulty), "Difficulty", Difficulty); + yield return new PerformanceDisplayAttribute(nameof(Accuracy), "Accuracy", Accuracy); } } } diff --git a/osu.Game/Rulesets/Difficulty/PerformanceAttributes.cs b/osu.Game/Rulesets/Difficulty/PerformanceAttributes.cs index ee1868ecff..9bdb5f8f6f 100644 --- a/osu.Game/Rulesets/Difficulty/PerformanceAttributes.cs +++ b/osu.Game/Rulesets/Difficulty/PerformanceAttributes.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Difficulty /// public virtual IEnumerable GetAttributesForDisplay() { - yield return new PerformanceDisplayAttribute("Total", Total); + yield return new PerformanceDisplayAttribute(nameof(Total), "Total", Total); } } } diff --git a/osu.Game/Rulesets/Difficulty/PerformanceDisplayAttribute.cs b/osu.Game/Rulesets/Difficulty/PerformanceDisplayAttribute.cs index 5bb505847e..e95cb03053 100644 --- a/osu.Game/Rulesets/Difficulty/PerformanceDisplayAttribute.cs +++ b/osu.Game/Rulesets/Difficulty/PerformanceDisplayAttribute.cs @@ -7,6 +7,11 @@ namespace osu.Game.Rulesets.Difficulty /// public class PerformanceDisplayAttribute { + /// + /// Name of the attribute property in . + /// + public string PropertyName { get; } + /// /// A custom display name for the attribute. /// @@ -17,8 +22,9 @@ namespace osu.Game.Rulesets.Difficulty /// public double Value { get; } - public PerformanceDisplayAttribute(string displayName, double value) + public PerformanceDisplayAttribute(string propertyName, string displayName, double value) { + PropertyName = propertyName; DisplayName = displayName; Value = value; } From c49cd60487584fd8c2d6e208bc9158942e6ce519 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Mon, 17 Jan 2022 21:26:55 +0800 Subject: [PATCH 427/996] Add bar chart to tooltip --- .../Statistics/PerformanceStatisticTooltip.cs | 108 ++++++++++++------ 1 file changed, 74 insertions(+), 34 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs index 618a94a309..ab7156e0c5 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs @@ -2,22 +2,27 @@ // See the LICENCE file in the repository root for full licence text. using System; using System.Globalization; +using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Difficulty; using osuTK; +using osuTK.Graphics; namespace osu.Game.Screens.Ranking.Expanded.Statistics { public class PerformanceStatisticTooltip : VisibilityContainer, ITooltip { private readonly Box background; - private Colour4 textColor; + private Colour4 totalColour; + private Colour4 textColour; protected override Container Content { get; } @@ -46,10 +51,16 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics private void load(OsuColour colours) { background.Colour = colours.Gray3; - textColor = colours.BlueLighter; + totalColour = colours.Blue; + textColour = colours.BlueLighter; + } + + protected override void PopIn() + { + if (lastAttributes.GetAttributesForDisplay().Count() > 1) + this.FadeIn(200, Easing.OutQuint); } - protected override void PopIn() => this.FadeIn(200, Easing.OutQuint); protected override void PopOut() => this.FadeOut(200, Easing.OutQuint); private PerformanceAttributes lastAttributes; @@ -64,42 +75,71 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics UpdateDisplay(attributes); } + private Drawable createAttributeItem(PerformanceDisplayAttribute attribute, double attributeSum) + { + bool isTotal = attribute.PropertyName == nameof(PerformanceAttributes.Total); + return new GridContainer + { + AutoSizeAxes = Axes.Both, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.Absolute, 110), + new Dimension(GridSizeMode.Absolute, 140), + new Dimension(GridSizeMode.AutoSize) + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize) + }, + Content = new[] + { + new Drawable[] + { + new OsuSpriteText + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Font = OsuFont.GetFont(weight: FontWeight.Regular), + Text = attribute.DisplayName, + Colour = isTotal ? totalColour : textColour + }, + new Bar + { + Alpha = isTotal ? 0 : 1, + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Width = 130, + Height = 5, + BackgroundColour = Color4.White.Opacity(0.5f), + Length = (float)(attribute.Value / attributeSum), + Margin = new MarginPadding { Left = 5, Right = 5 } + }, + new OsuSpriteText + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Font = OsuFont.GetFont(weight: FontWeight.SemiBold), + Text = ((int)Math.Round(attribute.Value, MidpointRounding.AwayFromZero)).ToString(CultureInfo.CurrentCulture), + Colour = isTotal ? totalColour : textColour + } + } + } + }; + } + protected virtual void UpdateDisplay(PerformanceAttributes attributes) { Content.Clear(); - foreach (PerformanceDisplayAttribute attr in attributes.GetAttributesForDisplay()) + var displayAttributes = attributes.GetAttributesForDisplay(); + + double attributeSum = displayAttributes + .Where(attr => attr.PropertyName != nameof(PerformanceAttributes.Total)) + .Sum(attr => attr.Value); + + foreach (PerformanceDisplayAttribute attr in displayAttributes) { - Content.Add(new GridContainer - { - AutoSizeAxes = Axes.Both, - ColumnDimensions = new[] - { - new Dimension(GridSizeMode.Absolute, 140), - new Dimension(GridSizeMode.AutoSize) - }, - RowDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize) - }, - Content = new[] - { - new Drawable[] - { - new OsuSpriteText - { - Font = OsuFont.GetFont(weight: FontWeight.Regular), - Text = attr.DisplayName, - Colour = textColor - }, - new OsuSpriteText - { - Font = OsuFont.GetFont(weight: FontWeight.SemiBold), - Text = ((int)Math.Round(attr.Value, MidpointRounding.AwayFromZero)).ToString(CultureInfo.CurrentCulture) - } - } - } - }); + Content.Add(createAttributeItem(attr, attributeSum)); } } From eddf45329445f8b43cb37b65cbeaa3d3bce0f332 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Mon, 17 Jan 2022 21:32:13 +0800 Subject: [PATCH 428/996] Fix code quality issues --- osu.Game/Rulesets/Difficulty/PerformanceDisplayAttribute.cs | 1 + .../Ranking/Expanded/Statistics/PerformanceStatistic.cs | 6 ++---- .../Expanded/Statistics/PerformanceStatisticTooltip.cs | 1 + 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Difficulty/PerformanceDisplayAttribute.cs b/osu.Game/Rulesets/Difficulty/PerformanceDisplayAttribute.cs index e95cb03053..7958bc174e 100644 --- a/osu.Game/Rulesets/Difficulty/PerformanceDisplayAttribute.cs +++ b/osu.Game/Rulesets/Difficulty/PerformanceDisplayAttribute.cs @@ -1,5 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. + namespace osu.Game.Rulesets.Difficulty { /// diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs index 888552e568..4fd6964a68 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs @@ -24,8 +24,6 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics private RollingCounter counter; - private PerformanceAttributes attributes; - public PerformanceStatistic(ScoreInfo score) : base("PP") { @@ -43,7 +41,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics { if (pp != null) { - attributes = pp; + TooltipContent = pp; performance.Value = (int)Math.Round(pp.Total, MidpointRounding.AwayFromZero); } } @@ -68,6 +66,6 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics public ITooltip GetCustomTooltip() => new PerformanceStatisticTooltip(); - public PerformanceAttributes TooltipContent => attributes; + public PerformanceAttributes TooltipContent { get; private set; } } } diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs index ab7156e0c5..e0bbf91381 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs @@ -1,5 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. + using System; using System.Globalization; using System.Linq; From 83387cb00bcb89b9974b8bcc223b56f78cb72f3e Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Mon, 17 Jan 2022 21:41:17 +0800 Subject: [PATCH 429/996] Add a comment --- .../Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs index e0bbf91381..deef30124c 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs @@ -58,6 +58,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics protected override void PopIn() { + // Don't display the tooltip if "Total" is the only item if (lastAttributes.GetAttributesForDisplay().Count() > 1) this.FadeIn(200, Easing.OutQuint); } From bd308ca38c5afbddbc18cebaa4e8effa1d4cca90 Mon Sep 17 00:00:00 2001 From: mk-56 Date: Mon, 17 Jan 2022 15:15:25 +0100 Subject: [PATCH 430/996] Cleanup --- .../Mods/ManiaModFlashlight.cs | 2 -- osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs | 15 ++------------- .../Mods/TaikoModFlashlight.cs | 1 - 3 files changed, 2 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs index 676b5f3842..4fff736c57 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs @@ -55,5 +55,3 @@ namespace osu.Game.Rulesets.Mania.Mods } } } - - diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs index f15527460c..f381d14ffe 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs @@ -27,6 +27,8 @@ namespace osu.Game.Rulesets.Osu.Mods private const double default_follow_delay = 120; + + private OsuFlashlight flashlight; public override Flashlight CreateFlashlight() => flashlight = new OsuFlashlight(ChangeRadius.Value, InitialRadius.Value, FollowDelay.Value); @@ -77,19 +79,6 @@ namespace osu.Game.Rulesets.Osu.Mods return base.OnMouseMove(e); } - private float getSizeFor(int combo) - { - if (ChangeRadius) - { - if (combo > 200) - return InitialRadius * 0.8f; - else if (combo > 100) - return InitialRadius * 0.9f; - } - - return InitialRadius; - } - protected override void OnComboChange(ValueChangedEvent e) { this.TransformTo(nameof(FlashlightSize), new Vector2(0, GetRadiusFor(e.NewValue)), FLASHLIGHT_FADE_DURATION); diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs index 29f29863c0..76f7c45b75 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs @@ -48,7 +48,6 @@ namespace osu.Game.Rulesets.Taiko.Mods private Vector2 getSizeFor(int combo) { // Preserve flashlight size through the playfield's aspect adjustment. - // return new Vector2(0, size * taikoPlayfield.DrawHeight / TaikoPlayfield.DEFAULT_HEIGHT); return new Vector2(0, GetRadiusFor(combo) * taikoPlayfield.DrawHeight / TaikoPlayfield.DEFAULT_HEIGHT); } From 125439d17760b6ad2ecba6d5ac0a25d404cd5d9a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 Jan 2022 11:09:26 +0900 Subject: [PATCH 431/996] Update all (non-NET6) nuget packages --- .../osu.Game.Rulesets.EmptyFreeform.Tests.csproj | 2 +- .../osu.Game.Rulesets.Pippidon.Tests.csproj | 2 +- .../osu.Game.Rulesets.EmptyScrolling.Tests.csproj | 2 +- .../osu.Game.Rulesets.Pippidon.Tests.csproj | 2 +- osu.Game.Benchmarks/osu.Game.Benchmarks.csproj | 2 +- .../osu.Game.Rulesets.Catch.Tests.csproj | 2 +- .../osu.Game.Rulesets.Mania.Tests.csproj | 2 +- .../osu.Game.Rulesets.Osu.Tests.csproj | 2 +- .../osu.Game.Rulesets.Taiko.Tests.csproj | 2 +- osu.Game.Tests/osu.Game.Tests.csproj | 2 +- .../osu.Game.Tournament.Tests.csproj | 2 +- osu.Game/osu.Game.csproj | 8 ++++---- 12 files changed, 15 insertions(+), 15 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 3c6aaa39ca..a7078f1c09 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 @@ -12,7 +12,7 @@ - + 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 0719dd30df..c133b0e3f8 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 @@ -12,7 +12,7 @@ - + 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 d0db43cc81..92b48470e8 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 @@ -12,7 +12,7 @@ - + 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 0719dd30df..c133b0e3f8 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 @@ -12,7 +12,7 @@ - + diff --git a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj index 57b914bee6..a8599f2cb6 100644 --- a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj +++ b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj @@ -9,7 +9,7 @@ - + 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 13f2e25f05..d5496b7479 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 @@ -4,7 +4,7 @@ - + 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 d51a6da4f9..2f12b1535e 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 @@ -4,7 +4,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 fea2e408f6..e5b2e070d8 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 @@ -5,7 +5,7 @@ - + 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 ad3713e047..e48d80323a 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 @@ -4,7 +4,7 @@ - + diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index 3b115d43e5..c64ef918e3 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -6,7 +6,7 @@ - + diff --git a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj index 130fcfaca1..fb09a7be1e 100644 --- a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj +++ b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj @@ -7,7 +7,7 @@ - + WinExe diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 2e5c9f4548..5508cac93e 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -18,9 +18,9 @@ - + - + @@ -35,10 +35,10 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + - + From 911a837f62b371ec7b4c119ff43af3f16c2658bb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 Jan 2022 11:09:35 +0900 Subject: [PATCH 432/996] 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 b2e3b32916..18e63bb219 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 5508cac93e..758575e74a 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 897be33c18..d6c4533538 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -60,7 +60,7 @@ - + @@ -83,7 +83,7 @@ - + From f9c5000774d08c3f9c7e6f561205d0c082326590 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 Jan 2022 11:27:28 +0900 Subject: [PATCH 433/996] Remove obsoleted sentry disposal call and fix incorrectly unbound logger event --- osu.Game/Utils/SentryLogger.cs | 46 ++++++++++++++-------------------- 1 file changed, 19 insertions(+), 27 deletions(-) diff --git a/osu.Game/Utils/SentryLogger.cs b/osu.Game/Utils/SentryLogger.cs index 8f12760a6b..dbf04283b6 100644 --- a/osu.Game/Utils/SentryLogger.cs +++ b/osu.Game/Utils/SentryLogger.cs @@ -16,6 +16,7 @@ namespace osu.Game.Utils { private SentryClient sentry; private Scope sentryScope; + private Exception lastException; public SentryLogger(OsuGame game) { @@ -30,30 +31,27 @@ namespace osu.Game.Utils sentry = new SentryClient(options); sentryScope = new Scope(options); - Exception lastException = null; + Logger.NewEntry += processLogEntry; + } - Logger.NewEntry += entry => + private void processLogEntry(LogEntry entry) + { + if (entry.Level < LogLevel.Verbose) return; + + var exception = entry.Exception; + + if (exception != null) { - if (entry.Level < LogLevel.Verbose) return; + if (!shouldSubmitException(exception)) return; - var exception = entry.Exception; + // since we let unhandled exceptions go ignored at times, we want to ensure they don't get submitted on subsequent reports. + if (lastException != null && lastException.Message == exception.Message && exception.StackTrace.StartsWith(lastException.StackTrace, StringComparison.Ordinal)) return; - if (exception != null) - { - if (!shouldSubmitException(exception)) - return; - - // since we let unhandled exceptions go ignored at times, we want to ensure they don't get submitted on subsequent reports. - if (lastException != null && - lastException.Message == exception.Message && exception.StackTrace.StartsWith(lastException.StackTrace, StringComparison.Ordinal)) - return; - - lastException = exception; - sentry.CaptureEvent(new SentryEvent(exception) { Message = entry.Message }, sentryScope); - } - else - sentryScope.AddBreadcrumb(DateTimeOffset.Now, entry.Message, entry.Target.ToString(), "navigation"); - }; + lastException = exception; + sentry.CaptureEvent(new SentryEvent(exception) { Message = entry.Message }, sentryScope); + } + else + sentryScope.AddBreadcrumb(DateTimeOffset.Now, entry.Message, entry.Target.ToString(), "navigation"); } private bool shouldSubmitException(Exception exception) @@ -92,15 +90,9 @@ namespace osu.Game.Utils GC.SuppressFinalize(this); } - private bool isDisposed; - protected virtual void Dispose(bool isDisposing) { - if (isDisposed) - return; - - isDisposed = true; - sentry?.Dispose(); + Logger.NewEntry -= processLogEntry; sentry = null; sentryScope = null; } From c725548b9ec7b78ad08ec5beb823e70d6f4672bf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 Jan 2022 11:42:50 +0900 Subject: [PATCH 434/996] Update stray realm reference in mobile projects --- osu.Android.props | 2 +- osu.iOS.props | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 18e63bb219..b296c114e9 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -56,6 +56,6 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index d6c4533538..5925581e28 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -88,6 +88,6 @@ - + From a7db793d8c14f1923aeac26aeabda00a2f6aa60b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 Jan 2022 11:39:22 +0900 Subject: [PATCH 435/996] Specify realm pipe path location I can't confirm this works yet, since I'm not sure what the preconditions are for the `.note` file to be created. What I can confirm is that a `.note` file hasn't appeared in my game data directory yet. --- osu.Game/Database/RealmContextFactory.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 96c24837a1..04253ade82 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.IO; using System.Linq; using System.Reflection; using System.Threading; @@ -188,10 +189,17 @@ namespace osu.Game.Database private RealmConfiguration getConfiguration() { + // This is currently the only usage of temporary files at the osu! side. + // If we use the temporary folder in more situations in the future, this should be moved to a higher level (helper method or OsuGameBase). + string tempPathLocation = Path.Combine(Path.GetTempPath(), @"lazer"); + if (!Directory.Exists(tempPathLocation)) + Directory.CreateDirectory(tempPathLocation); + return new RealmConfiguration(storage.GetFullPath(Filename, true)) { SchemaVersion = schema_version, MigrationCallback = onMigration, + FallbackPipePath = tempPathLocation, }; } From ebc9d3a613e3e73b996f66302646de1be9ae74b0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 Jan 2022 11:57:38 +0900 Subject: [PATCH 436/996] Fix playlist aggregate scores not displaying (again) --- osu.Game/Scoring/ScoreManager.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 52180d7ff2..ccf3226792 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -133,8 +133,7 @@ namespace osu.Game.Scoring public async Task GetTotalScoreAsync([NotNull] ScoreInfo score, ScoringMode mode = ScoringMode.Standardised, CancellationToken cancellationToken = default) { // TODO: This is required for playlist aggregate scores. They should likely not be getting here in the first place. - // ReSharper disable once ConditionIsAlwaysTrueOrFalse - if (score.BeatmapInfo == null) + if (string.IsNullOrEmpty(score.BeatmapInfo.Hash)) return score.TotalScore; int beatmapMaxCombo; From b9ec860cf2c7a5a3eae50cc06c6301e7ed5e5802 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 Jan 2022 12:20:52 +0900 Subject: [PATCH 437/996] Ensure global beatmap/ruleset are always mutated from the update thread This came up while testing the new realm thread, where `MusicController` would fall over when `OsuTestScene` changes the global beatmap from an async load thread (causing a cross-thread realm access). We don't want to have to schedule every usage of these bindables, so this seems like a good constraint to put in place. --- osu.Game/OsuGameBase.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 9256514a0a..8126b74418 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -327,6 +327,7 @@ namespace osu.Game dependencies.CacheAs(MusicController); Ruleset.BindValueChanged(onRulesetChanged); + Beatmap.BindValueChanged(onBeatmapChanged); } protected virtual void InitialiseFonts() @@ -448,8 +449,17 @@ namespace osu.Game protected override Storage CreateStorage(GameHost host, Storage defaultStorage) => new OsuStorage(host, defaultStorage); + private void onBeatmapChanged(ValueChangedEvent valueChangedEvent) + { + if (!ThreadSafety.IsUpdateThread) + throw new InvalidOperationException("Global beatmap bindable must be changed from update thread."); + } + private void onRulesetChanged(ValueChangedEvent r) { + if (!ThreadSafety.IsUpdateThread) + throw new InvalidOperationException("Global ruleset bindable must be changed from update thread."); + if (r.NewValue?.Available != true) { // reject the change if the ruleset is not available. From 014c840d807e00ff2b35bcce9d096c0396d309a9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 Jan 2022 12:24:42 +0900 Subject: [PATCH 438/996] Fix incorrect thread usage of ruleset in tournament `DataLoadTest` --- osu.Game.Tournament.Tests/NonVisual/DataLoadTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tournament.Tests/NonVisual/DataLoadTest.cs b/osu.Game.Tournament.Tests/NonVisual/DataLoadTest.cs index db019f9242..65753bfe00 100644 --- a/osu.Game.Tournament.Tests/NonVisual/DataLoadTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/DataLoadTest.cs @@ -35,9 +35,9 @@ namespace osu.Game.Tournament.Tests.NonVisual public class TestTournament : TournamentGameBase { - [BackgroundDependencyLoader] - private void load() + protected override void LoadComplete() { + base.LoadComplete(); Ruleset.Value = new RulesetInfo(); // not available } } From b9aae5569f75ce9fd62d0cb187806f0917b009db Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 Jan 2022 13:22:37 +0900 Subject: [PATCH 439/996] Fix `OsuTestScene` potentially mutating global bindables --- osu.Game/Tests/Visual/OsuTestScene.cs | 45 +++++++++++++++++---------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 6b029729ea..be811dd54b 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -28,7 +28,6 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.UI; -using osu.Game.Screens; using osu.Game.Storyboards; using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Rulesets; @@ -38,13 +37,16 @@ namespace osu.Game.Tests.Visual [ExcludeFromDynamicCompile] public abstract class OsuTestScene : TestScene { - protected Bindable Beatmap { get; private set; } + [Cached] + protected Bindable Beatmap { get; } = new Bindable(); - protected Bindable Ruleset; + [Cached] + protected Bindable Ruleset { get; } = new Bindable(); - protected Bindable> SelectedMods; + [Cached] + protected Bindable> SelectedMods { get; } = new Bindable>(Array.Empty()); - protected new OsuScreenDependencies Dependencies { get; private set; } + protected new DependencyContainer Dependencies { get; private set; } protected IResourceStore Resources; @@ -139,17 +141,15 @@ namespace osu.Game.Tests.Visual var providedRuleset = CreateRuleset(); if (providedRuleset != null) - baseDependencies = rulesetDependencies = new DrawableRulesetDependencies(providedRuleset, baseDependencies); + isolatedBaseDependencies = rulesetDependencies = new DrawableRulesetDependencies(providedRuleset, baseDependencies); - Dependencies = new OsuScreenDependencies(false, baseDependencies); + Dependencies = isolatedBaseDependencies; - Beatmap = Dependencies.Beatmap; + Beatmap.Default = parent.Get>().Default; Beatmap.SetDefault(); - Ruleset = Dependencies.Ruleset; - Ruleset.SetDefault(); + Ruleset.Value = CreateRuleset()?.RulesetInfo ?? parent.Get().AvailableRulesets.First(); - SelectedMods = Dependencies.Mods; SelectedMods.SetDefault(); if (!UseOnlineAPI) @@ -162,6 +162,23 @@ namespace osu.Game.Tests.Visual return Dependencies; } + protected override void LoadComplete() + { + base.LoadComplete(); + + var parentBeatmap = Parent.Dependencies.Get>(); + parentBeatmap.Value = Beatmap.Value; + Beatmap.BindTo(parentBeatmap); + + var parentRuleset = Parent.Dependencies.Get>(); + parentRuleset.Value = Ruleset.Value; + Ruleset.BindTo(parentRuleset); + + var parentMods = Parent.Dependencies.Get>>(); + parentMods.Value = SelectedMods.Value; + SelectedMods.BindTo(parentMods); + } + protected override Container Content => content ?? base.Content; private readonly Container content; @@ -286,12 +303,6 @@ namespace osu.Game.Tests.Visual protected virtual WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) => new ClockBackedTestWorkingBeatmap(beatmap, storyboard, Clock, Audio); - [BackgroundDependencyLoader] - private void load(RulesetStore rulesets) - { - Ruleset.Value = CreateRuleset()?.RulesetInfo ?? rulesets.AvailableRulesets.First(); - } - protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); From da9a60a6954c5b194e9e1331fae92792fa9090c8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 Jan 2022 13:22:55 +0900 Subject: [PATCH 440/996] Update broken test scenes to match new `OsuTestScene` logic --- .../Visual/Editing/TestSceneComposeScreen.cs | 5 ++-- .../Visual/Editing/TestSceneEditorClock.cs | 6 ++--- .../Editing/TestSceneEditorSeekSnapping.cs | 6 ++--- .../Visual/Editing/TestSceneTimingScreen.cs | 5 ++-- osu.Game/Tests/Visual/EditorClockTestScene.cs | 4 +-- osu.Game/Tests/Visual/EditorTestScene.cs | 25 +++++++++++++------ 6 files changed, 31 insertions(+), 20 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneComposeScreen.cs b/osu.Game.Tests/Visual/Editing/TestSceneComposeScreen.cs index 9b8567e853..d100fba8d6 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneComposeScreen.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneComposeScreen.cs @@ -29,9 +29,10 @@ namespace osu.Game.Tests.Visual.Editing [Cached] private EditorClipboard clipboard = new EditorClipboard(); - [BackgroundDependencyLoader] - private void load() + protected override void LoadComplete() { + base.LoadComplete(); + Beatmap.Value = CreateWorkingBeatmap(editorBeatmap.PlayableBeatmap); Child = new ComposeScreen diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorClock.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorClock.cs index 0abf0c47f8..4b9be77471 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorClock.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorClock.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Osu; @@ -37,9 +36,10 @@ namespace osu.Game.Tests.Visual.Editing }); } - [BackgroundDependencyLoader] - private void load() + protected override void LoadComplete() { + base.LoadComplete(); + Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); // ensure that music controller does not change this beatmap due to it // completing naturally as part of the test. diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSeekSnapping.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSeekSnapping.cs index 3a19eabe81..863f42520b 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSeekSnapping.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSeekSnapping.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; -using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -23,9 +22,10 @@ namespace osu.Game.Tests.Visual.Editing BeatDivisor.Value = 4; } - [BackgroundDependencyLoader] - private void load() + protected override void LoadComplete() { + base.LoadComplete(); + var testBeatmap = new Beatmap { ControlPointInfo = new ControlPointInfo(), diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs index 4bbffbdc7a..17b8189fc7 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs @@ -29,9 +29,10 @@ namespace osu.Game.Tests.Visual.Editing editorBeatmap = new EditorBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)); } - [BackgroundDependencyLoader] - private void load() + protected override void LoadComplete() { + base.LoadComplete(); + Beatmap.Value = CreateWorkingBeatmap(editorBeatmap.PlayableBeatmap); Beatmap.Disabled = true; diff --git a/osu.Game/Tests/Visual/EditorClockTestScene.cs b/osu.Game/Tests/Visual/EditorClockTestScene.cs index c2e9892735..66ab427565 100644 --- a/osu.Game/Tests/Visual/EditorClockTestScene.cs +++ b/osu.Game/Tests/Visual/EditorClockTestScene.cs @@ -35,9 +35,9 @@ namespace osu.Game.Tests.Visual return dependencies; } - [BackgroundDependencyLoader] - private void load() + protected override void LoadComplete() { + base.LoadComplete(); Beatmap.BindValueChanged(beatmapChanged, true); } diff --git a/osu.Game/Tests/Visual/EditorTestScene.cs b/osu.Game/Tests/Visual/EditorTestScene.cs index 07152b5a3e..d9dd6e4fa8 100644 --- a/osu.Game/Tests/Visual/EditorTestScene.cs +++ b/osu.Game/Tests/Visual/EditorTestScene.cs @@ -42,17 +42,27 @@ namespace osu.Game.Tests.Visual Alpha = 0 }; + private TestBeatmapManager testBeatmapManager; + private WorkingBeatmap working; + [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio, RulesetStore rulesets) { Add(logo); - var working = CreateWorkingBeatmap(Ruleset.Value); - - Beatmap.Value = working; + working = CreateWorkingBeatmap(Ruleset.Value); if (IsolateSavingFromDatabase) - Dependencies.CacheAs(new TestBeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default, working)); + Dependencies.CacheAs(testBeatmapManager = new TestBeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Beatmap.Value = working; + if (testBeatmapManager != null) + testBeatmapManager.TestBeatmap = working; } protected virtual bool EditorComponentsReady => Editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true @@ -114,12 +124,11 @@ namespace osu.Game.Tests.Visual private class TestBeatmapManager : BeatmapManager { - private readonly WorkingBeatmap testBeatmap; + public WorkingBeatmap TestBeatmap; - public TestBeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore resources, GameHost host, WorkingBeatmap defaultBeatmap, WorkingBeatmap testBeatmap) + public TestBeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore resources, GameHost host, WorkingBeatmap defaultBeatmap) : base(storage, contextFactory, rulesets, api, audioManager, resources, host, defaultBeatmap) { - this.testBeatmap = testBeatmap; } protected override BeatmapModelManager CreateBeatmapModelManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, GameHost host) @@ -143,7 +152,7 @@ namespace osu.Game.Tests.Visual } public override WorkingBeatmap GetWorkingBeatmap(BeatmapInfo beatmapInfo) - => testBeatmapManager.testBeatmap; + => testBeatmapManager.TestBeatmap; } internal class TestBeatmapModelManager : BeatmapModelManager From c3d3c03f0fad8b994bf99d8ee3c587654eff3a66 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 Jan 2022 12:15:40 +0900 Subject: [PATCH 441/996] Fix `TestScenePlaylistsScreen` crashing on entering room --- osu.Game.Tests/Visual/Playlists/TestScenePlaylistsScreen.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsScreen.cs index e52f823f0b..63bd7c8068 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsScreen.cs @@ -2,8 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Game.Overlays; namespace osu.Game.Tests.Visual.Playlists { @@ -12,9 +10,6 @@ namespace osu.Game.Tests.Visual.Playlists { protected override bool UseOnlineAPI => true; - [Cached] - private MusicController musicController { get; set; } = new MusicController(); - public TestScenePlaylistsScreen() { var multi = new Screens.OnlinePlay.Playlists.Playlists(); From d26f4d50bd92834f021e89ac56263e426e64e6e1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 Jan 2022 13:58:12 +0900 Subject: [PATCH 442/996] Add test coverage of aggregate room scores displaying correctly --- .../TestScenePlaylistsRoomCreation.cs | 3 +++ .../Online/Leaderboards/LeaderboardScore.cs | 10 ++++---- .../OnlinePlay/TestRoomRequestsHandler.cs | 23 +++++++++++++++++++ 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs index e59884f4f4..6efdd1b8b6 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs @@ -18,6 +18,7 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.OnlinePlay.Components; +using osu.Game.Screens.OnlinePlay.Match.Components; using osu.Game.Screens.OnlinePlay.Playlists; using osu.Game.Screens.Play; using osu.Game.Tests.Beatmaps; @@ -71,6 +72,8 @@ namespace osu.Game.Tests.Visual.Playlists AddUntilStep("Progress details are hidden", () => match.ChildrenOfType().FirstOrDefault()?.Parent.Alpha == 0); + AddUntilStep("Leaderboard shows two aggregate scores", () => match.ChildrenOfType().Count(s => s.ScoreText.Text != "0") == 2); + AddStep("start match", () => match.ChildrenOfType().First().TriggerClick()); AddUntilStep("player loader loaded", () => Stack.CurrentScreen is PlayerLoader); } diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 14eec8b388..8f4a103513 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -53,7 +53,9 @@ namespace osu.Game.Online.Leaderboards private Drawable avatar; private Drawable scoreRank; private OsuSpriteText nameLabel; - private GlowingSpriteText scoreLabel; + + public GlowingSpriteText ScoreText { get; private set; } + private Container flagBadgeContainer; private FillFlowContainer modsContainer; @@ -198,7 +200,7 @@ namespace osu.Game.Online.Leaderboards Spacing = new Vector2(5f, 0f), Children = new Drawable[] { - scoreLabel = new GlowingSpriteText + ScoreText = new GlowingSpriteText { TextColour = Color4.White, GlowColour = Color4Extensions.FromHex(@"83ccfa"), @@ -240,7 +242,7 @@ namespace osu.Game.Online.Leaderboards public override void Show() { - foreach (var d in new[] { avatar, nameLabel, scoreLabel, scoreRank, flagBadgeContainer, modsContainer }.Concat(statisticsLabels)) + foreach (var d in new[] { avatar, nameLabel, ScoreText, scoreRank, flagBadgeContainer, modsContainer }.Concat(statisticsLabels)) d.FadeOut(); Alpha = 0; @@ -262,7 +264,7 @@ namespace osu.Game.Online.Leaderboards using (BeginDelayedSequence(250)) { - scoreLabel.FadeIn(200); + ScoreText.FadeIn(200); scoreRank.FadeIn(200); using (BeginDelayedSequence(50)) diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs index 520f2c4585..5a0a7e71d4 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs @@ -70,6 +70,29 @@ namespace osu.Game.Tests.Visual.OnlinePlay return true; } + case GetRoomLeaderboardRequest roomLeaderboardRequest: + roomLeaderboardRequest.TriggerSuccess(new APILeaderboard + { + Leaderboard = new List + { + new APIUserScoreAggregate + { + TotalScore = 1000000, + TotalAttempts = 5, + CompletedBeatmaps = 2, + User = new APIUser { Username = "best user" } + }, + new APIUserScoreAggregate + { + TotalScore = 50, + TotalAttempts = 1, + CompletedBeatmaps = 1, + User = new APIUser { Username = "worst user" } + } + } + }); + return true; + case PartRoomRequest partRoomRequest: partRoomRequest.TriggerSuccess(); return true; From a714941f932cd07c0f1315a3a78a534a9e1101ae Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 Jan 2022 14:06:22 +0900 Subject: [PATCH 443/996] Rename EF variable to make reading code easier --- osu.Game/Database/EFToRealmMigrator.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index 7683accc5c..98037bb8a3 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -30,20 +30,20 @@ namespace osu.Game.Database public void Run() { - using (var db = efContextFactory.GetForWrite()) + using (var ef = efContextFactory.GetForWrite()) { - migrateSettings(db); - migrateSkins(db); + migrateSettings(ef); + migrateSkins(ef); - migrateBeatmaps(db); - migrateScores(db); + migrateBeatmaps(ef); + migrateScores(ef); } } - private void migrateBeatmaps(DatabaseWriteUsage db) + private void migrateBeatmaps(DatabaseWriteUsage ef) { // can be removed 20220730. - var existingBeatmapSets = db.Context.EFBeatmapSetInfo + var existingBeatmapSets = ef.Context.EFBeatmapSetInfo .Include(s => s.Beatmaps).ThenInclude(b => b.RulesetInfo) .Include(s => s.Beatmaps).ThenInclude(b => b.Metadata) .Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty) @@ -117,7 +117,7 @@ namespace osu.Game.Database } } - db.Context.RemoveRange(existingBeatmapSets); + ef.Context.RemoveRange(existingBeatmapSets); // Intentionally don't clean up the files, so they don't get purged by EF. transaction.Commit(); From b1a75ce48098116ee14a6eb9db39cd1339cdf4dc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 Jan 2022 14:17:43 +0900 Subject: [PATCH 444/996] Permanently delete `client.db` after migration completes --- osu.Game/Database/EFToRealmMigrator.cs | 4 ++++ osu.Game/OsuGameBase.cs | 1 + 2 files changed, 5 insertions(+) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index 98037bb8a3..7ad8e507bb 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -38,6 +38,10 @@ namespace osu.Game.Database migrateBeatmaps(ef); migrateScores(ef); } + + // Delete the database permanently. + // Will cause future startups to not attempt migration. + efContextFactory.ResetDatabase(); } private void migrateBeatmaps(DatabaseWriteUsage ef) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index a9538c1e8c..18b22588a6 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -193,6 +193,7 @@ namespace osu.Game dependencies.Cache(RulesetStore = new RulesetStore(realmFactory, Storage)); dependencies.CacheAs(RulesetStore); + // A non-null context factory means there's still content to migrate. if (efContextFactory != null) new EFToRealmMigrator(efContextFactory, realmFactory, LocalConfig).Run(); From 798482c94122eb9d23221e90e33ccc390a52380a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 Jan 2022 14:19:25 +0900 Subject: [PATCH 445/996] Create backups before deleting scores and beatmaps from EF database --- osu.Game/Database/DatabaseContextFactory.cs | 8 +++++++ osu.Game/Database/EFToRealmMigrator.cs | 25 +++++++++++++-------- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/osu.Game/Database/DatabaseContextFactory.cs b/osu.Game/Database/DatabaseContextFactory.cs index f79505d7c5..c2a60122eb 100644 --- a/osu.Game/Database/DatabaseContextFactory.cs +++ b/osu.Game/Database/DatabaseContextFactory.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.IO; using System.Linq; using System.Threading; using Microsoft.EntityFrameworkCore.Storage; @@ -144,6 +145,13 @@ namespace osu.Game.Database Database = { AutoTransactionsEnabled = false } }; + public void CreateBackup(string filename) + { + using (var source = storage.GetStream(DATABASE_NAME)) + using (var destination = storage.GetStream(filename, FileAccess.Write, FileMode.CreateNew)) + source.CopyTo(destination); + } + public void ResetDatabase() { lock (writeLock) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index 7ad8e507bb..3ecbd50b3e 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.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. +using System; +using System.Collections.Generic; using System.Linq; using Microsoft.EntityFrameworkCore; using osu.Game.Beatmaps; @@ -31,13 +33,16 @@ namespace osu.Game.Database public void Run() { using (var ef = efContextFactory.GetForWrite()) - { migrateSettings(ef); + + using (var ef = efContextFactory.GetForWrite()) migrateSkins(ef); + using (var ef = efContextFactory.GetForWrite()) migrateBeatmaps(ef); + + using (var ef = efContextFactory.GetForWrite()) migrateScores(ef); - } // Delete the database permanently. // Will cause future startups to not attempt migration. @@ -47,13 +52,13 @@ namespace osu.Game.Database private void migrateBeatmaps(DatabaseWriteUsage ef) { // can be removed 20220730. - var existingBeatmapSets = ef.Context.EFBeatmapSetInfo - .Include(s => s.Beatmaps).ThenInclude(b => b.RulesetInfo) - .Include(s => s.Beatmaps).ThenInclude(b => b.Metadata) - .Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty) - .Include(s => s.Files).ThenInclude(f => f.FileInfo) - .Include(s => s.Metadata) - .ToList(); + List existingBeatmapSets = ef.Context.EFBeatmapSetInfo + .Include(s => s.Beatmaps).ThenInclude(b => b.RulesetInfo) + .Include(s => s.Beatmaps).ThenInclude(b => b.Metadata) + .Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty) + .Include(s => s.Files).ThenInclude(f => f.FileInfo) + .Include(s => s.Metadata) + .ToList(); // previous entries in EF are removed post migration. if (!existingBeatmapSets.Any()) @@ -121,6 +126,7 @@ namespace osu.Game.Database } } + efContextFactory.CreateBackup($"client.before_beatmap_migration_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}.db"); ef.Context.RemoveRange(existingBeatmapSets); // Intentionally don't clean up the files, so they don't get purged by EF. @@ -207,6 +213,7 @@ namespace osu.Game.Database } } + efContextFactory.CreateBackup($"client.before_scores_migration_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}.db"); db.Context.RemoveRange(existingScores); // Intentionally don't clean up the files, so they don't get purged by EF. From cf30d48721242567eb44d7d0e31fb6cb9e2d1b69 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 Jan 2022 14:19:31 +0900 Subject: [PATCH 446/996] Add more logging during migration process --- osu.Game/Database/EFToRealmMigrator.cs | 29 +++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index 3ecbd50b3e..d8a3b66c7c 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using Microsoft.EntityFrameworkCore; +using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Models; @@ -64,6 +65,8 @@ namespace osu.Game.Database if (!existingBeatmapSets.Any()) return; + Logger.Log("Beginning beatmaps migration to realm", LoggingTarget.Database); + using (var realm = realmContextFactory.CreateContext()) using (var transaction = realm.BeginWrite()) { @@ -71,6 +74,8 @@ namespace osu.Game.Database // note that this cannot be written as: `realm.All().All(s => s.Protected)`, because realm does not support `.All()`. if (!realm.All().Any(s => !s.Protected)) { + Logger.Log($"Migrating {existingBeatmapSets.Count} beatmaps", LoggingTarget.Database); + foreach (var beatmapSet in existingBeatmapSets) { var realmBeatmapSet = new BeatmapSetInfo @@ -161,17 +166,19 @@ namespace osu.Game.Database private void migrateScores(DatabaseWriteUsage db) { // can be removed 20220730. - var existingScores = db.Context.ScoreInfo - .Include(s => s.Ruleset) - .Include(s => s.BeatmapInfo) - .Include(s => s.Files) - .ThenInclude(f => f.FileInfo) - .ToList(); + List existingScores = db.Context.ScoreInfo + .Include(s => s.Ruleset) + .Include(s => s.BeatmapInfo) + .Include(s => s.Files) + .ThenInclude(f => f.FileInfo) + .ToList(); // previous entries in EF are removed post migration. if (!existingScores.Any()) return; + Logger.Log("Beginning scores migration to realm", LoggingTarget.Database); + using (var realm = realmContextFactory.CreateContext()) using (var transaction = realm.BeginWrite()) { @@ -179,6 +186,8 @@ namespace osu.Game.Database // note that this cannot be written as: `realm.All().All(s => s.Protected)`, because realm does not support `.All()`. if (!realm.All().Any()) { + Logger.Log($"Migrating {existingScores.Count} scores", LoggingTarget.Database); + foreach (var score in existingScores) { var realmScore = new ScoreInfo @@ -254,6 +263,8 @@ namespace osu.Game.Database // note that this cannot be written as: `realm.All().All(s => s.Protected)`, because realm does not support `.All()`. if (!realm.All().Any(s => !s.Protected)) { + Logger.Log($"Migrating {existingSkins.Count} skins", LoggingTarget.Database); + foreach (var skin in existingSkins) { var realmSkin = new SkinInfo @@ -297,18 +308,22 @@ namespace osu.Game.Database private void migrateSettings(DatabaseWriteUsage db) { // migrate ruleset settings. can be removed 20220315. - var existingSettings = db.Context.DatabasedSetting; + var existingSettings = db.Context.DatabasedSetting.ToList(); // previous entries in EF are removed post migration. if (!existingSettings.Any()) return; + Logger.Log("Beginning settings migration to realm", LoggingTarget.Database); + using (var realm = realmContextFactory.CreateContext()) using (var transaction = realm.BeginWrite()) { // only migrate data if the realm database is empty. if (!realm.All().Any()) { + Logger.Log($"Migrating {existingSettings.Count} settings", LoggingTarget.Database); + foreach (var dkb in existingSettings) { if (dkb.RulesetID == null) From 2b1c15b6cc1b7a6f51157cb598e63b835322ea3c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 Jan 2022 14:30:32 +0900 Subject: [PATCH 447/996] Allow `BlockAllOperations` to be called from a non-update thread if update has never run --- osu.Game/Database/RealmContextFactory.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 9307e06be0..01c54d6ee2 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -366,17 +366,17 @@ namespace osu.Game.Database if (isDisposed) throw new ObjectDisposedException(nameof(RealmContextFactory)); - if (!ThreadSafety.IsUpdateThread) - throw new InvalidOperationException(@$"{nameof(BlockAllOperations)} must be called from the update thread."); - - Logger.Log(@"Blocking realm operations.", LoggingTarget.Database); - try { contextCreationLock.Wait(); lock (contextLock) { + if (!ThreadSafety.IsUpdateThread && context != null) + throw new InvalidOperationException(@$"{nameof(BlockAllOperations)} must be called from the update thread."); + + Logger.Log(@"Blocking realm operations.", LoggingTarget.Database); + context?.Dispose(); context = null; } From bf50a9b8f8d92d0a23a498822513d65d9fade133 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 Jan 2022 14:30:41 +0900 Subject: [PATCH 448/996] Also backup the realm database before migration --- osu.Game/Database/EFToRealmMigrator.cs | 8 ++++++-- osu.Game/Database/RealmContextFactory.cs | 11 +++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index d8a3b66c7c..9fad29b3f4 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -75,6 +75,9 @@ namespace osu.Game.Database if (!realm.All().Any(s => !s.Protected)) { Logger.Log($"Migrating {existingBeatmapSets.Count} beatmaps", LoggingTarget.Database); + string migration = $"before_beatmap_migration_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}"; + realmContextFactory.CreateBackup($"client.{migration}.realm"); + efContextFactory.CreateBackup($"client.{migration}.db"); foreach (var beatmapSet in existingBeatmapSets) { @@ -131,7 +134,6 @@ namespace osu.Game.Database } } - efContextFactory.CreateBackup($"client.before_beatmap_migration_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}.db"); ef.Context.RemoveRange(existingBeatmapSets); // Intentionally don't clean up the files, so they don't get purged by EF. @@ -187,6 +189,9 @@ namespace osu.Game.Database if (!realm.All().Any()) { Logger.Log($"Migrating {existingScores.Count} scores", LoggingTarget.Database); + string migration = $"before_score_migration_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}"; + realmContextFactory.CreateBackup($"client.{migration}.realm"); + efContextFactory.CreateBackup($"client.{migration}.db"); foreach (var score in existingScores) { @@ -222,7 +227,6 @@ namespace osu.Game.Database } } - efContextFactory.CreateBackup($"client.before_scores_migration_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}.db"); db.Context.RemoveRange(existingScores); // Intentionally don't clean up the files, so they don't get purged by EF. diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 01c54d6ee2..c86a9e54e1 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.IO; using System.Linq; using System.Reflection; using System.Threading; @@ -353,6 +354,16 @@ namespace osu.Game.Database private string? getRulesetShortNameFromLegacyID(long rulesetId) => efContextFactory?.Get().RulesetInfo.FirstOrDefault(r => r.ID == rulesetId)?.ShortName; + public void CreateBackup(string filename) + { + using (BlockAllOperations()) + { + using (var source = storage.GetStream(Filename)) + using (var destination = storage.GetStream(filename, FileAccess.Write, FileMode.CreateNew)) + source.CopyTo(destination); + } + } + /// /// Flush any active contexts and block any further writes. /// From 3429fd87681b928a52954b4a3b32ae38fd2bd794 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 Jan 2022 14:41:02 +0900 Subject: [PATCH 449/996] Fix transaction scope and add even more logging --- osu.Game/Database/DatabaseContextFactory.cs | 2 + osu.Game/Database/EFToRealmMigrator.cs | 215 +++++++++++--------- osu.Game/Database/RealmContextFactory.cs | 1 + 3 files changed, 122 insertions(+), 96 deletions(-) diff --git a/osu.Game/Database/DatabaseContextFactory.cs b/osu.Game/Database/DatabaseContextFactory.cs index c2a60122eb..cc690a9fda 100644 --- a/osu.Game/Database/DatabaseContextFactory.cs +++ b/osu.Game/Database/DatabaseContextFactory.cs @@ -5,6 +5,7 @@ using System.IO; using System.Linq; using System.Threading; using Microsoft.EntityFrameworkCore.Storage; +using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Statistics; @@ -147,6 +148,7 @@ namespace osu.Game.Database public void CreateBackup(string filename) { + Logger.Log($"Creating full EF database backup at {filename}", LoggingTarget.Database); using (var source = storage.GetStream(DATABASE_NAME)) using (var destination = storage.GetStream(filename, FileAccess.Write, FileMode.CreateNew)) source.CopyTo(destination); diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index 9fad29b3f4..8f5de80e3b 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -47,6 +47,7 @@ namespace osu.Game.Database // Delete the database permanently. // Will cause future startups to not attempt migration. + Logger.Log("Migration successful, deleting EF database", LoggingTarget.Database); efContextFactory.ResetDatabase(); } @@ -61,83 +62,94 @@ namespace osu.Game.Database .Include(s => s.Metadata) .ToList(); - // previous entries in EF are removed post migration. - if (!existingBeatmapSets.Any()) - return; - Logger.Log("Beginning beatmaps migration to realm", LoggingTarget.Database); - using (var realm = realmContextFactory.CreateContext()) - using (var transaction = realm.BeginWrite()) + // previous entries in EF are removed post migration. + if (!existingBeatmapSets.Any()) { + Logger.Log("No beatmaps found to migrate", LoggingTarget.Database); + return; + } + + using (var realm = realmContextFactory.CreateContext()) + { + Logger.Log($"Found {existingBeatmapSets.Count} beatmaps in EF", LoggingTarget.Database); + string migration = $"before_beatmap_migration_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}"; + efContextFactory.CreateBackup($"client.{migration}.db"); + // only migrate data if the realm database is empty. // note that this cannot be written as: `realm.All().All(s => s.Protected)`, because realm does not support `.All()`. - if (!realm.All().Any(s => !s.Protected)) + if (realm.All().Any(s => !s.Protected)) + { + Logger.Log("Skipping migration as realm already has beatmaps loaded", LoggingTarget.Database); + } + else { - Logger.Log($"Migrating {existingBeatmapSets.Count} beatmaps", LoggingTarget.Database); - string migration = $"before_beatmap_migration_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}"; realmContextFactory.CreateBackup($"client.{migration}.realm"); - efContextFactory.CreateBackup($"client.{migration}.db"); - foreach (var beatmapSet in existingBeatmapSets) + using (var transaction = realm.BeginWrite()) { - var realmBeatmapSet = new BeatmapSetInfo + foreach (var beatmapSet in existingBeatmapSets) { - OnlineID = beatmapSet.OnlineID ?? -1, - DateAdded = beatmapSet.DateAdded, - Status = beatmapSet.Status, - DeletePending = beatmapSet.DeletePending, - Hash = beatmapSet.Hash, - Protected = beatmapSet.Protected, - }; - - migrateFiles(beatmapSet, realm, realmBeatmapSet); - - foreach (var beatmap in beatmapSet.Beatmaps) - { - var realmBeatmap = new BeatmapInfo + var realmBeatmapSet = new BeatmapSetInfo { - DifficultyName = beatmap.DifficultyName, - Status = beatmap.Status, - OnlineID = beatmap.OnlineID ?? -1, - Length = beatmap.Length, - BPM = beatmap.BPM, - Hash = beatmap.Hash, - StarRating = beatmap.StarRating, - MD5Hash = beatmap.MD5Hash, - Hidden = beatmap.Hidden, - AudioLeadIn = beatmap.AudioLeadIn, - StackLeniency = beatmap.StackLeniency, - SpecialStyle = beatmap.SpecialStyle, - LetterboxInBreaks = beatmap.LetterboxInBreaks, - WidescreenStoryboard = beatmap.WidescreenStoryboard, - EpilepsyWarning = beatmap.EpilepsyWarning, - SamplesMatchPlaybackRate = beatmap.SamplesMatchPlaybackRate, - DistanceSpacing = beatmap.DistanceSpacing, - BeatDivisor = beatmap.BeatDivisor, - GridSize = beatmap.GridSize, - TimelineZoom = beatmap.TimelineZoom, - Countdown = beatmap.Countdown, - CountdownOffset = beatmap.CountdownOffset, - MaxCombo = beatmap.MaxCombo, - Bookmarks = beatmap.Bookmarks, - Ruleset = realm.Find(beatmap.RulesetInfo.ShortName), - Difficulty = new BeatmapDifficulty(beatmap.BaseDifficulty), - Metadata = getBestMetadata(beatmap.Metadata, beatmapSet.Metadata), - BeatmapSet = realmBeatmapSet, + OnlineID = beatmapSet.OnlineID ?? -1, + DateAdded = beatmapSet.DateAdded, + Status = beatmapSet.Status, + DeletePending = beatmapSet.DeletePending, + Hash = beatmapSet.Hash, + Protected = beatmapSet.Protected, }; - realmBeatmapSet.Beatmaps.Add(realmBeatmap); + migrateFiles(beatmapSet, realm, realmBeatmapSet); + + foreach (var beatmap in beatmapSet.Beatmaps) + { + var realmBeatmap = new BeatmapInfo + { + DifficultyName = beatmap.DifficultyName, + Status = beatmap.Status, + OnlineID = beatmap.OnlineID ?? -1, + Length = beatmap.Length, + BPM = beatmap.BPM, + Hash = beatmap.Hash, + StarRating = beatmap.StarRating, + MD5Hash = beatmap.MD5Hash, + Hidden = beatmap.Hidden, + AudioLeadIn = beatmap.AudioLeadIn, + StackLeniency = beatmap.StackLeniency, + SpecialStyle = beatmap.SpecialStyle, + LetterboxInBreaks = beatmap.LetterboxInBreaks, + WidescreenStoryboard = beatmap.WidescreenStoryboard, + EpilepsyWarning = beatmap.EpilepsyWarning, + SamplesMatchPlaybackRate = beatmap.SamplesMatchPlaybackRate, + DistanceSpacing = beatmap.DistanceSpacing, + BeatDivisor = beatmap.BeatDivisor, + GridSize = beatmap.GridSize, + TimelineZoom = beatmap.TimelineZoom, + Countdown = beatmap.Countdown, + CountdownOffset = beatmap.CountdownOffset, + MaxCombo = beatmap.MaxCombo, + Bookmarks = beatmap.Bookmarks, + Ruleset = realm.Find(beatmap.RulesetInfo.ShortName), + Difficulty = new BeatmapDifficulty(beatmap.BaseDifficulty), + Metadata = getBestMetadata(beatmap.Metadata, beatmapSet.Metadata), + BeatmapSet = realmBeatmapSet, + }; + + realmBeatmapSet.Beatmaps.Add(realmBeatmap); + } + + realm.Add(realmBeatmapSet); } - realm.Add(realmBeatmapSet); + transaction.Commit(); } } + Logger.Log($"Successfully migrated {existingBeatmapSets.Count} beatmaps to realm", LoggingTarget.Database); ef.Context.RemoveRange(existingBeatmapSets); // Intentionally don't clean up the files, so they don't get purged by EF. - - transaction.Commit(); } } @@ -175,62 +187,73 @@ namespace osu.Game.Database .ThenInclude(f => f.FileInfo) .ToList(); - // previous entries in EF are removed post migration. - if (!existingScores.Any()) - return; - Logger.Log("Beginning scores migration to realm", LoggingTarget.Database); - using (var realm = realmContextFactory.CreateContext()) - using (var transaction = realm.BeginWrite()) + // previous entries in EF are removed post migration. + if (!existingScores.Any()) { + Logger.Log("No scores found to migrate", LoggingTarget.Database); + return; + } + + using (var realm = realmContextFactory.CreateContext()) + { + Logger.Log($"Found {existingScores.Count} scores in EF", LoggingTarget.Database); + string migration = $"before_score_migration_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}"; + efContextFactory.CreateBackup($"client.{migration}.db"); + // only migrate data if the realm database is empty. // note that this cannot be written as: `realm.All().All(s => s.Protected)`, because realm does not support `.All()`. - if (!realm.All().Any()) + if (realm.All().Any()) + { + Logger.Log("Skipping migration as realm already has scores loaded", LoggingTarget.Database); + } + else { - Logger.Log($"Migrating {existingScores.Count} scores", LoggingTarget.Database); - string migration = $"before_score_migration_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}"; realmContextFactory.CreateBackup($"client.{migration}.realm"); - efContextFactory.CreateBackup($"client.{migration}.db"); - foreach (var score in existingScores) + using (var transaction = realm.BeginWrite()) { - var realmScore = new ScoreInfo + foreach (var score in existingScores) { - Hash = score.Hash, - DeletePending = score.DeletePending, - OnlineID = score.OnlineID ?? -1, - ModsJson = score.ModsJson, - StatisticsJson = score.StatisticsJson, - User = score.User, - TotalScore = score.TotalScore, - MaxCombo = score.MaxCombo, - Accuracy = score.Accuracy, - HasReplay = ((IScoreInfo)score).HasReplay, - Date = score.Date, - PP = score.PP, - BeatmapInfo = realm.All().First(b => b.Hash == score.BeatmapInfo.Hash), - Ruleset = realm.Find(score.Ruleset.ShortName), - Rank = score.Rank, - HitEvents = score.HitEvents, - Passed = score.Passed, - Combo = score.Combo, - Position = score.Position, - Statistics = score.Statistics, - Mods = score.Mods, - APIMods = score.APIMods, - }; + var realmScore = new ScoreInfo + { + Hash = score.Hash, + DeletePending = score.DeletePending, + OnlineID = score.OnlineID ?? -1, + ModsJson = score.ModsJson, + StatisticsJson = score.StatisticsJson, + User = score.User, + TotalScore = score.TotalScore, + MaxCombo = score.MaxCombo, + Accuracy = score.Accuracy, + HasReplay = ((IScoreInfo)score).HasReplay, + Date = score.Date, + PP = score.PP, + BeatmapInfo = realm.All().First(b => b.Hash == score.BeatmapInfo.Hash), + Ruleset = realm.Find(score.Ruleset.ShortName), + Rank = score.Rank, + HitEvents = score.HitEvents, + Passed = score.Passed, + Combo = score.Combo, + Position = score.Position, + Statistics = score.Statistics, + Mods = score.Mods, + APIMods = score.APIMods, + }; - migrateFiles(score, realm, realmScore); + migrateFiles(score, realm, realmScore); - realm.Add(realmScore); + realm.Add(realmScore); + } + + transaction.Commit(); } } + Logger.Log($"Successfully migrated {existingScores.Count} scores to realm", LoggingTarget.Database); db.Context.RemoveRange(existingScores); // Intentionally don't clean up the files, so they don't get purged by EF. - - transaction.Commit(); } } diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index c86a9e54e1..8548e63e94 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -358,6 +358,7 @@ namespace osu.Game.Database { using (BlockAllOperations()) { + Logger.Log($"Creating full realm database backup at {filename}", LoggingTarget.Database); using (var source = storage.GetStream(Filename)) using (var destination = storage.GetStream(filename, FileAccess.Write, FileMode.CreateNew)) source.CopyTo(destination); From 9a43ed742b0260b29dc28d9221676ffce938f3a8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 Jan 2022 15:21:08 +0900 Subject: [PATCH 450/996] Update automapper spec in line with v11 See https://docs.automapper.org/en/latest/11.0-Upgrade-Guide.html for more details. --- osu.Game/Beatmaps/BeatmapInfo.cs | 4 -- osu.Game/Database/RealmObjectExtensions.cs | 56 +++++++++++++++------- osu.Game/Scoring/ScoreInfo.cs | 3 +- 3 files changed, 39 insertions(+), 24 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 224cf60c49..b0e10c2c38 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -3,7 +3,6 @@ using System; using System.Linq; -using AutoMapper; using JetBrains.Annotations; using Newtonsoft.Json; using osu.Framework.Testing; @@ -37,7 +36,6 @@ namespace osu.Game.Beatmaps public BeatmapMetadata Metadata { get; set; } - [IgnoreMap] [Backlink(nameof(ScoreInfo.BeatmapInfo))] public IQueryable Scores { get; } = null!; @@ -155,7 +153,6 @@ namespace osu.Game.Beatmaps #region Compatibility properties [Ignored] - [IgnoreMap] public int RulesetID { get => Ruleset.OnlineID; @@ -169,7 +166,6 @@ namespace osu.Game.Beatmaps } [Ignored] - [IgnoreMap] public BeatmapDifficulty BaseDifficulty { get => Difficulty; diff --git a/osu.Game/Database/RealmObjectExtensions.cs b/osu.Game/Database/RealmObjectExtensions.cs index a162bd64d3..4ddf0077ca 100644 --- a/osu.Game/Database/RealmObjectExtensions.cs +++ b/osu.Game/Database/RealmObjectExtensions.cs @@ -4,7 +4,9 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.Serialization; using AutoMapper; +using AutoMapper.Internal; using osu.Framework.Development; using osu.Game.Beatmaps; using osu.Game.Input.Bindings; @@ -60,7 +62,14 @@ namespace osu.Game.Database } }); - c.AddGlobalIgnore(nameof(RealmObjectBase.ObjectSchema)); + c.Internal().ForAllMaps((typeMap, expression) => + { + expression.ForAllMembers(m => + { + if (m.DestinationMember.Has() || m.DestinationMember.Has() || m.DestinationMember.Has()) + m.Ignore(); + }); + }); }).CreateMapper(); private static readonly IMapper mapper = new MapperConfiguration(c => @@ -80,24 +89,35 @@ namespace osu.Game.Database c.CreateMap(); c.CreateMap(); c.CreateMap(); - c.CreateMap().MaxDepth(2).AfterMap((s, d) => - { - for (int i = 0; i < d.BeatmapSet?.Beatmaps.Count; i++) - { - if (d.BeatmapSet.Beatmaps[i].Equals(d)) - { - d.BeatmapSet.Beatmaps[i] = d; - break; - } - } - }); - c.CreateMap().MaxDepth(2).AfterMap((s, d) => - { - foreach (var beatmap in d.Beatmaps) - beatmap.BeatmapSet = d; - }); + c.CreateMap() + .MaxDepth(2) + .AfterMap((s, d) => + { + for (int i = 0; i < d.BeatmapSet?.Beatmaps.Count; i++) + { + if (d.BeatmapSet.Beatmaps[i].Equals(d)) + { + d.BeatmapSet.Beatmaps[i] = d; + break; + } + } + }); + c.CreateMap() + .MaxDepth(2) + .AfterMap((s, d) => + { + foreach (var beatmap in d.Beatmaps) + beatmap.BeatmapSet = d; + }); - c.AddGlobalIgnore(nameof(RealmObjectBase.ObjectSchema)); + c.Internal().ForAllMaps((typeMap, expression) => + { + expression.ForAllMembers(m => + { + if (m.DestinationMember.Has() || m.DestinationMember.Has() || m.DestinationMember.Has()) + m.Ignore(); + }); + }); }).CreateMapper(); /// diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index fd0f14266a..5614f34f3f 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using AutoMapper; using JetBrains.Annotations; using Newtonsoft.Json; using osu.Framework.Localisation; @@ -85,7 +84,7 @@ namespace osu.Game.Scoring // Eventually we should either persist enough information to realm to not require the API lookups, or perform the API lookups locally. private APIUser? user; - [IgnoreMap] + [Ignored] public APIUser User { get => user ??= new APIUser From 7084183d6c3a3a126d0c5ddc2401df16e264fab8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 Jan 2022 15:43:02 +0900 Subject: [PATCH 451/996] Fix test beatmaps created without hash being populated --- osu.Game.Tests/Resources/TestResources.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Resources/TestResources.cs b/osu.Game.Tests/Resources/TestResources.cs index d0ffb9c3db..1d99a5c20d 100644 --- a/osu.Game.Tests/Resources/TestResources.cs +++ b/osu.Game.Tests/Resources/TestResources.cs @@ -130,6 +130,7 @@ namespace osu.Game.Tests.Resources StarRating = diff, Length = length, BPM = bpm, + Hash = Guid.NewGuid().ToString().ComputeMD5Hash(), Ruleset = rulesetInfo, Metadata = metadata, BaseDifficulty = new BeatmapDifficulty From a5862ca00df2e6839abdb47e5dd64f9270d704c5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 Jan 2022 15:46:27 +0900 Subject: [PATCH 452/996] Improve reliability of mod deserialisation --- osu.Game/Scoring/ScoreInfo.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 5614f34f3f..e1328d8a06 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -186,10 +186,7 @@ namespace osu.Game.Scoring if (mods != null) return mods; - if (apiMods != null) - return APIMods.Select(m => m.ToMod(Ruleset.CreateInstance())).ToArray(); - - return Array.Empty(); + return APIMods.Select(m => m.ToMod(Ruleset.CreateInstance())).ToArray(); } set { @@ -216,7 +213,7 @@ namespace osu.Game.Scoring // then check mods set via Mods property. if (mods != null) - apiMods = mods.Select(m => new APIMod(m)).ToArray(); + apiMods ??= mods.Select(m => new APIMod(m)).ToArray(); return apiMods ?? Array.Empty(); } From 246a4a4bfe3abb8219965c55a9bd56c0317953d0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 Jan 2022 16:05:12 +0900 Subject: [PATCH 453/996] Add support for starting with a fresh realm database if the existing one is not usable The most common scenario is switching between schema versions when testing. This should alleviate the manual overhead of that for the majority of cases. For users, this will show a notification on startup if their database was purged, similar to what we had with EF. --- osu.Game/Database/RealmContextFactory.cs | 37 +++++++++++++++++++----- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 8548e63e94..e6fe5dfc54 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -105,8 +105,20 @@ namespace osu.Game.Database if (!Filename.EndsWith(realm_extension, StringComparison.Ordinal)) Filename += realm_extension; - // This method triggers the first `CreateContext` call, which will implicitly run realm migrations and bring the schema up-to-date. - cleanupPendingDeletions(); + try + { + // This method triggers the first `CreateContext` call, which will implicitly run realm migrations and bring the schema up-to-date. + cleanupPendingDeletions(); + } + catch (Exception e) + { + 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}"); + storage.Delete(Filename); + + cleanupPendingDeletions(); + } } private void cleanupPendingDeletions() @@ -396,14 +408,23 @@ namespace osu.Game.Database const int sleep_length = 200; int timeout = 5000; - // see https://github.com/realm/realm-dotnet/discussions/2657 - while (!Compact()) + try { - Thread.Sleep(sleep_length); - timeout -= sleep_length; + // see https://github.com/realm/realm-dotnet/discussions/2657 + while (!Compact()) + { + Thread.Sleep(sleep_length); + timeout -= sleep_length; - if (timeout < 0) - throw new TimeoutException(@"Took too long to acquire lock"); + if (timeout < 0) + throw new TimeoutException(@"Took too long to acquire lock"); + } + } + catch (Exception e) + { + // Compact may fail if the realm is in a bad state. + // We still want to continue with the blocking operation, though. + Logger.Log($"Realm compact failed with error {e}", LoggingTarget.Database); } } catch From 8978b88f69300d868635d295104d093b0aafe76b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 Jan 2022 16:22:11 +0900 Subject: [PATCH 454/996] Add test coverage of startup ruleset being non-default --- .../Navigation/TestSceneStartupRuleset.cs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 osu.Game.Tests/Visual/Navigation/TestSceneStartupRuleset.cs diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneStartupRuleset.cs b/osu.Game.Tests/Visual/Navigation/TestSceneStartupRuleset.cs new file mode 100644 index 0000000000..85dd501fd3 --- /dev/null +++ b/osu.Game.Tests/Visual/Navigation/TestSceneStartupRuleset.cs @@ -0,0 +1,32 @@ +// 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.Framework.Development; +using osu.Game.Configuration; + +namespace osu.Game.Tests.Visual.Navigation +{ + [TestFixture] + public class TestSceneStartupRuleset : OsuGameTestScene + { + protected override TestOsuGame CreateTestGame() + { + // Must be done in this function due to the RecycleLocalStorage call just before. + var config = DebugUtils.IsDebugBuild + ? new DevelopmentOsuConfigManager(LocalStorage) + : new OsuConfigManager(LocalStorage); + + config.SetValue(OsuSetting.Ruleset, "mania"); + config.Save(); + + return base.CreateTestGame(); + } + + [Test] + public void TestRulesetConsumed() + { + AddUntilStep("ruleset correct", () => Game.Ruleset.Value.ShortName == "mania"); + } + } +} From 8b8940439e770184fe80ce0c87d4387850575cc1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 Jan 2022 16:11:03 +0900 Subject: [PATCH 455/996] Fix starting game with non-default ruleset failing --- osu.Game/OsuGameBase.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 8126b74418..4c6e9689d3 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -451,13 +451,13 @@ namespace osu.Game private void onBeatmapChanged(ValueChangedEvent valueChangedEvent) { - if (!ThreadSafety.IsUpdateThread) + if (IsLoaded && !ThreadSafety.IsUpdateThread) throw new InvalidOperationException("Global beatmap bindable must be changed from update thread."); } private void onRulesetChanged(ValueChangedEvent r) { - if (!ThreadSafety.IsUpdateThread) + if (IsLoaded && !ThreadSafety.IsUpdateThread) throw new InvalidOperationException("Global ruleset bindable must be changed from update thread."); if (r.NewValue?.Available != true) From 488f0449249951fd204b4551b6beae7e290e43e0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 Jan 2022 16:46:14 +0900 Subject: [PATCH 456/996] Remove one more outdated comment --- osu.Game/Tests/Visual/OsuTestScene.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index c631286d11..4af8ec3a99 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -294,10 +294,6 @@ namespace osu.Game.Tests.Visual if (MusicController?.TrackLoaded == true) MusicController.Stop(); - // TODO: what should we do here, if anything? should we use an in-memory realm in this instance? - // if (contextFactory?.IsValueCreated == true) - // contextFactory.Value.ResetDatabase(); - RecycleLocalStorage(true); } From 3e9e7a8fb61e6af4279b4ee405e9aa8c42674c95 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 Jan 2022 16:48:27 +0900 Subject: [PATCH 457/996] Fix `PLaylistsResultsScreen` tests falling over due to missing beatmap --- .../Visual/Playlists/TestScenePlaylistsResultsScreen.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs index e9210496ca..11df115b1a 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs @@ -44,6 +44,10 @@ namespace osu.Game.Tests.Visual.Playlists requestComplete = false; totalCount = 0; bindHandler(); + + // beatmap is required to be an actual beatmap so the scores can get their scores correctly calculated for standardised scoring. + // else the tests that rely on ordering will fall over. + Beatmap.Value = CreateWorkingBeatmap(Ruleset.Value); }); [Test] From 27ea37c6907cbfe67946bcc62f3f4796b94e8011 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 17 Jan 2022 18:48:11 +0900 Subject: [PATCH 458/996] Rewrite `TopLocalRank` to use realm subscriptions This addresses the number one performance concern with realm (when entering song select). Previous logic was causing instantiation and property reads of every score in the database due to the `AsEnumerable` specfication. --- .../Screens/Select/Carousel/TopLocalRank.cs | 75 ++++++------------- 1 file changed, 24 insertions(+), 51 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs index fb1fa09253..f12de8562d 100644 --- a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs +++ b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs @@ -6,9 +6,9 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Database; +using osu.Game.Models; using osu.Game.Online.API; using osu.Game.Online.Leaderboards; using osu.Game.Rulesets; @@ -30,72 +30,45 @@ namespace osu.Game.Screens.Select.Carousel [Resolved] private IAPIProvider api { get; set; } + private IDisposable scoreSubscription; + + private bool rankUpdatePending; + public TopLocalRank(BeatmapInfo beatmapInfo) : base(null) { this.beatmapInfo = beatmapInfo; } - [BackgroundDependencyLoader] - private void load() - { - ruleset.ValueChanged += _ => fetchAndLoadTopScore(); - - fetchAndLoadTopScore(); - } - protected override void LoadComplete() { base.LoadComplete(); - scoreSubscription = realmFactory.Context.All() - .Filter($"{nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} = $0", beatmapInfo.ID) - .QueryAsyncWithNotifications((_, changes, ___) => - { - if (changes == null) - return; - - fetchTopScoreRank(); - }); - } - - private IDisposable scoreSubscription; - - private ScheduledDelegate scheduledRankUpdate; - - private void fetchAndLoadTopScore() - { - // TODO: this lookup likely isn't required, we can use the results of the subscription directly. - var rank = fetchTopScoreRank(); - - scheduledRankUpdate = Scheduler.Add(() => + ruleset.BindValueChanged(_ => { - Rank = rank; - + rankUpdatePending = true; // Required since presence is changed via IsPresent override Invalidate(Invalidation.Presence); - }); + + scoreSubscription?.Dispose(); + scoreSubscription = realmFactory.Context.All() + .Filter($"{nameof(ScoreInfo.User)}.{nameof(RealmUser.OnlineID)} == $0" + + $"&& {nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} == $1" + + $"&& {nameof(ScoreInfo.Ruleset)}.{nameof(RulesetInfo.ShortName)} == $2" + + $"&& {nameof(ScoreInfo.DeletePending)} == false", api.LocalUser.Value.Id, beatmapInfo.ID, ruleset.Value.ShortName) + .OrderByDescending(s => s.TotalScore) + .QueryAsyncWithNotifications((items, changes, ___) => + { + if (changes == null) + rankUpdatePending = false; + + Rank = items.FirstOrDefault()?.Rank; + }); + }, true); } // We're present if a rank is set, or if there is a pending rank update (IsPresent = true is required for the scheduler to run). - public override bool IsPresent => base.IsPresent && (Rank != null || scheduledRankUpdate?.Completed == false); - - private ScoreRank? fetchTopScoreRank() - { - if (realmFactory == null || beatmapInfo == null || ruleset?.Value == null || api?.LocalUser.Value == null) - return null; - - using (var realm = realmFactory.CreateContext()) - { - return realm.All() - .AsEnumerable() - // TODO: update to use a realm filter directly (or at least figure out the beatmap part to reduce scope). - .Where(s => s.UserID == api.LocalUser.Value.Id && s.BeatmapInfoID == beatmapInfo.ID && s.RulesetID == ruleset.Value.ID && !s.DeletePending) - .OrderByDescending(s => s.TotalScore) - .FirstOrDefault() - ?.Rank; - } - } + public override bool IsPresent => base.IsPresent && (Rank != null || rankUpdatePending); protected override void Dispose(bool isDisposing) { From 3b0977903bcb7490dcc488a13ca6dc80f39d3dc0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 Jan 2022 19:12:10 +0900 Subject: [PATCH 459/996] Use `IQueryable` directly to avoid insane overheads --- osu.Game/Database/EFToRealmMigrator.cs | 37 +++++++++++++------------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index 8f5de80e3b..fc75f73e4d 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using System.Linq; using Microsoft.EntityFrameworkCore; using osu.Framework.Logging; @@ -54,13 +53,12 @@ namespace osu.Game.Database private void migrateBeatmaps(DatabaseWriteUsage ef) { // can be removed 20220730. - List existingBeatmapSets = ef.Context.EFBeatmapSetInfo - .Include(s => s.Beatmaps).ThenInclude(b => b.RulesetInfo) - .Include(s => s.Beatmaps).ThenInclude(b => b.Metadata) - .Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty) - .Include(s => s.Files).ThenInclude(f => f.FileInfo) - .Include(s => s.Metadata) - .ToList(); + var existingBeatmapSets = ef.Context.EFBeatmapSetInfo + .Include(s => s.Beatmaps).ThenInclude(b => b.RulesetInfo) + .Include(s => s.Beatmaps).ThenInclude(b => b.Metadata) + .Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty) + .Include(s => s.Files).ThenInclude(f => f.FileInfo) + .Include(s => s.Metadata); Logger.Log("Beginning beatmaps migration to realm", LoggingTarget.Database); @@ -71,9 +69,11 @@ namespace osu.Game.Database return; } + int count = existingBeatmapSets.Count(); + using (var realm = realmContextFactory.CreateContext()) { - Logger.Log($"Found {existingBeatmapSets.Count} beatmaps in EF", LoggingTarget.Database); + Logger.Log($"Found {count} beatmaps in EF", LoggingTarget.Database); string migration = $"before_beatmap_migration_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}"; efContextFactory.CreateBackup($"client.{migration}.db"); @@ -147,7 +147,7 @@ namespace osu.Game.Database } } - Logger.Log($"Successfully migrated {existingBeatmapSets.Count} beatmaps to realm", LoggingTarget.Database); + Logger.Log($"Successfully migrated {count} beatmaps to realm", LoggingTarget.Database); ef.Context.RemoveRange(existingBeatmapSets); // Intentionally don't clean up the files, so they don't get purged by EF. } @@ -180,12 +180,11 @@ namespace osu.Game.Database private void migrateScores(DatabaseWriteUsage db) { // can be removed 20220730. - List existingScores = db.Context.ScoreInfo - .Include(s => s.Ruleset) - .Include(s => s.BeatmapInfo) - .Include(s => s.Files) - .ThenInclude(f => f.FileInfo) - .ToList(); + var existingScores = db.Context.ScoreInfo + .Include(s => s.Ruleset) + .Include(s => s.BeatmapInfo) + .Include(s => s.Files) + .ThenInclude(f => f.FileInfo); Logger.Log("Beginning scores migration to realm", LoggingTarget.Database); @@ -196,9 +195,11 @@ namespace osu.Game.Database return; } + int count = existingScores.Count(); + using (var realm = realmContextFactory.CreateContext()) { - Logger.Log($"Found {existingScores.Count} scores in EF", LoggingTarget.Database); + Logger.Log($"Found {count} scores in EF", LoggingTarget.Database); string migration = $"before_score_migration_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}"; efContextFactory.CreateBackup($"client.{migration}.db"); @@ -251,7 +252,7 @@ namespace osu.Game.Database } } - Logger.Log($"Successfully migrated {existingScores.Count} scores to realm", LoggingTarget.Database); + Logger.Log($"Successfully migrated {count} scores to realm", LoggingTarget.Database); db.Context.RemoveRange(existingScores); // Intentionally don't clean up the files, so they don't get purged by EF. } From 519f7e6ad2ba914bb79a7f44563294f70a2b4204 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 Jan 2022 19:14:27 +0900 Subject: [PATCH 460/996] Don't bother with removing from EF as the file is going to be deleted anyway --- osu.Game/Database/EFToRealmMigrator.cs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index fc75f73e4d..1a8b312c6c 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -148,8 +148,6 @@ namespace osu.Game.Database } Logger.Log($"Successfully migrated {count} beatmaps to realm", LoggingTarget.Database); - ef.Context.RemoveRange(existingBeatmapSets); - // Intentionally don't clean up the files, so they don't get purged by EF. } } @@ -253,8 +251,6 @@ namespace osu.Game.Database } Logger.Log($"Successfully migrated {count} scores to realm", LoggingTarget.Database); - db.Context.RemoveRange(existingScores); - // Intentionally don't clean up the files, so they don't get purged by EF. } } @@ -313,9 +309,6 @@ namespace osu.Game.Database } } - db.Context.RemoveRange(existingSkins); - // Intentionally don't clean up the files, so they don't get purged by EF. - transaction.Commit(); } } @@ -372,8 +365,6 @@ namespace osu.Game.Database } } - db.Context.RemoveRange(existingSettings); - transaction.Commit(); } } From f5b8653491279056417a7676fcbf0b677bdbbe1b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 Jan 2022 19:28:09 +0900 Subject: [PATCH 461/996] Add spaces to query string --- osu.Game/Screens/Select/Carousel/TopLocalRank.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs index f12de8562d..4542ab897b 100644 --- a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs +++ b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs @@ -53,9 +53,9 @@ namespace osu.Game.Screens.Select.Carousel scoreSubscription?.Dispose(); scoreSubscription = realmFactory.Context.All() .Filter($"{nameof(ScoreInfo.User)}.{nameof(RealmUser.OnlineID)} == $0" - + $"&& {nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} == $1" - + $"&& {nameof(ScoreInfo.Ruleset)}.{nameof(RulesetInfo.ShortName)} == $2" - + $"&& {nameof(ScoreInfo.DeletePending)} == false", api.LocalUser.Value.Id, beatmapInfo.ID, ruleset.Value.ShortName) + + $" && {nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} == $1" + + $" && {nameof(ScoreInfo.Ruleset)}.{nameof(RulesetInfo.ShortName)} == $2" + + $" && {nameof(ScoreInfo.DeletePending)} == false", api.LocalUser.Value.Id, beatmapInfo.ID, ruleset.Value.ShortName) .OrderByDescending(s => s.TotalScore) .QueryAsyncWithNotifications((items, changes, ___) => { From bb5b9458e8130a2a5f213e968f5590d00ede3d87 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 12 Jan 2022 17:03:23 +0900 Subject: [PATCH 462/996] Remove all EF migrations --- .../20171019041408_InitialCreate.Designer.cs | 293 ---------- .../20171019041408_InitialCreate.cs | 311 ----------- ...025071459_AddMissingIndexRules.Designer.cs | 299 ---------- .../20171025071459_AddMissingIndexRules.cs | 80 --- ...eatmapOnlineIDUniqueConstraint.Designer.cs | 302 ---------- ...5731_AddBeatmapOnlineIDUniqueConstraint.cs | 23 - ...034410_AddRulesetInfoShortName.Designer.cs | 307 ----------- .../20171209034410_AddRulesetInfoShortName.cs | 33 -- .../20180125143340_Settings.Designer.cs | 329 ----------- .../Migrations/20180125143340_Settings.cs | 55 -- .../20180131154205_AddMuteBinding.cs | 23 - .../20180219060912_AddSkins.Designer.cs | 379 ------------- .../Migrations/20180219060912_AddSkins.cs | 71 --- ...54_RemoveUniqueHashConstraints.Designer.cs | 377 ------------- ...80529055154_RemoveUniqueHashConstraints.cs | 51 -- ...111_UpdateTaikoDefaultBindings.Designer.cs | 376 ------------- ...180621044111_UpdateTaikoDefaultBindings.cs | 17 - ...628011956_RemoveNegativeSetIDs.Designer.cs | 376 ------------- .../20180628011956_RemoveNegativeSetIDs.cs | 18 - .../20180913080842_AddRankStatus.Designer.cs | 380 ------------- .../20180913080842_AddRankStatus.cs | 33 -- ...0181007180454_StandardizePaths.Designer.cs | 380 ------------- .../20181007180454_StandardizePaths.cs | 26 - ...20181128100659_AddSkinInfoHash.Designer.cs | 387 ------------- .../20181128100659_AddSkinInfoHash.cs | 41 -- ...81130113755_AddScoreInfoTables.Designer.cs | 484 ---------------- .../20181130113755_AddScoreInfoTables.cs | 112 ---- ...20190225062029_AddUserIDColumn.Designer.cs | 487 ----------------- .../20190225062029_AddUserIDColumn.cs | 22 - .../20190525060824_SkinSettings.Designer.cs | 498 ----------------- .../Migrations/20190525060824_SkinSettings.cs | 54 -- ...AddDateAddedColumnToBeatmapSet.Designer.cs | 489 ----------------- ...05091246_AddDateAddedColumnToBeatmapSet.cs | 24 - ...8070844_AddBPMAndLengthColumns.Designer.cs | 504 ----------------- .../20190708070844_AddBPMAndLengthColumns.cs | 33 -- ...20190913104727_AddBeatmapVideo.Designer.cs | 506 ----------------- .../20190913104727_AddBeatmapVideo.cs | 22 - ...02094919_RefreshVolumeBindings.Designer.cs | 506 ----------------- .../20200302094919_RefreshVolumeBindings.cs | 16 - ...01019224408_AddEpilepsyWarning.Designer.cs | 508 ----------------- .../20201019224408_AddEpilepsyWarning.cs | 23 - ...700_RefreshVolumeBindingsAgain.Designer.cs | 506 ----------------- ...210412045700_RefreshVolumeBindingsAgain.cs | 16 - ...60743_AddSkinInstantiationInfo.Designer.cs | 508 ----------------- ...20210511060743_AddSkinInstantiationInfo.cs | 22 - ...9_AddAuthorIdToBeatmapMetadata.Designer.cs | 511 ----------------- ...0514062639_AddAuthorIdToBeatmapMetadata.cs | 23 - ...824185035_AddCountdownSettings.Designer.cs | 513 ----------------- .../20210824185035_AddCountdownSettings.cs | 23 - ...11_AddSamplesMatchPlaybackRate.Designer.cs | 515 ------------------ ...10912144011_AddSamplesMatchPlaybackRate.cs | 23 - .../20211020081609_ResetSkinHashes.cs | 23 - .../Migrations/OsuDbContextModelSnapshot.cs | 513 ----------------- 53 files changed, 12451 deletions(-) delete mode 100644 osu.Game/Migrations/20171019041408_InitialCreate.Designer.cs delete mode 100644 osu.Game/Migrations/20171019041408_InitialCreate.cs delete mode 100644 osu.Game/Migrations/20171025071459_AddMissingIndexRules.Designer.cs delete mode 100644 osu.Game/Migrations/20171025071459_AddMissingIndexRules.cs delete mode 100644 osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.Designer.cs delete mode 100644 osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.cs delete mode 100644 osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.Designer.cs delete mode 100644 osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.cs delete mode 100644 osu.Game/Migrations/20180125143340_Settings.Designer.cs delete mode 100644 osu.Game/Migrations/20180125143340_Settings.cs delete mode 100644 osu.Game/Migrations/20180131154205_AddMuteBinding.cs delete mode 100644 osu.Game/Migrations/20180219060912_AddSkins.Designer.cs delete mode 100644 osu.Game/Migrations/20180219060912_AddSkins.cs delete mode 100644 osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.Designer.cs delete mode 100644 osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.cs delete mode 100644 osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.Designer.cs delete mode 100644 osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.cs delete mode 100644 osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.Designer.cs delete mode 100644 osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.cs delete mode 100644 osu.Game/Migrations/20180913080842_AddRankStatus.Designer.cs delete mode 100644 osu.Game/Migrations/20180913080842_AddRankStatus.cs delete mode 100644 osu.Game/Migrations/20181007180454_StandardizePaths.Designer.cs delete mode 100644 osu.Game/Migrations/20181007180454_StandardizePaths.cs delete mode 100644 osu.Game/Migrations/20181128100659_AddSkinInfoHash.Designer.cs delete mode 100644 osu.Game/Migrations/20181128100659_AddSkinInfoHash.cs delete mode 100644 osu.Game/Migrations/20181130113755_AddScoreInfoTables.Designer.cs delete mode 100644 osu.Game/Migrations/20181130113755_AddScoreInfoTables.cs delete mode 100644 osu.Game/Migrations/20190225062029_AddUserIDColumn.Designer.cs delete mode 100644 osu.Game/Migrations/20190225062029_AddUserIDColumn.cs delete mode 100644 osu.Game/Migrations/20190525060824_SkinSettings.Designer.cs delete mode 100644 osu.Game/Migrations/20190525060824_SkinSettings.cs delete mode 100644 osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.Designer.cs delete mode 100644 osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.cs delete mode 100644 osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.Designer.cs delete mode 100644 osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.cs delete mode 100644 osu.Game/Migrations/20190913104727_AddBeatmapVideo.Designer.cs delete mode 100644 osu.Game/Migrations/20190913104727_AddBeatmapVideo.cs delete mode 100644 osu.Game/Migrations/20200302094919_RefreshVolumeBindings.Designer.cs delete mode 100644 osu.Game/Migrations/20200302094919_RefreshVolumeBindings.cs delete mode 100644 osu.Game/Migrations/20201019224408_AddEpilepsyWarning.Designer.cs delete mode 100644 osu.Game/Migrations/20201019224408_AddEpilepsyWarning.cs delete mode 100644 osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.Designer.cs delete mode 100644 osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.cs delete mode 100644 osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.Designer.cs delete mode 100644 osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.cs delete mode 100644 osu.Game/Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.Designer.cs delete mode 100644 osu.Game/Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.cs delete mode 100644 osu.Game/Migrations/20210824185035_AddCountdownSettings.Designer.cs delete mode 100644 osu.Game/Migrations/20210824185035_AddCountdownSettings.cs delete mode 100644 osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.Designer.cs delete mode 100644 osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.cs delete mode 100644 osu.Game/Migrations/20211020081609_ResetSkinHashes.cs delete mode 100644 osu.Game/Migrations/OsuDbContextModelSnapshot.cs diff --git a/osu.Game/Migrations/20171019041408_InitialCreate.Designer.cs b/osu.Game/Migrations/20171019041408_InitialCreate.Designer.cs deleted file mode 100644 index c751530bf4..0000000000 --- a/osu.Game/Migrations/20171019041408_InitialCreate.Designer.cs +++ /dev/null @@ -1,293 +0,0 @@ -// -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage; -using osu.Game.Database; -using System; - -namespace osu.Game.Migrations -{ - [DbContext(typeof(OsuDbContext))] - [Migration("20171019041408_InitialCreate")] - partial class InitialCreate - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.0.0-rtm-26452"); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("ApproachRate"); - - b.Property("CircleSize"); - - b.Property("DrainRate"); - - b.Property("OverallDifficulty"); - - b.Property("SliderMultiplier"); - - b.Property("SliderTickRate"); - - b.HasKey("ID"); - - b.ToTable("BeatmapDifficulty"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("AudioLeadIn"); - - b.Property("BaseDifficultyID"); - - b.Property("BeatDivisor"); - - b.Property("BeatmapSetInfoID"); - - b.Property("Countdown"); - - b.Property("DistanceSpacing"); - - b.Property("GridSize"); - - b.Property("Hash"); - - b.Property("Hidden"); - - b.Property("LetterboxInBreaks"); - - b.Property("MD5Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapID"); - - b.Property("Path"); - - b.Property("RulesetID"); - - b.Property("SpecialStyle"); - - b.Property("StackLeniency"); - - b.Property("StarDifficulty"); - - b.Property("StoredBookmarks"); - - b.Property("TimelineZoom"); - - b.Property("Version"); - - b.Property("WidescreenStoryboard"); - - b.HasKey("ID"); - - b.HasIndex("BaseDifficultyID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("Hash"); - - b.HasIndex("MD5Hash"); - - b.HasIndex("MetadataID"); - - b.HasIndex("RulesetID"); - - b.ToTable("BeatmapInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Artist"); - - b.Property("ArtistUnicode"); - - b.Property("AudioFile"); - - b.Property("AuthorString") - .HasColumnName("Author"); - - b.Property("BackgroundFile"); - - b.Property("PreviewTime"); - - b.Property("Source"); - - b.Property("Tags"); - - b.Property("Title"); - - b.Property("TitleUnicode"); - - b.HasKey("ID"); - - b.ToTable("BeatmapMetadata"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("BeatmapSetInfoID"); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.HasKey("ID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("FileInfoID"); - - b.ToTable("BeatmapSetFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapSetID"); - - b.Property("Protected"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash"); - - b.HasIndex("MetadataID"); - - b.ToTable("BeatmapSetInfo"); - }); - - modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntAction") - .HasColumnName("Action"); - - b.Property("KeysString") - .HasColumnName("Keys"); - - b.Property("RulesetID"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("IntAction"); - - b.HasIndex("Variant"); - - b.ToTable("KeyBinding"); - }); - - modelBuilder.Entity("osu.Game.IO.FileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Hash"); - - b.Property("ReferenceCount"); - - b.HasKey("ID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("ReferenceCount"); - - b.ToTable("FileInfo"); - }); - - modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Available"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.HasKey("ID"); - - b.HasIndex("Available"); - - b.ToTable("RulesetInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") - .WithMany() - .HasForeignKey("BaseDifficultyID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") - .WithMany("Beatmaps") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("Beatmaps") - .HasForeignKey("MetadataID"); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") - .WithMany("Files") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("BeatmapSets") - .HasForeignKey("MetadataID"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/osu.Game/Migrations/20171019041408_InitialCreate.cs b/osu.Game/Migrations/20171019041408_InitialCreate.cs deleted file mode 100644 index 9b6881f98c..0000000000 --- a/osu.Game/Migrations/20171019041408_InitialCreate.cs +++ /dev/null @@ -1,311 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace osu.Game.Migrations -{ - public partial class InitialCreate : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "BeatmapDifficulty", - columns: table => new - { - ID = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - ApproachRate = table.Column(type: "REAL", nullable: false), - CircleSize = table.Column(type: "REAL", nullable: false), - DrainRate = table.Column(type: "REAL", nullable: false), - OverallDifficulty = table.Column(type: "REAL", nullable: false), - SliderMultiplier = table.Column(type: "REAL", nullable: false), - SliderTickRate = table.Column(type: "REAL", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_BeatmapDifficulty", x => x.ID); - }); - - migrationBuilder.CreateTable( - name: "BeatmapMetadata", - columns: table => new - { - ID = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Artist = table.Column(type: "TEXT", nullable: true), - ArtistUnicode = table.Column(type: "TEXT", nullable: true), - AudioFile = table.Column(type: "TEXT", nullable: true), - Author = table.Column(type: "TEXT", nullable: true), - BackgroundFile = table.Column(type: "TEXT", nullable: true), - PreviewTime = table.Column(type: "INTEGER", nullable: false), - Source = table.Column(type: "TEXT", nullable: true), - Tags = table.Column(type: "TEXT", nullable: true), - Title = table.Column(type: "TEXT", nullable: true), - TitleUnicode = table.Column(type: "TEXT", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_BeatmapMetadata", x => x.ID); - }); - - migrationBuilder.CreateTable( - name: "FileInfo", - columns: table => new - { - ID = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Hash = table.Column(type: "TEXT", nullable: true), - ReferenceCount = table.Column(type: "INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_FileInfo", x => x.ID); - }); - - migrationBuilder.CreateTable( - name: "KeyBinding", - columns: table => new - { - ID = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Action = table.Column(type: "INTEGER", nullable: false), - Keys = table.Column(type: "TEXT", nullable: true), - RulesetID = table.Column(type: "INTEGER", nullable: true), - Variant = table.Column(type: "INTEGER", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_KeyBinding", x => x.ID); - }); - - migrationBuilder.CreateTable( - name: "RulesetInfo", - columns: table => new - { - ID = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Available = table.Column(type: "INTEGER", nullable: false), - InstantiationInfo = table.Column(type: "TEXT", nullable: true), - Name = table.Column(type: "TEXT", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_RulesetInfo", x => x.ID); - }); - - migrationBuilder.CreateTable( - name: "BeatmapSetInfo", - columns: table => new - { - ID = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - DeletePending = table.Column(type: "INTEGER", nullable: false), - Hash = table.Column(type: "TEXT", nullable: true), - MetadataID = table.Column(type: "INTEGER", nullable: true), - OnlineBeatmapSetID = table.Column(type: "INTEGER", nullable: true), - Protected = table.Column(type: "INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_BeatmapSetInfo", x => x.ID); - table.ForeignKey( - name: "FK_BeatmapSetInfo_BeatmapMetadata_MetadataID", - column: x => x.MetadataID, - principalTable: "BeatmapMetadata", - principalColumn: "ID", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateTable( - name: "BeatmapInfo", - columns: table => new - { - ID = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - AudioLeadIn = table.Column(type: "INTEGER", nullable: false), - BaseDifficultyID = table.Column(type: "INTEGER", nullable: false), - BeatDivisor = table.Column(type: "INTEGER", nullable: false), - BeatmapSetInfoID = table.Column(type: "INTEGER", nullable: false), - Countdown = table.Column(type: "INTEGER", nullable: false), - DistanceSpacing = table.Column(type: "REAL", nullable: false), - GridSize = table.Column(type: "INTEGER", nullable: false), - Hash = table.Column(type: "TEXT", nullable: true), - Hidden = table.Column(type: "INTEGER", nullable: false), - LetterboxInBreaks = table.Column(type: "INTEGER", nullable: false), - MD5Hash = table.Column(type: "TEXT", nullable: true), - MetadataID = table.Column(type: "INTEGER", nullable: true), - OnlineBeatmapID = table.Column(type: "INTEGER", nullable: true), - Path = table.Column(type: "TEXT", nullable: true), - RulesetID = table.Column(type: "INTEGER", nullable: false), - SpecialStyle = table.Column(type: "INTEGER", nullable: false), - StackLeniency = table.Column(type: "REAL", nullable: false), - StarDifficulty = table.Column(type: "REAL", nullable: false), - StoredBookmarks = table.Column(type: "TEXT", nullable: true), - TimelineZoom = table.Column(type: "REAL", nullable: false), - Version = table.Column(type: "TEXT", nullable: true), - WidescreenStoryboard = table.Column(type: "INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_BeatmapInfo", x => x.ID); - table.ForeignKey( - name: "FK_BeatmapInfo_BeatmapDifficulty_BaseDifficultyID", - column: x => x.BaseDifficultyID, - principalTable: "BeatmapDifficulty", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_BeatmapInfo_BeatmapSetInfo_BeatmapSetInfoID", - column: x => x.BeatmapSetInfoID, - principalTable: "BeatmapSetInfo", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_BeatmapInfo_BeatmapMetadata_MetadataID", - column: x => x.MetadataID, - principalTable: "BeatmapMetadata", - principalColumn: "ID", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_BeatmapInfo_RulesetInfo_RulesetID", - column: x => x.RulesetID, - principalTable: "RulesetInfo", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "BeatmapSetFileInfo", - columns: table => new - { - ID = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - BeatmapSetInfoID = table.Column(type: "INTEGER", nullable: false), - FileInfoID = table.Column(type: "INTEGER", nullable: false), - Filename = table.Column(type: "TEXT", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_BeatmapSetFileInfo", x => x.ID); - table.ForeignKey( - name: "FK_BeatmapSetFileInfo_BeatmapSetInfo_BeatmapSetInfoID", - column: x => x.BeatmapSetInfoID, - principalTable: "BeatmapSetInfo", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_BeatmapSetFileInfo_FileInfo_FileInfoID", - column: x => x.FileInfoID, - principalTable: "FileInfo", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "IX_BeatmapInfo_BaseDifficultyID", - table: "BeatmapInfo", - column: "BaseDifficultyID"); - - migrationBuilder.CreateIndex( - name: "IX_BeatmapInfo_BeatmapSetInfoID", - table: "BeatmapInfo", - column: "BeatmapSetInfoID"); - - migrationBuilder.CreateIndex( - name: "IX_BeatmapInfo_Hash", - table: "BeatmapInfo", - column: "Hash"); - - migrationBuilder.CreateIndex( - name: "IX_BeatmapInfo_MD5Hash", - table: "BeatmapInfo", - column: "MD5Hash"); - - migrationBuilder.CreateIndex( - name: "IX_BeatmapInfo_MetadataID", - table: "BeatmapInfo", - column: "MetadataID"); - - migrationBuilder.CreateIndex( - name: "IX_BeatmapInfo_RulesetID", - table: "BeatmapInfo", - column: "RulesetID"); - - migrationBuilder.CreateIndex( - name: "IX_BeatmapSetFileInfo_BeatmapSetInfoID", - table: "BeatmapSetFileInfo", - column: "BeatmapSetInfoID"); - - migrationBuilder.CreateIndex( - name: "IX_BeatmapSetFileInfo_FileInfoID", - table: "BeatmapSetFileInfo", - column: "FileInfoID"); - - migrationBuilder.CreateIndex( - name: "IX_BeatmapSetInfo_DeletePending", - table: "BeatmapSetInfo", - column: "DeletePending"); - - migrationBuilder.CreateIndex( - name: "IX_BeatmapSetInfo_Hash", - table: "BeatmapSetInfo", - column: "Hash"); - - migrationBuilder.CreateIndex( - name: "IX_BeatmapSetInfo_MetadataID", - table: "BeatmapSetInfo", - column: "MetadataID"); - - migrationBuilder.CreateIndex( - name: "IX_FileInfo_Hash", - table: "FileInfo", - column: "Hash", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_FileInfo_ReferenceCount", - table: "FileInfo", - column: "ReferenceCount"); - - migrationBuilder.CreateIndex( - name: "IX_KeyBinding_Action", - table: "KeyBinding", - column: "Action"); - - migrationBuilder.CreateIndex( - name: "IX_KeyBinding_Variant", - table: "KeyBinding", - column: "Variant"); - - migrationBuilder.CreateIndex( - name: "IX_RulesetInfo_Available", - table: "RulesetInfo", - column: "Available"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "BeatmapInfo"); - - migrationBuilder.DropTable( - name: "BeatmapSetFileInfo"); - - migrationBuilder.DropTable( - name: "KeyBinding"); - - migrationBuilder.DropTable( - name: "BeatmapDifficulty"); - - migrationBuilder.DropTable( - name: "RulesetInfo"); - - migrationBuilder.DropTable( - name: "BeatmapSetInfo"); - - migrationBuilder.DropTable( - name: "FileInfo"); - - migrationBuilder.DropTable( - name: "BeatmapMetadata"); - } - } -} diff --git a/osu.Game/Migrations/20171025071459_AddMissingIndexRules.Designer.cs b/osu.Game/Migrations/20171025071459_AddMissingIndexRules.Designer.cs deleted file mode 100644 index 4cd234f2ef..0000000000 --- a/osu.Game/Migrations/20171025071459_AddMissingIndexRules.Designer.cs +++ /dev/null @@ -1,299 +0,0 @@ -// -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage; -using osu.Game.Database; -using System; - -namespace osu.Game.Migrations -{ - [DbContext(typeof(OsuDbContext))] - [Migration("20171025071459_AddMissingIndexRules")] - partial class AddMissingIndexRules - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.0.0-rtm-26452"); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("ApproachRate"); - - b.Property("CircleSize"); - - b.Property("DrainRate"); - - b.Property("OverallDifficulty"); - - b.Property("SliderMultiplier"); - - b.Property("SliderTickRate"); - - b.HasKey("ID"); - - b.ToTable("BeatmapDifficulty"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("AudioLeadIn"); - - b.Property("BaseDifficultyID"); - - b.Property("BeatDivisor"); - - b.Property("BeatmapSetInfoID"); - - b.Property("Countdown"); - - b.Property("DistanceSpacing"); - - b.Property("GridSize"); - - b.Property("Hash"); - - b.Property("Hidden"); - - b.Property("LetterboxInBreaks"); - - b.Property("MD5Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapID"); - - b.Property("Path"); - - b.Property("RulesetID"); - - b.Property("SpecialStyle"); - - b.Property("StackLeniency"); - - b.Property("StarDifficulty"); - - b.Property("StoredBookmarks"); - - b.Property("TimelineZoom"); - - b.Property("Version"); - - b.Property("WidescreenStoryboard"); - - b.HasKey("ID"); - - b.HasIndex("BaseDifficultyID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MD5Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("RulesetID"); - - b.ToTable("BeatmapInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Artist"); - - b.Property("ArtistUnicode"); - - b.Property("AudioFile"); - - b.Property("AuthorString") - .HasColumnName("Author"); - - b.Property("BackgroundFile"); - - b.Property("PreviewTime"); - - b.Property("Source"); - - b.Property("Tags"); - - b.Property("Title"); - - b.Property("TitleUnicode"); - - b.HasKey("ID"); - - b.ToTable("BeatmapMetadata"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("BeatmapSetInfoID"); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.HasKey("ID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("FileInfoID"); - - b.ToTable("BeatmapSetFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapSetID"); - - b.Property("Protected"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapSetID") - .IsUnique(); - - b.ToTable("BeatmapSetInfo"); - }); - - modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntAction") - .HasColumnName("Action"); - - b.Property("KeysString") - .HasColumnName("Keys"); - - b.Property("RulesetID"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("IntAction"); - - b.HasIndex("Variant"); - - b.ToTable("KeyBinding"); - }); - - modelBuilder.Entity("osu.Game.IO.FileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Hash"); - - b.Property("ReferenceCount"); - - b.HasKey("ID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("ReferenceCount"); - - b.ToTable("FileInfo"); - }); - - modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Available"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.HasKey("ID"); - - b.HasIndex("Available"); - - b.ToTable("RulesetInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") - .WithMany() - .HasForeignKey("BaseDifficultyID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") - .WithMany("Beatmaps") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("Beatmaps") - .HasForeignKey("MetadataID"); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") - .WithMany("Files") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("BeatmapSets") - .HasForeignKey("MetadataID"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/osu.Game/Migrations/20171025071459_AddMissingIndexRules.cs b/osu.Game/Migrations/20171025071459_AddMissingIndexRules.cs deleted file mode 100644 index c9fc59c5a2..0000000000 --- a/osu.Game/Migrations/20171025071459_AddMissingIndexRules.cs +++ /dev/null @@ -1,80 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace osu.Game.Migrations -{ - public partial class AddMissingIndexRules : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropIndex( - name: "IX_BeatmapSetInfo_Hash", - table: "BeatmapSetInfo"); - - migrationBuilder.DropIndex( - name: "IX_BeatmapInfo_Hash", - table: "BeatmapInfo"); - - migrationBuilder.DropIndex( - name: "IX_BeatmapInfo_MD5Hash", - table: "BeatmapInfo"); - - migrationBuilder.CreateIndex( - name: "IX_BeatmapSetInfo_Hash", - table: "BeatmapSetInfo", - column: "Hash", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_BeatmapSetInfo_OnlineBeatmapSetID", - table: "BeatmapSetInfo", - column: "OnlineBeatmapSetID", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_BeatmapInfo_Hash", - table: "BeatmapInfo", - column: "Hash", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_BeatmapInfo_MD5Hash", - table: "BeatmapInfo", - column: "MD5Hash", - unique: true); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropIndex( - name: "IX_BeatmapSetInfo_Hash", - table: "BeatmapSetInfo"); - - migrationBuilder.DropIndex( - name: "IX_BeatmapSetInfo_OnlineBeatmapSetID", - table: "BeatmapSetInfo"); - - migrationBuilder.DropIndex( - name: "IX_BeatmapInfo_Hash", - table: "BeatmapInfo"); - - migrationBuilder.DropIndex( - name: "IX_BeatmapInfo_MD5Hash", - table: "BeatmapInfo"); - - migrationBuilder.CreateIndex( - name: "IX_BeatmapSetInfo_Hash", - table: "BeatmapSetInfo", - column: "Hash"); - - migrationBuilder.CreateIndex( - name: "IX_BeatmapInfo_Hash", - table: "BeatmapInfo", - column: "Hash"); - - migrationBuilder.CreateIndex( - name: "IX_BeatmapInfo_MD5Hash", - table: "BeatmapInfo", - column: "MD5Hash"); - } - } -} diff --git a/osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.Designer.cs b/osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.Designer.cs deleted file mode 100644 index 006acf12cd..0000000000 --- a/osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.Designer.cs +++ /dev/null @@ -1,302 +0,0 @@ -// -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage; -using osu.Game.Database; -using System; - -namespace osu.Game.Migrations -{ - [DbContext(typeof(OsuDbContext))] - [Migration("20171119065731_AddBeatmapOnlineIDUniqueConstraint")] - partial class AddBeatmapOnlineIDUniqueConstraint - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.0.0-rtm-26452"); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("ApproachRate"); - - b.Property("CircleSize"); - - b.Property("DrainRate"); - - b.Property("OverallDifficulty"); - - b.Property("SliderMultiplier"); - - b.Property("SliderTickRate"); - - b.HasKey("ID"); - - b.ToTable("BeatmapDifficulty"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("AudioLeadIn"); - - b.Property("BaseDifficultyID"); - - b.Property("BeatDivisor"); - - b.Property("BeatmapSetInfoID"); - - b.Property("Countdown"); - - b.Property("DistanceSpacing"); - - b.Property("GridSize"); - - b.Property("Hash"); - - b.Property("Hidden"); - - b.Property("LetterboxInBreaks"); - - b.Property("MD5Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapID"); - - b.Property("Path"); - - b.Property("RulesetID"); - - b.Property("SpecialStyle"); - - b.Property("StackLeniency"); - - b.Property("StarDifficulty"); - - b.Property("StoredBookmarks"); - - b.Property("TimelineZoom"); - - b.Property("Version"); - - b.Property("WidescreenStoryboard"); - - b.HasKey("ID"); - - b.HasIndex("BaseDifficultyID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MD5Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("BeatmapInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Artist"); - - b.Property("ArtistUnicode"); - - b.Property("AudioFile"); - - b.Property("AuthorString") - .HasColumnName("Author"); - - b.Property("BackgroundFile"); - - b.Property("PreviewTime"); - - b.Property("Source"); - - b.Property("Tags"); - - b.Property("Title"); - - b.Property("TitleUnicode"); - - b.HasKey("ID"); - - b.ToTable("BeatmapMetadata"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("BeatmapSetInfoID"); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.HasKey("ID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("FileInfoID"); - - b.ToTable("BeatmapSetFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapSetID"); - - b.Property("Protected"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapSetID") - .IsUnique(); - - b.ToTable("BeatmapSetInfo"); - }); - - modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntAction") - .HasColumnName("Action"); - - b.Property("KeysString") - .HasColumnName("Keys"); - - b.Property("RulesetID"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("IntAction"); - - b.HasIndex("Variant"); - - b.ToTable("KeyBinding"); - }); - - modelBuilder.Entity("osu.Game.IO.FileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Hash"); - - b.Property("ReferenceCount"); - - b.HasKey("ID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("ReferenceCount"); - - b.ToTable("FileInfo"); - }); - - modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Available"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.HasKey("ID"); - - b.HasIndex("Available"); - - b.ToTable("RulesetInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") - .WithMany() - .HasForeignKey("BaseDifficultyID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") - .WithMany("Beatmaps") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("Beatmaps") - .HasForeignKey("MetadataID"); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") - .WithMany("Files") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("BeatmapSets") - .HasForeignKey("MetadataID"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.cs b/osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.cs deleted file mode 100644 index 084ae67940..0000000000 --- a/osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace osu.Game.Migrations -{ - public partial class AddBeatmapOnlineIDUniqueConstraint : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateIndex( - name: "IX_BeatmapInfo_OnlineBeatmapID", - table: "BeatmapInfo", - column: "OnlineBeatmapID", - unique: true); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropIndex( - name: "IX_BeatmapInfo_OnlineBeatmapID", - table: "BeatmapInfo"); - } - } -} diff --git a/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.Designer.cs b/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.Designer.cs deleted file mode 100644 index fc2496bc24..0000000000 --- a/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.Designer.cs +++ /dev/null @@ -1,307 +0,0 @@ -// -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage; -using osu.Game.Database; -using System; - -namespace osu.Game.Migrations -{ - [DbContext(typeof(OsuDbContext))] - [Migration("20171209034410_AddRulesetInfoShortName")] - partial class AddRulesetInfoShortName - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.0.0-rtm-26452"); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("ApproachRate"); - - b.Property("CircleSize"); - - b.Property("DrainRate"); - - b.Property("OverallDifficulty"); - - b.Property("SliderMultiplier"); - - b.Property("SliderTickRate"); - - b.HasKey("ID"); - - b.ToTable("BeatmapDifficulty"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("AudioLeadIn"); - - b.Property("BaseDifficultyID"); - - b.Property("BeatDivisor"); - - b.Property("BeatmapSetInfoID"); - - b.Property("Countdown"); - - b.Property("DistanceSpacing"); - - b.Property("GridSize"); - - b.Property("Hash"); - - b.Property("Hidden"); - - b.Property("LetterboxInBreaks"); - - b.Property("MD5Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapID"); - - b.Property("Path"); - - b.Property("RulesetID"); - - b.Property("SpecialStyle"); - - b.Property("StackLeniency"); - - b.Property("StarDifficulty"); - - b.Property("StoredBookmarks"); - - b.Property("TimelineZoom"); - - b.Property("Version"); - - b.Property("WidescreenStoryboard"); - - b.HasKey("ID"); - - b.HasIndex("BaseDifficultyID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MD5Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("BeatmapInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Artist"); - - b.Property("ArtistUnicode"); - - b.Property("AudioFile"); - - b.Property("AuthorString") - .HasColumnName("Author"); - - b.Property("BackgroundFile"); - - b.Property("PreviewTime"); - - b.Property("Source"); - - b.Property("Tags"); - - b.Property("Title"); - - b.Property("TitleUnicode"); - - b.HasKey("ID"); - - b.ToTable("BeatmapMetadata"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("BeatmapSetInfoID"); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.HasKey("ID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("FileInfoID"); - - b.ToTable("BeatmapSetFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapSetID"); - - b.Property("Protected"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapSetID") - .IsUnique(); - - b.ToTable("BeatmapSetInfo"); - }); - - modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntAction") - .HasColumnName("Action"); - - b.Property("KeysString") - .HasColumnName("Keys"); - - b.Property("RulesetID"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("IntAction"); - - b.HasIndex("Variant"); - - b.ToTable("KeyBinding"); - }); - - modelBuilder.Entity("osu.Game.IO.FileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Hash"); - - b.Property("ReferenceCount"); - - b.HasKey("ID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("ReferenceCount"); - - b.ToTable("FileInfo"); - }); - - modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Available"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.Property("ShortName"); - - b.HasKey("ID"); - - b.HasIndex("Available"); - - b.HasIndex("ShortName") - .IsUnique(); - - b.ToTable("RulesetInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") - .WithMany() - .HasForeignKey("BaseDifficultyID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") - .WithMany("Beatmaps") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("Beatmaps") - .HasForeignKey("MetadataID"); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") - .WithMany("Files") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("BeatmapSets") - .HasForeignKey("MetadataID"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.cs b/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.cs deleted file mode 100644 index 09cf0af89c..0000000000 --- a/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace osu.Game.Migrations -{ - public partial class AddRulesetInfoShortName : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "ShortName", - table: "RulesetInfo", - type: "TEXT", - nullable: true); - - migrationBuilder.CreateIndex( - name: "IX_RulesetInfo_ShortName", - table: "RulesetInfo", - column: "ShortName", - unique: true); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropIndex( - name: "IX_RulesetInfo_ShortName", - table: "RulesetInfo"); - - migrationBuilder.DropColumn( - name: "ShortName", - table: "RulesetInfo"); - } - } -} diff --git a/osu.Game/Migrations/20180125143340_Settings.Designer.cs b/osu.Game/Migrations/20180125143340_Settings.Designer.cs deleted file mode 100644 index 4bb599eec1..0000000000 --- a/osu.Game/Migrations/20180125143340_Settings.Designer.cs +++ /dev/null @@ -1,329 +0,0 @@ -// -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage; -using osu.Game.Database; -using System; - -namespace osu.Game.Migrations -{ - [DbContext(typeof(OsuDbContext))] - [Migration("20180125143340_Settings")] - partial class Settings - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.0.0-rtm-26452"); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("ApproachRate"); - - b.Property("CircleSize"); - - b.Property("DrainRate"); - - b.Property("OverallDifficulty"); - - b.Property("SliderMultiplier"); - - b.Property("SliderTickRate"); - - b.HasKey("ID"); - - b.ToTable("BeatmapDifficulty"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("AudioLeadIn"); - - b.Property("BaseDifficultyID"); - - b.Property("BeatDivisor"); - - b.Property("BeatmapSetInfoID"); - - b.Property("Countdown"); - - b.Property("DistanceSpacing"); - - b.Property("GridSize"); - - b.Property("Hash"); - - b.Property("Hidden"); - - b.Property("LetterboxInBreaks"); - - b.Property("MD5Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapID"); - - b.Property("Path"); - - b.Property("RulesetID"); - - b.Property("SpecialStyle"); - - b.Property("StackLeniency"); - - b.Property("StarDifficulty"); - - b.Property("StoredBookmarks"); - - b.Property("TimelineZoom"); - - b.Property("Version"); - - b.Property("WidescreenStoryboard"); - - b.HasKey("ID"); - - b.HasIndex("BaseDifficultyID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MD5Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("BeatmapInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Artist"); - - b.Property("ArtistUnicode"); - - b.Property("AudioFile"); - - b.Property("AuthorString") - .HasColumnName("Author"); - - b.Property("BackgroundFile"); - - b.Property("PreviewTime"); - - b.Property("Source"); - - b.Property("Tags"); - - b.Property("Title"); - - b.Property("TitleUnicode"); - - b.HasKey("ID"); - - b.ToTable("BeatmapMetadata"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("BeatmapSetInfoID"); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.HasKey("ID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("FileInfoID"); - - b.ToTable("BeatmapSetFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapSetID"); - - b.Property("Protected"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapSetID") - .IsUnique(); - - b.ToTable("BeatmapSetInfo"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntKey") - .HasColumnName("Key"); - - b.Property("RulesetID"); - - b.Property("StringValue") - .HasColumnName("Value"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("Settings"); - }); - - modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntAction") - .HasColumnName("Action"); - - b.Property("KeysString") - .HasColumnName("Keys"); - - b.Property("RulesetID"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("IntAction"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("KeyBinding"); - }); - - modelBuilder.Entity("osu.Game.IO.FileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Hash"); - - b.Property("ReferenceCount"); - - b.HasKey("ID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("ReferenceCount"); - - b.ToTable("FileInfo"); - }); - - modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Available"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.Property("ShortName"); - - b.HasKey("ID"); - - b.HasIndex("Available"); - - b.HasIndex("ShortName") - .IsUnique(); - - b.ToTable("RulesetInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") - .WithMany() - .HasForeignKey("BaseDifficultyID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") - .WithMany("Beatmaps") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("Beatmaps") - .HasForeignKey("MetadataID"); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") - .WithMany("Files") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("BeatmapSets") - .HasForeignKey("MetadataID"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/osu.Game/Migrations/20180125143340_Settings.cs b/osu.Game/Migrations/20180125143340_Settings.cs deleted file mode 100644 index 166d3c086d..0000000000 --- a/osu.Game/Migrations/20180125143340_Settings.cs +++ /dev/null @@ -1,55 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace osu.Game.Migrations -{ - public partial class Settings : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropIndex( - name: "IX_KeyBinding_Variant", - table: "KeyBinding"); - - migrationBuilder.CreateTable( - name: "Settings", - columns: table => new - { - ID = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Key = table.Column(type: "TEXT", nullable: false), - RulesetID = table.Column(type: "INTEGER", nullable: true), - Value = table.Column(type: "TEXT", nullable: true), - Variant = table.Column(type: "INTEGER", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Settings", x => x.ID); - }); - - migrationBuilder.CreateIndex( - name: "IX_KeyBinding_RulesetID_Variant", - table: "KeyBinding", - columns: new[] { "RulesetID", "Variant" }); - - migrationBuilder.CreateIndex( - name: "IX_Settings_RulesetID_Variant", - table: "Settings", - columns: new[] { "RulesetID", "Variant" }); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "Settings"); - - migrationBuilder.DropIndex( - name: "IX_KeyBinding_RulesetID_Variant", - table: "KeyBinding"); - - migrationBuilder.CreateIndex( - name: "IX_KeyBinding_Variant", - table: "KeyBinding", - column: "Variant"); - } - } -} diff --git a/osu.Game/Migrations/20180131154205_AddMuteBinding.cs b/osu.Game/Migrations/20180131154205_AddMuteBinding.cs deleted file mode 100644 index 5564a30bbf..0000000000 --- a/osu.Game/Migrations/20180131154205_AddMuteBinding.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Infrastructure; -using osu.Game.Database; -using osu.Game.Input.Bindings; - -namespace osu.Game.Migrations -{ - [DbContext(typeof(OsuDbContext))] - [Migration("20180131154205_AddMuteBinding")] - public partial class AddMuteBinding : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.Sql($"UPDATE KeyBinding SET Action = Action + 1 WHERE RulesetID IS NULL AND Variant IS NULL AND Action >= {(int)GlobalAction.ToggleMute}"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.Sql($"DELETE FROM KeyBinding WHERE RulesetID IS NULL AND Variant IS NULL AND Action = {(int)GlobalAction.ToggleMute}"); - migrationBuilder.Sql($"UPDATE KeyBinding SET Action = Action - 1 WHERE RulesetID IS NULL AND Variant IS NULL AND Action > {(int)GlobalAction.ToggleMute}"); - } - } -} diff --git a/osu.Game/Migrations/20180219060912_AddSkins.Designer.cs b/osu.Game/Migrations/20180219060912_AddSkins.Designer.cs deleted file mode 100644 index cdc4ef2e66..0000000000 --- a/osu.Game/Migrations/20180219060912_AddSkins.Designer.cs +++ /dev/null @@ -1,379 +0,0 @@ -// -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage; -using osu.Game.Database; -using System; - -namespace osu.Game.Migrations -{ - [DbContext(typeof(OsuDbContext))] - [Migration("20180219060912_AddSkins")] - partial class AddSkins - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.0.0-rtm-26452"); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("ApproachRate"); - - b.Property("CircleSize"); - - b.Property("DrainRate"); - - b.Property("OverallDifficulty"); - - b.Property("SliderMultiplier"); - - b.Property("SliderTickRate"); - - b.HasKey("ID"); - - b.ToTable("BeatmapDifficulty"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("AudioLeadIn"); - - b.Property("BaseDifficultyID"); - - b.Property("BeatDivisor"); - - b.Property("BeatmapSetInfoID"); - - b.Property("Countdown"); - - b.Property("DistanceSpacing"); - - b.Property("GridSize"); - - b.Property("Hash"); - - b.Property("Hidden"); - - b.Property("LetterboxInBreaks"); - - b.Property("MD5Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapID"); - - b.Property("Path"); - - b.Property("RulesetID"); - - b.Property("SpecialStyle"); - - b.Property("StackLeniency"); - - b.Property("StarDifficulty"); - - b.Property("StoredBookmarks"); - - b.Property("TimelineZoom"); - - b.Property("Version"); - - b.Property("WidescreenStoryboard"); - - b.HasKey("ID"); - - b.HasIndex("BaseDifficultyID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MD5Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("BeatmapInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Artist"); - - b.Property("ArtistUnicode"); - - b.Property("AudioFile"); - - b.Property("AuthorString") - .HasColumnName("Author"); - - b.Property("BackgroundFile"); - - b.Property("PreviewTime"); - - b.Property("Source"); - - b.Property("Tags"); - - b.Property("Title"); - - b.Property("TitleUnicode"); - - b.HasKey("ID"); - - b.ToTable("BeatmapMetadata"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("BeatmapSetInfoID"); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.HasKey("ID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("FileInfoID"); - - b.ToTable("BeatmapSetFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapSetID"); - - b.Property("Protected"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapSetID") - .IsUnique(); - - b.ToTable("BeatmapSetInfo"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntKey") - .HasColumnName("Key"); - - b.Property("RulesetID"); - - b.Property("StringValue") - .HasColumnName("Value"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("Settings"); - }); - - modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntAction") - .HasColumnName("Action"); - - b.Property("KeysString") - .HasColumnName("Keys"); - - b.Property("RulesetID"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("IntAction"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("KeyBinding"); - }); - - modelBuilder.Entity("osu.Game.IO.FileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Hash"); - - b.Property("ReferenceCount"); - - b.HasKey("ID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("ReferenceCount"); - - b.ToTable("FileInfo"); - }); - - modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Available"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.Property("ShortName"); - - b.HasKey("ID"); - - b.HasIndex("Available"); - - b.HasIndex("ShortName") - .IsUnique(); - - b.ToTable("RulesetInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("SkinInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("SkinInfoID"); - - b.ToTable("SkinFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Creator"); - - b.Property("DeletePending"); - - b.Property("Name"); - - b.HasKey("ID"); - - b.ToTable("SkinInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") - .WithMany() - .HasForeignKey("BaseDifficultyID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") - .WithMany("Beatmaps") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("Beatmaps") - .HasForeignKey("MetadataID"); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") - .WithMany("Files") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("BeatmapSets") - .HasForeignKey("MetadataID"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Files") - .HasForeignKey("SkinInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/osu.Game/Migrations/20180219060912_AddSkins.cs b/osu.Game/Migrations/20180219060912_AddSkins.cs deleted file mode 100644 index a0270ab0fd..0000000000 --- a/osu.Game/Migrations/20180219060912_AddSkins.cs +++ /dev/null @@ -1,71 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace osu.Game.Migrations -{ - public partial class AddSkins : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "SkinInfo", - columns: table => new - { - ID = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Creator = table.Column(type: "TEXT", nullable: true), - DeletePending = table.Column(type: "INTEGER", nullable: false), - Name = table.Column(type: "TEXT", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_SkinInfo", x => x.ID); - }); - - migrationBuilder.CreateTable( - name: "SkinFileInfo", - columns: table => new - { - ID = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - FileInfoID = table.Column(type: "INTEGER", nullable: false), - Filename = table.Column(type: "TEXT", nullable: false), - SkinInfoID = table.Column(type: "INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_SkinFileInfo", x => x.ID); - table.ForeignKey( - name: "FK_SkinFileInfo_FileInfo_FileInfoID", - column: x => x.FileInfoID, - principalTable: "FileInfo", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_SkinFileInfo_SkinInfo_SkinInfoID", - column: x => x.SkinInfoID, - principalTable: "SkinInfo", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "IX_SkinFileInfo_FileInfoID", - table: "SkinFileInfo", - column: "FileInfoID"); - - migrationBuilder.CreateIndex( - name: "IX_SkinFileInfo_SkinInfoID", - table: "SkinFileInfo", - column: "SkinInfoID"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "SkinFileInfo"); - - migrationBuilder.DropTable( - name: "SkinInfo"); - } - } -} diff --git a/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.Designer.cs b/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.Designer.cs deleted file mode 100644 index f28408bfb3..0000000000 --- a/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.Designer.cs +++ /dev/null @@ -1,377 +0,0 @@ -// -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage; -using osu.Game.Database; -using System; - -namespace osu.Game.Migrations -{ - [DbContext(typeof(OsuDbContext))] - [Migration("20180529055154_RemoveUniqueHashConstraints")] - partial class RemoveUniqueHashConstraints - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.0.3-rtm-10026"); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("ApproachRate"); - - b.Property("CircleSize"); - - b.Property("DrainRate"); - - b.Property("OverallDifficulty"); - - b.Property("SliderMultiplier"); - - b.Property("SliderTickRate"); - - b.HasKey("ID"); - - b.ToTable("BeatmapDifficulty"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("AudioLeadIn"); - - b.Property("BaseDifficultyID"); - - b.Property("BeatDivisor"); - - b.Property("BeatmapSetInfoID"); - - b.Property("Countdown"); - - b.Property("DistanceSpacing"); - - b.Property("GridSize"); - - b.Property("Hash"); - - b.Property("Hidden"); - - b.Property("LetterboxInBreaks"); - - b.Property("MD5Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapID"); - - b.Property("Path"); - - b.Property("RulesetID"); - - b.Property("SpecialStyle"); - - b.Property("StackLeniency"); - - b.Property("StarDifficulty"); - - b.Property("StoredBookmarks"); - - b.Property("TimelineZoom"); - - b.Property("Version"); - - b.Property("WidescreenStoryboard"); - - b.HasKey("ID"); - - b.HasIndex("BaseDifficultyID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("Hash"); - - b.HasIndex("MD5Hash"); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("BeatmapInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Artist"); - - b.Property("ArtistUnicode"); - - b.Property("AudioFile"); - - b.Property("AuthorString") - .HasColumnName("Author"); - - b.Property("BackgroundFile"); - - b.Property("PreviewTime"); - - b.Property("Source"); - - b.Property("Tags"); - - b.Property("Title"); - - b.Property("TitleUnicode"); - - b.HasKey("ID"); - - b.ToTable("BeatmapMetadata"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("BeatmapSetInfoID"); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.HasKey("ID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("FileInfoID"); - - b.ToTable("BeatmapSetFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapSetID"); - - b.Property("Protected"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapSetID") - .IsUnique(); - - b.ToTable("BeatmapSetInfo"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntKey") - .HasColumnName("Key"); - - b.Property("RulesetID"); - - b.Property("StringValue") - .HasColumnName("Value"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("Settings"); - }); - - modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntAction") - .HasColumnName("Action"); - - b.Property("KeysString") - .HasColumnName("Keys"); - - b.Property("RulesetID"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("IntAction"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("KeyBinding"); - }); - - modelBuilder.Entity("osu.Game.IO.FileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Hash"); - - b.Property("ReferenceCount"); - - b.HasKey("ID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("ReferenceCount"); - - b.ToTable("FileInfo"); - }); - - modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Available"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.Property("ShortName"); - - b.HasKey("ID"); - - b.HasIndex("Available"); - - b.HasIndex("ShortName") - .IsUnique(); - - b.ToTable("RulesetInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("SkinInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("SkinInfoID"); - - b.ToTable("SkinFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Creator"); - - b.Property("DeletePending"); - - b.Property("Name"); - - b.HasKey("ID"); - - b.ToTable("SkinInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") - .WithMany() - .HasForeignKey("BaseDifficultyID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") - .WithMany("Beatmaps") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("Beatmaps") - .HasForeignKey("MetadataID"); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") - .WithMany("Files") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("BeatmapSets") - .HasForeignKey("MetadataID"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Files") - .HasForeignKey("SkinInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.cs b/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.cs deleted file mode 100644 index 27269cc5fc..0000000000 --- a/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.cs +++ /dev/null @@ -1,51 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace osu.Game.Migrations -{ - public partial class RemoveUniqueHashConstraints : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropIndex( - name: "IX_BeatmapInfo_Hash", - table: "BeatmapInfo"); - - migrationBuilder.DropIndex( - name: "IX_BeatmapInfo_MD5Hash", - table: "BeatmapInfo"); - - migrationBuilder.CreateIndex( - name: "IX_BeatmapInfo_Hash", - table: "BeatmapInfo", - column: "Hash"); - - migrationBuilder.CreateIndex( - name: "IX_BeatmapInfo_MD5Hash", - table: "BeatmapInfo", - column: "MD5Hash"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropIndex( - name: "IX_BeatmapInfo_Hash", - table: "BeatmapInfo"); - - migrationBuilder.DropIndex( - name: "IX_BeatmapInfo_MD5Hash", - table: "BeatmapInfo"); - - migrationBuilder.CreateIndex( - name: "IX_BeatmapInfo_Hash", - table: "BeatmapInfo", - column: "Hash", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_BeatmapInfo_MD5Hash", - table: "BeatmapInfo", - column: "MD5Hash", - unique: true); - } - } -} diff --git a/osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.Designer.cs b/osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.Designer.cs deleted file mode 100644 index aaa11e88b6..0000000000 --- a/osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.Designer.cs +++ /dev/null @@ -1,376 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using osu.Game.Database; - -namespace osu.Game.Migrations -{ - [DbContext(typeof(OsuDbContext))] - [Migration("20180621044111_UpdateTaikoDefaultBindings")] - partial class UpdateTaikoDefaultBindings - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.1.1-rtm-30846"); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("ApproachRate"); - - b.Property("CircleSize"); - - b.Property("DrainRate"); - - b.Property("OverallDifficulty"); - - b.Property("SliderMultiplier"); - - b.Property("SliderTickRate"); - - b.HasKey("ID"); - - b.ToTable("BeatmapDifficulty"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("AudioLeadIn"); - - b.Property("BaseDifficultyID"); - - b.Property("BeatDivisor"); - - b.Property("BeatmapSetInfoID"); - - b.Property("Countdown"); - - b.Property("DistanceSpacing"); - - b.Property("GridSize"); - - b.Property("Hash"); - - b.Property("Hidden"); - - b.Property("LetterboxInBreaks"); - - b.Property("MD5Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapID"); - - b.Property("Path"); - - b.Property("RulesetID"); - - b.Property("SpecialStyle"); - - b.Property("StackLeniency"); - - b.Property("StarDifficulty"); - - b.Property("StoredBookmarks"); - - b.Property("TimelineZoom"); - - b.Property("Version"); - - b.Property("WidescreenStoryboard"); - - b.HasKey("ID"); - - b.HasIndex("BaseDifficultyID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("Hash"); - - b.HasIndex("MD5Hash"); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("BeatmapInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Artist"); - - b.Property("ArtistUnicode"); - - b.Property("AudioFile"); - - b.Property("AuthorString") - .HasColumnName("Author"); - - b.Property("BackgroundFile"); - - b.Property("PreviewTime"); - - b.Property("Source"); - - b.Property("Tags"); - - b.Property("Title"); - - b.Property("TitleUnicode"); - - b.HasKey("ID"); - - b.ToTable("BeatmapMetadata"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("BeatmapSetInfoID"); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.HasKey("ID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("FileInfoID"); - - b.ToTable("BeatmapSetFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapSetID"); - - b.Property("Protected"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapSetID") - .IsUnique(); - - b.ToTable("BeatmapSetInfo"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntKey") - .HasColumnName("Key"); - - b.Property("RulesetID"); - - b.Property("StringValue") - .HasColumnName("Value"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("Settings"); - }); - - modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntAction") - .HasColumnName("Action"); - - b.Property("KeysString") - .HasColumnName("Keys"); - - b.Property("RulesetID"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("IntAction"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("KeyBinding"); - }); - - modelBuilder.Entity("osu.Game.IO.FileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Hash"); - - b.Property("ReferenceCount"); - - b.HasKey("ID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("ReferenceCount"); - - b.ToTable("FileInfo"); - }); - - modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Available"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.Property("ShortName"); - - b.HasKey("ID"); - - b.HasIndex("Available"); - - b.HasIndex("ShortName") - .IsUnique(); - - b.ToTable("RulesetInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("SkinInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("SkinInfoID"); - - b.ToTable("SkinFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Creator"); - - b.Property("DeletePending"); - - b.Property("Name"); - - b.HasKey("ID"); - - b.ToTable("SkinInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") - .WithMany() - .HasForeignKey("BaseDifficultyID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") - .WithMany("Beatmaps") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("Beatmaps") - .HasForeignKey("MetadataID"); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") - .WithMany("Files") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("BeatmapSets") - .HasForeignKey("MetadataID"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Files") - .HasForeignKey("SkinInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.cs b/osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.cs deleted file mode 100644 index 71304ea979..0000000000 --- a/osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace osu.Game.Migrations -{ - public partial class UpdateTaikoDefaultBindings : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.Sql("DELETE FROM KeyBinding WHERE RulesetID = 1"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - // we can't really tell if these should be restored or not, so let's just not do so. - } - } -} diff --git a/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.Designer.cs b/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.Designer.cs deleted file mode 100644 index 7eeacd56d7..0000000000 --- a/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.Designer.cs +++ /dev/null @@ -1,376 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using osu.Game.Database; - -namespace osu.Game.Migrations -{ - [DbContext(typeof(OsuDbContext))] - [Migration("20180628011956_RemoveNegativeSetIDs")] - partial class RemoveNegativeSetIDs - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.1.1-rtm-30846"); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("ApproachRate"); - - b.Property("CircleSize"); - - b.Property("DrainRate"); - - b.Property("OverallDifficulty"); - - b.Property("SliderMultiplier"); - - b.Property("SliderTickRate"); - - b.HasKey("ID"); - - b.ToTable("BeatmapDifficulty"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("AudioLeadIn"); - - b.Property("BaseDifficultyID"); - - b.Property("BeatDivisor"); - - b.Property("BeatmapSetInfoID"); - - b.Property("Countdown"); - - b.Property("DistanceSpacing"); - - b.Property("GridSize"); - - b.Property("Hash"); - - b.Property("Hidden"); - - b.Property("LetterboxInBreaks"); - - b.Property("MD5Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapID"); - - b.Property("Path"); - - b.Property("RulesetID"); - - b.Property("SpecialStyle"); - - b.Property("StackLeniency"); - - b.Property("StarDifficulty"); - - b.Property("StoredBookmarks"); - - b.Property("TimelineZoom"); - - b.Property("Version"); - - b.Property("WidescreenStoryboard"); - - b.HasKey("ID"); - - b.HasIndex("BaseDifficultyID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("Hash"); - - b.HasIndex("MD5Hash"); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("BeatmapInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Artist"); - - b.Property("ArtistUnicode"); - - b.Property("AudioFile"); - - b.Property("AuthorString") - .HasColumnName("Author"); - - b.Property("BackgroundFile"); - - b.Property("PreviewTime"); - - b.Property("Source"); - - b.Property("Tags"); - - b.Property("Title"); - - b.Property("TitleUnicode"); - - b.HasKey("ID"); - - b.ToTable("BeatmapMetadata"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("BeatmapSetInfoID"); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.HasKey("ID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("FileInfoID"); - - b.ToTable("BeatmapSetFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapSetID"); - - b.Property("Protected"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapSetID") - .IsUnique(); - - b.ToTable("BeatmapSetInfo"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntKey") - .HasColumnName("Key"); - - b.Property("RulesetID"); - - b.Property("StringValue") - .HasColumnName("Value"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("Settings"); - }); - - modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntAction") - .HasColumnName("Action"); - - b.Property("KeysString") - .HasColumnName("Keys"); - - b.Property("RulesetID"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("IntAction"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("KeyBinding"); - }); - - modelBuilder.Entity("osu.Game.IO.FileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Hash"); - - b.Property("ReferenceCount"); - - b.HasKey("ID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("ReferenceCount"); - - b.ToTable("FileInfo"); - }); - - modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Available"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.Property("ShortName"); - - b.HasKey("ID"); - - b.HasIndex("Available"); - - b.HasIndex("ShortName") - .IsUnique(); - - b.ToTable("RulesetInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("SkinInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("SkinInfoID"); - - b.ToTable("SkinFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Creator"); - - b.Property("DeletePending"); - - b.Property("Name"); - - b.HasKey("ID"); - - b.ToTable("SkinInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") - .WithMany() - .HasForeignKey("BaseDifficultyID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") - .WithMany("Beatmaps") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("Beatmaps") - .HasForeignKey("MetadataID"); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") - .WithMany("Files") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("BeatmapSets") - .HasForeignKey("MetadataID"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Files") - .HasForeignKey("SkinInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.cs b/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.cs deleted file mode 100644 index 506d65f761..0000000000 --- a/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace osu.Game.Migrations -{ - public partial class RemoveNegativeSetIDs : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - // There was a change that beatmaps were being loaded with "-1" online IDs, which is completely incorrect. - // This ensures there will not be unique key conflicts as a result of these incorrectly imported beatmaps. - migrationBuilder.Sql("UPDATE BeatmapSetInfo SET OnlineBeatmapSetID = null WHERE OnlineBeatmapSetID <= 0"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - } - } -} diff --git a/osu.Game/Migrations/20180913080842_AddRankStatus.Designer.cs b/osu.Game/Migrations/20180913080842_AddRankStatus.Designer.cs deleted file mode 100644 index 5ab43da046..0000000000 --- a/osu.Game/Migrations/20180913080842_AddRankStatus.Designer.cs +++ /dev/null @@ -1,380 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using osu.Game.Database; - -namespace osu.Game.Migrations -{ - [DbContext(typeof(OsuDbContext))] - [Migration("20180913080842_AddRankStatus")] - partial class AddRankStatus - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.1.2-rtm-30932"); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("ApproachRate"); - - b.Property("CircleSize"); - - b.Property("DrainRate"); - - b.Property("OverallDifficulty"); - - b.Property("SliderMultiplier"); - - b.Property("SliderTickRate"); - - b.HasKey("ID"); - - b.ToTable("BeatmapDifficulty"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("AudioLeadIn"); - - b.Property("BaseDifficultyID"); - - b.Property("BeatDivisor"); - - b.Property("BeatmapSetInfoID"); - - b.Property("Countdown"); - - b.Property("DistanceSpacing"); - - b.Property("GridSize"); - - b.Property("Hash"); - - b.Property("Hidden"); - - b.Property("LetterboxInBreaks"); - - b.Property("MD5Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapID"); - - b.Property("Path"); - - b.Property("RulesetID"); - - b.Property("SpecialStyle"); - - b.Property("StackLeniency"); - - b.Property("StarDifficulty"); - - b.Property("Status"); - - b.Property("StoredBookmarks"); - - b.Property("TimelineZoom"); - - b.Property("Version"); - - b.Property("WidescreenStoryboard"); - - b.HasKey("ID"); - - b.HasIndex("BaseDifficultyID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("Hash"); - - b.HasIndex("MD5Hash"); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("BeatmapInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Artist"); - - b.Property("ArtistUnicode"); - - b.Property("AudioFile"); - - b.Property("AuthorString") - .HasColumnName("Author"); - - b.Property("BackgroundFile"); - - b.Property("PreviewTime"); - - b.Property("Source"); - - b.Property("Tags"); - - b.Property("Title"); - - b.Property("TitleUnicode"); - - b.HasKey("ID"); - - b.ToTable("BeatmapMetadata"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("BeatmapSetInfoID"); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.HasKey("ID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("FileInfoID"); - - b.ToTable("BeatmapSetFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapSetID"); - - b.Property("Protected"); - - b.Property("Status"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapSetID") - .IsUnique(); - - b.ToTable("BeatmapSetInfo"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntKey") - .HasColumnName("Key"); - - b.Property("RulesetID"); - - b.Property("StringValue") - .HasColumnName("Value"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("Settings"); - }); - - modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntAction") - .HasColumnName("Action"); - - b.Property("KeysString") - .HasColumnName("Keys"); - - b.Property("RulesetID"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("IntAction"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("KeyBinding"); - }); - - modelBuilder.Entity("osu.Game.IO.FileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Hash"); - - b.Property("ReferenceCount"); - - b.HasKey("ID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("ReferenceCount"); - - b.ToTable("FileInfo"); - }); - - modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Available"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.Property("ShortName"); - - b.HasKey("ID"); - - b.HasIndex("Available"); - - b.HasIndex("ShortName") - .IsUnique(); - - b.ToTable("RulesetInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("SkinInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("SkinInfoID"); - - b.ToTable("SkinFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Creator"); - - b.Property("DeletePending"); - - b.Property("Name"); - - b.HasKey("ID"); - - b.ToTable("SkinInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") - .WithMany() - .HasForeignKey("BaseDifficultyID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") - .WithMany("Beatmaps") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("Beatmaps") - .HasForeignKey("MetadataID"); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") - .WithMany("Files") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("BeatmapSets") - .HasForeignKey("MetadataID"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Files") - .HasForeignKey("SkinInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/osu.Game/Migrations/20180913080842_AddRankStatus.cs b/osu.Game/Migrations/20180913080842_AddRankStatus.cs deleted file mode 100644 index bba4944bb7..0000000000 --- a/osu.Game/Migrations/20180913080842_AddRankStatus.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace osu.Game.Migrations -{ - public partial class AddRankStatus : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "Status", - table: "BeatmapSetInfo", - nullable: false, - defaultValue: -3); // NONE - - migrationBuilder.AddColumn( - name: "Status", - table: "BeatmapInfo", - nullable: false, - defaultValue: -3); // NONE - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "Status", - table: "BeatmapSetInfo"); - - migrationBuilder.DropColumn( - name: "Status", - table: "BeatmapInfo"); - } - } -} diff --git a/osu.Game/Migrations/20181007180454_StandardizePaths.Designer.cs b/osu.Game/Migrations/20181007180454_StandardizePaths.Designer.cs deleted file mode 100644 index b387a45ecf..0000000000 --- a/osu.Game/Migrations/20181007180454_StandardizePaths.Designer.cs +++ /dev/null @@ -1,380 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using osu.Game.Database; - -namespace osu.Game.Migrations -{ - [DbContext(typeof(OsuDbContext))] - [Migration("20181007180454_StandardizePaths")] - partial class StandardizePaths - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.1.3-rtm-32065"); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("ApproachRate"); - - b.Property("CircleSize"); - - b.Property("DrainRate"); - - b.Property("OverallDifficulty"); - - b.Property("SliderMultiplier"); - - b.Property("SliderTickRate"); - - b.HasKey("ID"); - - b.ToTable("BeatmapDifficulty"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("AudioLeadIn"); - - b.Property("BaseDifficultyID"); - - b.Property("BeatDivisor"); - - b.Property("BeatmapSetInfoID"); - - b.Property("Countdown"); - - b.Property("DistanceSpacing"); - - b.Property("GridSize"); - - b.Property("Hash"); - - b.Property("Hidden"); - - b.Property("LetterboxInBreaks"); - - b.Property("MD5Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapID"); - - b.Property("Path"); - - b.Property("RulesetID"); - - b.Property("SpecialStyle"); - - b.Property("StackLeniency"); - - b.Property("StarDifficulty"); - - b.Property("Status"); - - b.Property("StoredBookmarks"); - - b.Property("TimelineZoom"); - - b.Property("Version"); - - b.Property("WidescreenStoryboard"); - - b.HasKey("ID"); - - b.HasIndex("BaseDifficultyID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("Hash"); - - b.HasIndex("MD5Hash"); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("BeatmapInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Artist"); - - b.Property("ArtistUnicode"); - - b.Property("AudioFile"); - - b.Property("AuthorString") - .HasColumnName("Author"); - - b.Property("BackgroundFile"); - - b.Property("PreviewTime"); - - b.Property("Source"); - - b.Property("Tags"); - - b.Property("Title"); - - b.Property("TitleUnicode"); - - b.HasKey("ID"); - - b.ToTable("BeatmapMetadata"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("BeatmapSetInfoID"); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.HasKey("ID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("FileInfoID"); - - b.ToTable("BeatmapSetFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapSetID"); - - b.Property("Protected"); - - b.Property("Status"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapSetID") - .IsUnique(); - - b.ToTable("BeatmapSetInfo"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntKey") - .HasColumnName("Key"); - - b.Property("RulesetID"); - - b.Property("StringValue") - .HasColumnName("Value"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("Settings"); - }); - - modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntAction") - .HasColumnName("Action"); - - b.Property("KeysString") - .HasColumnName("Keys"); - - b.Property("RulesetID"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("IntAction"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("KeyBinding"); - }); - - modelBuilder.Entity("osu.Game.IO.FileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Hash"); - - b.Property("ReferenceCount"); - - b.HasKey("ID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("ReferenceCount"); - - b.ToTable("FileInfo"); - }); - - modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Available"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.Property("ShortName"); - - b.HasKey("ID"); - - b.HasIndex("Available"); - - b.HasIndex("ShortName") - .IsUnique(); - - b.ToTable("RulesetInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("SkinInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("SkinInfoID"); - - b.ToTable("SkinFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Creator"); - - b.Property("DeletePending"); - - b.Property("Name"); - - b.HasKey("ID"); - - b.ToTable("SkinInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") - .WithMany() - .HasForeignKey("BaseDifficultyID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") - .WithMany("Beatmaps") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("Beatmaps") - .HasForeignKey("MetadataID"); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") - .WithMany("Files") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("BeatmapSets") - .HasForeignKey("MetadataID"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Files") - .HasForeignKey("SkinInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/osu.Game/Migrations/20181007180454_StandardizePaths.cs b/osu.Game/Migrations/20181007180454_StandardizePaths.cs deleted file mode 100644 index 274b8030a9..0000000000 --- a/osu.Game/Migrations/20181007180454_StandardizePaths.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; -using System.IO; - -namespace osu.Game.Migrations -{ - public partial class StandardizePaths : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - string windowsStyle = @"\"; - string standardized = "/"; - - // Escaping \ does not seem to be needed. - migrationBuilder.Sql($"UPDATE `BeatmapInfo` SET `Path` = REPLACE(`Path`, '{windowsStyle}', '{standardized}')"); - migrationBuilder.Sql($"UPDATE `BeatmapMetadata` SET `AudioFile` = REPLACE(`AudioFile`, '{windowsStyle}', '{standardized}')"); - migrationBuilder.Sql($"UPDATE `BeatmapMetadata` SET `BackgroundFile` = REPLACE(`BackgroundFile`, '{windowsStyle}', '{standardized}')"); - migrationBuilder.Sql($"UPDATE `BeatmapSetFileInfo` SET `Filename` = REPLACE(`Filename`, '{windowsStyle}', '{standardized}')"); - migrationBuilder.Sql($"UPDATE `SkinFileInfo` SET `Filename` = REPLACE(`Filename`, '{windowsStyle}', '{standardized}')"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - } - } -} diff --git a/osu.Game/Migrations/20181128100659_AddSkinInfoHash.Designer.cs b/osu.Game/Migrations/20181128100659_AddSkinInfoHash.Designer.cs deleted file mode 100644 index 120674671a..0000000000 --- a/osu.Game/Migrations/20181128100659_AddSkinInfoHash.Designer.cs +++ /dev/null @@ -1,387 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using osu.Game.Database; - -namespace osu.Game.Migrations -{ - [DbContext(typeof(OsuDbContext))] - [Migration("20181128100659_AddSkinInfoHash")] - partial class AddSkinInfoHash - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.1.4-rtm-31024"); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("ApproachRate"); - - b.Property("CircleSize"); - - b.Property("DrainRate"); - - b.Property("OverallDifficulty"); - - b.Property("SliderMultiplier"); - - b.Property("SliderTickRate"); - - b.HasKey("ID"); - - b.ToTable("BeatmapDifficulty"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("AudioLeadIn"); - - b.Property("BaseDifficultyID"); - - b.Property("BeatDivisor"); - - b.Property("BeatmapSetInfoID"); - - b.Property("Countdown"); - - b.Property("DistanceSpacing"); - - b.Property("GridSize"); - - b.Property("Hash"); - - b.Property("Hidden"); - - b.Property("LetterboxInBreaks"); - - b.Property("MD5Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapID"); - - b.Property("Path"); - - b.Property("RulesetID"); - - b.Property("SpecialStyle"); - - b.Property("StackLeniency"); - - b.Property("StarDifficulty"); - - b.Property("Status"); - - b.Property("StoredBookmarks"); - - b.Property("TimelineZoom"); - - b.Property("Version"); - - b.Property("WidescreenStoryboard"); - - b.HasKey("ID"); - - b.HasIndex("BaseDifficultyID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("Hash"); - - b.HasIndex("MD5Hash"); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("BeatmapInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Artist"); - - b.Property("ArtistUnicode"); - - b.Property("AudioFile"); - - b.Property("AuthorString") - .HasColumnName("Author"); - - b.Property("BackgroundFile"); - - b.Property("PreviewTime"); - - b.Property("Source"); - - b.Property("Tags"); - - b.Property("Title"); - - b.Property("TitleUnicode"); - - b.HasKey("ID"); - - b.ToTable("BeatmapMetadata"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("BeatmapSetInfoID"); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.HasKey("ID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("FileInfoID"); - - b.ToTable("BeatmapSetFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapSetID"); - - b.Property("Protected"); - - b.Property("Status"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapSetID") - .IsUnique(); - - b.ToTable("BeatmapSetInfo"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntKey") - .HasColumnName("Key"); - - b.Property("RulesetID"); - - b.Property("StringValue") - .HasColumnName("Value"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("Settings"); - }); - - modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntAction") - .HasColumnName("Action"); - - b.Property("KeysString") - .HasColumnName("Keys"); - - b.Property("RulesetID"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("IntAction"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("KeyBinding"); - }); - - modelBuilder.Entity("osu.Game.IO.FileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Hash"); - - b.Property("ReferenceCount"); - - b.HasKey("ID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("ReferenceCount"); - - b.ToTable("FileInfo"); - }); - - modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Available"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.Property("ShortName"); - - b.HasKey("ID"); - - b.HasIndex("Available"); - - b.HasIndex("ShortName") - .IsUnique(); - - b.ToTable("RulesetInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("SkinInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("SkinInfoID"); - - b.ToTable("SkinFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Creator"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("Name"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.ToTable("SkinInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") - .WithMany() - .HasForeignKey("BaseDifficultyID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") - .WithMany("Beatmaps") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("Beatmaps") - .HasForeignKey("MetadataID"); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") - .WithMany("Files") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("BeatmapSets") - .HasForeignKey("MetadataID"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Files") - .HasForeignKey("SkinInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/osu.Game/Migrations/20181128100659_AddSkinInfoHash.cs b/osu.Game/Migrations/20181128100659_AddSkinInfoHash.cs deleted file mode 100644 index 860264a7dd..0000000000 --- a/osu.Game/Migrations/20181128100659_AddSkinInfoHash.cs +++ /dev/null @@ -1,41 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace osu.Game.Migrations -{ - public partial class AddSkinInfoHash : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "Hash", - table: "SkinInfo", - nullable: true); - - migrationBuilder.CreateIndex( - name: "IX_SkinInfo_DeletePending", - table: "SkinInfo", - column: "DeletePending"); - - migrationBuilder.CreateIndex( - name: "IX_SkinInfo_Hash", - table: "SkinInfo", - column: "Hash", - unique: true); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropIndex( - name: "IX_SkinInfo_DeletePending", - table: "SkinInfo"); - - migrationBuilder.DropIndex( - name: "IX_SkinInfo_Hash", - table: "SkinInfo"); - - migrationBuilder.DropColumn( - name: "Hash", - table: "SkinInfo"); - } - } -} diff --git a/osu.Game/Migrations/20181130113755_AddScoreInfoTables.Designer.cs b/osu.Game/Migrations/20181130113755_AddScoreInfoTables.Designer.cs deleted file mode 100644 index eee53182ce..0000000000 --- a/osu.Game/Migrations/20181130113755_AddScoreInfoTables.Designer.cs +++ /dev/null @@ -1,484 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using osu.Game.Database; - -namespace osu.Game.Migrations -{ - [DbContext(typeof(OsuDbContext))] - [Migration("20181130113755_AddScoreInfoTables")] - partial class AddScoreInfoTables - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.1.4-rtm-31024"); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("ApproachRate"); - - b.Property("CircleSize"); - - b.Property("DrainRate"); - - b.Property("OverallDifficulty"); - - b.Property("SliderMultiplier"); - - b.Property("SliderTickRate"); - - b.HasKey("ID"); - - b.ToTable("BeatmapDifficulty"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("AudioLeadIn"); - - b.Property("BaseDifficultyID"); - - b.Property("BeatDivisor"); - - b.Property("BeatmapSetInfoID"); - - b.Property("Countdown"); - - b.Property("DistanceSpacing"); - - b.Property("GridSize"); - - b.Property("Hash"); - - b.Property("Hidden"); - - b.Property("LetterboxInBreaks"); - - b.Property("MD5Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapID"); - - b.Property("Path"); - - b.Property("RulesetID"); - - b.Property("SpecialStyle"); - - b.Property("StackLeniency"); - - b.Property("StarDifficulty"); - - b.Property("Status"); - - b.Property("StoredBookmarks"); - - b.Property("TimelineZoom"); - - b.Property("Version"); - - b.Property("WidescreenStoryboard"); - - b.HasKey("ID"); - - b.HasIndex("BaseDifficultyID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("Hash"); - - b.HasIndex("MD5Hash"); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("BeatmapInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Artist"); - - b.Property("ArtistUnicode"); - - b.Property("AudioFile"); - - b.Property("AuthorString") - .HasColumnName("Author"); - - b.Property("BackgroundFile"); - - b.Property("PreviewTime"); - - b.Property("Source"); - - b.Property("Tags"); - - b.Property("Title"); - - b.Property("TitleUnicode"); - - b.HasKey("ID"); - - b.ToTable("BeatmapMetadata"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("BeatmapSetInfoID"); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.HasKey("ID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("FileInfoID"); - - b.ToTable("BeatmapSetFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapSetID"); - - b.Property("Protected"); - - b.Property("Status"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapSetID") - .IsUnique(); - - b.ToTable("BeatmapSetInfo"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntKey") - .HasColumnName("Key"); - - b.Property("RulesetID"); - - b.Property("StringValue") - .HasColumnName("Value"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("Settings"); - }); - - modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntAction") - .HasColumnName("Action"); - - b.Property("KeysString") - .HasColumnName("Keys"); - - b.Property("RulesetID"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("IntAction"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("KeyBinding"); - }); - - modelBuilder.Entity("osu.Game.IO.FileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Hash"); - - b.Property("ReferenceCount"); - - b.HasKey("ID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("ReferenceCount"); - - b.ToTable("FileInfo"); - }); - - modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Available"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.Property("ShortName"); - - b.HasKey("ID"); - - b.HasIndex("Available"); - - b.HasIndex("ShortName") - .IsUnique(); - - b.ToTable("RulesetInfo"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("ScoreInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("ScoreInfoID"); - - b.ToTable("ScoreFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Accuracy") - .HasColumnType("DECIMAL(1,4)"); - - b.Property("BeatmapInfoID"); - - b.Property("Combo"); - - b.Property("Date"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MaxCombo"); - - b.Property("ModsJson") - .HasColumnName("Mods"); - - b.Property("OnlineScoreID"); - - b.Property("PP"); - - b.Property("Rank"); - - b.Property("RulesetID"); - - b.Property("StatisticsJson") - .HasColumnName("Statistics"); - - b.Property("TotalScore"); - - b.Property("UserString") - .HasColumnName("User"); - - b.HasKey("ID"); - - b.HasIndex("BeatmapInfoID"); - - b.HasIndex("OnlineScoreID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("ScoreInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("SkinInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("SkinInfoID"); - - b.ToTable("SkinFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Creator"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("Name"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.ToTable("SkinInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") - .WithMany() - .HasForeignKey("BaseDifficultyID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") - .WithMany("Beatmaps") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("Beatmaps") - .HasForeignKey("MetadataID"); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") - .WithMany("Files") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("BeatmapSets") - .HasForeignKey("MetadataID"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Scoring.ScoreInfo") - .WithMany("Files") - .HasForeignKey("ScoreInfoID"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") - .WithMany() - .HasForeignKey("BeatmapInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Files") - .HasForeignKey("SkinInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/osu.Game/Migrations/20181130113755_AddScoreInfoTables.cs b/osu.Game/Migrations/20181130113755_AddScoreInfoTables.cs deleted file mode 100644 index 2b6f94c5a4..0000000000 --- a/osu.Game/Migrations/20181130113755_AddScoreInfoTables.cs +++ /dev/null @@ -1,112 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace osu.Game.Migrations -{ - public partial class AddScoreInfoTables : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "ScoreInfo", - columns: table => new - { - ID = table.Column(nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Rank = table.Column(nullable: false), - TotalScore = table.Column(nullable: false), - Accuracy = table.Column(type: "DECIMAL(1,4)", nullable: false), - PP = table.Column(nullable: true), - MaxCombo = table.Column(nullable: false), - Combo = table.Column(nullable: false), - RulesetID = table.Column(nullable: false), - Mods = table.Column(nullable: true), - User = table.Column(nullable: true), - BeatmapInfoID = table.Column(nullable: false), - OnlineScoreID = table.Column(nullable: true), - Date = table.Column(nullable: false), - Statistics = table.Column(nullable: true), - Hash = table.Column(nullable: true), - DeletePending = table.Column(nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_ScoreInfo", x => x.ID); - table.ForeignKey( - name: "FK_ScoreInfo_BeatmapInfo_BeatmapInfoID", - column: x => x.BeatmapInfoID, - principalTable: "BeatmapInfo", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_ScoreInfo_RulesetInfo_RulesetID", - column: x => x.RulesetID, - principalTable: "RulesetInfo", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "ScoreFileInfo", - columns: table => new - { - ID = table.Column(nullable: false) - .Annotation("Sqlite:Autoincrement", true), - FileInfoID = table.Column(nullable: false), - Filename = table.Column(nullable: false), - ScoreInfoID = table.Column(nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_ScoreFileInfo", x => x.ID); - table.ForeignKey( - name: "FK_ScoreFileInfo_FileInfo_FileInfoID", - column: x => x.FileInfoID, - principalTable: "FileInfo", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_ScoreFileInfo_ScoreInfo_ScoreInfoID", - column: x => x.ScoreInfoID, - principalTable: "ScoreInfo", - principalColumn: "ID", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateIndex( - name: "IX_ScoreFileInfo_FileInfoID", - table: "ScoreFileInfo", - column: "FileInfoID"); - - migrationBuilder.CreateIndex( - name: "IX_ScoreFileInfo_ScoreInfoID", - table: "ScoreFileInfo", - column: "ScoreInfoID"); - - migrationBuilder.CreateIndex( - name: "IX_ScoreInfo_BeatmapInfoID", - table: "ScoreInfo", - column: "BeatmapInfoID"); - - migrationBuilder.CreateIndex( - name: "IX_ScoreInfo_OnlineScoreID", - table: "ScoreInfo", - column: "OnlineScoreID", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_ScoreInfo_RulesetID", - table: "ScoreInfo", - column: "RulesetID"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "ScoreFileInfo"); - - migrationBuilder.DropTable( - name: "ScoreInfo"); - } - } -} diff --git a/osu.Game/Migrations/20190225062029_AddUserIDColumn.Designer.cs b/osu.Game/Migrations/20190225062029_AddUserIDColumn.Designer.cs deleted file mode 100644 index 8e1e3a59f3..0000000000 --- a/osu.Game/Migrations/20190225062029_AddUserIDColumn.Designer.cs +++ /dev/null @@ -1,487 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using osu.Game.Database; - -namespace osu.Game.Migrations -{ - [DbContext(typeof(OsuDbContext))] - [Migration("20190225062029_AddUserIDColumn")] - partial class AddUserIDColumn - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.2.1-servicing-10028"); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("ApproachRate"); - - b.Property("CircleSize"); - - b.Property("DrainRate"); - - b.Property("OverallDifficulty"); - - b.Property("SliderMultiplier"); - - b.Property("SliderTickRate"); - - b.HasKey("ID"); - - b.ToTable("BeatmapDifficulty"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("AudioLeadIn"); - - b.Property("BaseDifficultyID"); - - b.Property("BeatDivisor"); - - b.Property("BeatmapSetInfoID"); - - b.Property("Countdown"); - - b.Property("DistanceSpacing"); - - b.Property("GridSize"); - - b.Property("Hash"); - - b.Property("Hidden"); - - b.Property("LetterboxInBreaks"); - - b.Property("MD5Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapID"); - - b.Property("Path"); - - b.Property("RulesetID"); - - b.Property("SpecialStyle"); - - b.Property("StackLeniency"); - - b.Property("StarDifficulty"); - - b.Property("Status"); - - b.Property("StoredBookmarks"); - - b.Property("TimelineZoom"); - - b.Property("Version"); - - b.Property("WidescreenStoryboard"); - - b.HasKey("ID"); - - b.HasIndex("BaseDifficultyID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("Hash"); - - b.HasIndex("MD5Hash"); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("BeatmapInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Artist"); - - b.Property("ArtistUnicode"); - - b.Property("AudioFile"); - - b.Property("AuthorString") - .HasColumnName("Author"); - - b.Property("BackgroundFile"); - - b.Property("PreviewTime"); - - b.Property("Source"); - - b.Property("Tags"); - - b.Property("Title"); - - b.Property("TitleUnicode"); - - b.HasKey("ID"); - - b.ToTable("BeatmapMetadata"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("BeatmapSetInfoID"); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.HasKey("ID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("FileInfoID"); - - b.ToTable("BeatmapSetFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapSetID"); - - b.Property("Protected"); - - b.Property("Status"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapSetID") - .IsUnique(); - - b.ToTable("BeatmapSetInfo"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntKey") - .HasColumnName("Key"); - - b.Property("RulesetID"); - - b.Property("StringValue") - .HasColumnName("Value"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("Settings"); - }); - - modelBuilder.Entity("osu.Game.IO.FileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Hash"); - - b.Property("ReferenceCount"); - - b.HasKey("ID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("ReferenceCount"); - - b.ToTable("FileInfo"); - }); - - modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntAction") - .HasColumnName("Action"); - - b.Property("KeysString") - .HasColumnName("Keys"); - - b.Property("RulesetID"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("IntAction"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("KeyBinding"); - }); - - modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Available"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.Property("ShortName"); - - b.HasKey("ID"); - - b.HasIndex("Available"); - - b.HasIndex("ShortName") - .IsUnique(); - - b.ToTable("RulesetInfo"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("ScoreInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("ScoreInfoID"); - - b.ToTable("ScoreFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Accuracy") - .HasColumnType("DECIMAL(1,4)"); - - b.Property("BeatmapInfoID"); - - b.Property("Combo"); - - b.Property("Date"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MaxCombo"); - - b.Property("ModsJson") - .HasColumnName("Mods"); - - b.Property("OnlineScoreID"); - - b.Property("PP"); - - b.Property("Rank"); - - b.Property("RulesetID"); - - b.Property("StatisticsJson") - .HasColumnName("Statistics"); - - b.Property("TotalScore"); - - b.Property("UserID") - .HasColumnName("UserID"); - - b.Property("UserString") - .HasColumnName("User"); - - b.HasKey("ID"); - - b.HasIndex("BeatmapInfoID"); - - b.HasIndex("OnlineScoreID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("ScoreInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("SkinInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("SkinInfoID"); - - b.ToTable("SkinFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Creator"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("Name"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.ToTable("SkinInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") - .WithMany() - .HasForeignKey("BaseDifficultyID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") - .WithMany("Beatmaps") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("Beatmaps") - .HasForeignKey("MetadataID"); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") - .WithMany("Files") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("BeatmapSets") - .HasForeignKey("MetadataID"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Scoring.ScoreInfo") - .WithMany("Files") - .HasForeignKey("ScoreInfoID"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") - .WithMany("Scores") - .HasForeignKey("BeatmapInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Files") - .HasForeignKey("SkinInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/osu.Game/Migrations/20190225062029_AddUserIDColumn.cs b/osu.Game/Migrations/20190225062029_AddUserIDColumn.cs deleted file mode 100644 index 0720e0eac7..0000000000 --- a/osu.Game/Migrations/20190225062029_AddUserIDColumn.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace osu.Game.Migrations -{ - public partial class AddUserIDColumn : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "UserID", - table: "ScoreInfo", - nullable: true); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "UserID", - table: "ScoreInfo"); - } - } -} diff --git a/osu.Game/Migrations/20190525060824_SkinSettings.Designer.cs b/osu.Game/Migrations/20190525060824_SkinSettings.Designer.cs deleted file mode 100644 index 348c42adb9..0000000000 --- a/osu.Game/Migrations/20190525060824_SkinSettings.Designer.cs +++ /dev/null @@ -1,498 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using osu.Game.Database; - -namespace osu.Game.Migrations -{ - [DbContext(typeof(OsuDbContext))] - [Migration("20190525060824_SkinSettings")] - partial class SkinSettings - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.2.4-servicing-10062"); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("ApproachRate"); - - b.Property("CircleSize"); - - b.Property("DrainRate"); - - b.Property("OverallDifficulty"); - - b.Property("SliderMultiplier"); - - b.Property("SliderTickRate"); - - b.HasKey("ID"); - - b.ToTable("BeatmapDifficulty"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("AudioLeadIn"); - - b.Property("BaseDifficultyID"); - - b.Property("BeatDivisor"); - - b.Property("BeatmapSetInfoID"); - - b.Property("Countdown"); - - b.Property("DistanceSpacing"); - - b.Property("GridSize"); - - b.Property("Hash"); - - b.Property("Hidden"); - - b.Property("LetterboxInBreaks"); - - b.Property("MD5Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapID"); - - b.Property("Path"); - - b.Property("RulesetID"); - - b.Property("SpecialStyle"); - - b.Property("StackLeniency"); - - b.Property("StarDifficulty"); - - b.Property("Status"); - - b.Property("StoredBookmarks"); - - b.Property("TimelineZoom"); - - b.Property("Version"); - - b.Property("WidescreenStoryboard"); - - b.HasKey("ID"); - - b.HasIndex("BaseDifficultyID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("Hash"); - - b.HasIndex("MD5Hash"); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("BeatmapInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Artist"); - - b.Property("ArtistUnicode"); - - b.Property("AudioFile"); - - b.Property("AuthorString") - .HasColumnName("Author"); - - b.Property("BackgroundFile"); - - b.Property("PreviewTime"); - - b.Property("Source"); - - b.Property("Tags"); - - b.Property("Title"); - - b.Property("TitleUnicode"); - - b.HasKey("ID"); - - b.ToTable("BeatmapMetadata"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("BeatmapSetInfoID"); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.HasKey("ID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("FileInfoID"); - - b.ToTable("BeatmapSetFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapSetID"); - - b.Property("Protected"); - - b.Property("Status"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapSetID") - .IsUnique(); - - b.ToTable("BeatmapSetInfo"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Key") - .HasColumnName("Key"); - - b.Property("RulesetID"); - - b.Property("SkinInfoID"); - - b.Property("StringValue") - .HasColumnName("Value"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("SkinInfoID"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("Settings"); - }); - - modelBuilder.Entity("osu.Game.IO.FileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Hash"); - - b.Property("ReferenceCount"); - - b.HasKey("ID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("ReferenceCount"); - - b.ToTable("FileInfo"); - }); - - modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntAction") - .HasColumnName("Action"); - - b.Property("KeysString") - .HasColumnName("Keys"); - - b.Property("RulesetID"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("IntAction"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("KeyBinding"); - }); - - modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Available"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.Property("ShortName"); - - b.HasKey("ID"); - - b.HasIndex("Available"); - - b.HasIndex("ShortName") - .IsUnique(); - - b.ToTable("RulesetInfo"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("ScoreInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("ScoreInfoID"); - - b.ToTable("ScoreFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Accuracy") - .HasColumnType("DECIMAL(1,4)"); - - b.Property("BeatmapInfoID"); - - b.Property("Combo"); - - b.Property("Date"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MaxCombo"); - - b.Property("ModsJson") - .HasColumnName("Mods"); - - b.Property("OnlineScoreID"); - - b.Property("PP"); - - b.Property("Rank"); - - b.Property("RulesetID"); - - b.Property("StatisticsJson") - .HasColumnName("Statistics"); - - b.Property("TotalScore"); - - b.Property("UserID") - .HasColumnName("UserID"); - - b.Property("UserString") - .HasColumnName("User"); - - b.HasKey("ID"); - - b.HasIndex("BeatmapInfoID"); - - b.HasIndex("OnlineScoreID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("ScoreInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("SkinInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("SkinInfoID"); - - b.ToTable("SkinFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Creator"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("Name"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.ToTable("SkinInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") - .WithMany() - .HasForeignKey("BaseDifficultyID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") - .WithMany("Beatmaps") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("Beatmaps") - .HasForeignKey("MetadataID"); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") - .WithMany("Files") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("BeatmapSets") - .HasForeignKey("MetadataID"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Settings") - .HasForeignKey("SkinInfoID"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Scoring.ScoreInfo") - .WithMany("Files") - .HasForeignKey("ScoreInfoID"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") - .WithMany("Scores") - .HasForeignKey("BeatmapInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Files") - .HasForeignKey("SkinInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/osu.Game/Migrations/20190525060824_SkinSettings.cs b/osu.Game/Migrations/20190525060824_SkinSettings.cs deleted file mode 100644 index 99237419b7..0000000000 --- a/osu.Game/Migrations/20190525060824_SkinSettings.cs +++ /dev/null @@ -1,54 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace osu.Game.Migrations -{ - public partial class SkinSettings : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.Sql(@"create table Settings_dg_tmp - ( - ID INTEGER not null - constraint PK_Settings - primary key autoincrement, - Key TEXT not null, - RulesetID INTEGER, - Value TEXT, - Variant INTEGER, - SkinInfoID int - constraint Settings_SkinInfo_ID_fk - references SkinInfo - on delete restrict - ); - - insert into Settings_dg_tmp(ID, Key, RulesetID, Value, Variant) select ID, Key, RulesetID, Value, Variant from Settings; - - drop table Settings; - - alter table Settings_dg_tmp rename to Settings; - - create index IX_Settings_RulesetID_Variant - on Settings (RulesetID, Variant); - - create index Settings_SkinInfoID_index - on Settings (SkinInfoID); - - "); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "FK_Settings_SkinInfo_SkinInfoID", - table: "Settings"); - - migrationBuilder.DropIndex( - name: "IX_Settings_SkinInfoID", - table: "Settings"); - - migrationBuilder.DropColumn( - name: "SkinInfoID", - table: "Settings"); - } - } -} diff --git a/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.Designer.cs b/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.Designer.cs deleted file mode 100644 index 9477369aa0..0000000000 --- a/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.Designer.cs +++ /dev/null @@ -1,489 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using osu.Game.Database; - -namespace osu.Game.Migrations -{ - [DbContext(typeof(OsuDbContext))] - [Migration("20190605091246_AddDateAddedColumnToBeatmapSet")] - partial class AddDateAddedColumnToBeatmapSet - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.2.4-servicing-10062"); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("ApproachRate"); - - b.Property("CircleSize"); - - b.Property("DrainRate"); - - b.Property("OverallDifficulty"); - - b.Property("SliderMultiplier"); - - b.Property("SliderTickRate"); - - b.HasKey("ID"); - - b.ToTable("BeatmapDifficulty"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("AudioLeadIn"); - - b.Property("BaseDifficultyID"); - - b.Property("BeatDivisor"); - - b.Property("BeatmapSetInfoID"); - - b.Property("Countdown"); - - b.Property("DistanceSpacing"); - - b.Property("GridSize"); - - b.Property("Hash"); - - b.Property("Hidden"); - - b.Property("LetterboxInBreaks"); - - b.Property("MD5Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapID"); - - b.Property("Path"); - - b.Property("RulesetID"); - - b.Property("SpecialStyle"); - - b.Property("StackLeniency"); - - b.Property("StarDifficulty"); - - b.Property("Status"); - - b.Property("StoredBookmarks"); - - b.Property("TimelineZoom"); - - b.Property("Version"); - - b.Property("WidescreenStoryboard"); - - b.HasKey("ID"); - - b.HasIndex("BaseDifficultyID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("Hash"); - - b.HasIndex("MD5Hash"); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("BeatmapInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Artist"); - - b.Property("ArtistUnicode"); - - b.Property("AudioFile"); - - b.Property("AuthorString") - .HasColumnName("Author"); - - b.Property("BackgroundFile"); - - b.Property("PreviewTime"); - - b.Property("Source"); - - b.Property("Tags"); - - b.Property("Title"); - - b.Property("TitleUnicode"); - - b.HasKey("ID"); - - b.ToTable("BeatmapMetadata"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("BeatmapSetInfoID"); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.HasKey("ID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("FileInfoID"); - - b.ToTable("BeatmapSetFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("DateAdded"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapSetID"); - - b.Property("Protected"); - - b.Property("Status"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapSetID") - .IsUnique(); - - b.ToTable("BeatmapSetInfo"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Key") - .HasColumnName("Key"); - - b.Property("RulesetID"); - - b.Property("StringValue") - .HasColumnName("Value"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("Settings"); - }); - - modelBuilder.Entity("osu.Game.IO.FileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Hash"); - - b.Property("ReferenceCount"); - - b.HasKey("ID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("ReferenceCount"); - - b.ToTable("FileInfo"); - }); - - modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntAction") - .HasColumnName("Action"); - - b.Property("KeysString") - .HasColumnName("Keys"); - - b.Property("RulesetID"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("IntAction"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("KeyBinding"); - }); - - modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Available"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.Property("ShortName"); - - b.HasKey("ID"); - - b.HasIndex("Available"); - - b.HasIndex("ShortName") - .IsUnique(); - - b.ToTable("RulesetInfo"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("ScoreInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("ScoreInfoID"); - - b.ToTable("ScoreFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Accuracy") - .HasColumnType("DECIMAL(1,4)"); - - b.Property("BeatmapInfoID"); - - b.Property("Combo"); - - b.Property("Date"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MaxCombo"); - - b.Property("ModsJson") - .HasColumnName("Mods"); - - b.Property("OnlineScoreID"); - - b.Property("PP"); - - b.Property("Rank"); - - b.Property("RulesetID"); - - b.Property("StatisticsJson") - .HasColumnName("Statistics"); - - b.Property("TotalScore"); - - b.Property("UserID") - .HasColumnName("UserID"); - - b.Property("UserString") - .HasColumnName("User"); - - b.HasKey("ID"); - - b.HasIndex("BeatmapInfoID"); - - b.HasIndex("OnlineScoreID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("ScoreInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("SkinInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("SkinInfoID"); - - b.ToTable("SkinFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Creator"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("Name"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.ToTable("SkinInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") - .WithMany() - .HasForeignKey("BaseDifficultyID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") - .WithMany("Beatmaps") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("Beatmaps") - .HasForeignKey("MetadataID"); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") - .WithMany("Files") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("BeatmapSets") - .HasForeignKey("MetadataID"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Scoring.ScoreInfo") - .WithMany("Files") - .HasForeignKey("ScoreInfoID"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") - .WithMany("Scores") - .HasForeignKey("BeatmapInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Files") - .HasForeignKey("SkinInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.cs b/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.cs deleted file mode 100644 index 55dc18b6a3..0000000000 --- a/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace osu.Game.Migrations -{ - public partial class AddDateAddedColumnToBeatmapSet : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "DateAdded", - table: "BeatmapSetInfo", - nullable: false, - defaultValue: new DateTimeOffset(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0))); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "DateAdded", - table: "BeatmapSetInfo"); - } - } -} diff --git a/osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.Designer.cs b/osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.Designer.cs deleted file mode 100644 index c5fcc16f84..0000000000 --- a/osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.Designer.cs +++ /dev/null @@ -1,504 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using osu.Game.Database; - -namespace osu.Game.Migrations -{ - [DbContext(typeof(OsuDbContext))] - [Migration("20190708070844_AddBPMAndLengthColumns")] - partial class AddBPMAndLengthColumns - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.2.4-servicing-10062"); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("ApproachRate"); - - b.Property("CircleSize"); - - b.Property("DrainRate"); - - b.Property("OverallDifficulty"); - - b.Property("SliderMultiplier"); - - b.Property("SliderTickRate"); - - b.HasKey("ID"); - - b.ToTable("BeatmapDifficulty"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("AudioLeadIn"); - - b.Property("BPM"); - - b.Property("BaseDifficultyID"); - - b.Property("BeatDivisor"); - - b.Property("BeatmapSetInfoID"); - - b.Property("Countdown"); - - b.Property("DistanceSpacing"); - - b.Property("GridSize"); - - b.Property("Hash"); - - b.Property("Hidden"); - - b.Property("Length"); - - b.Property("LetterboxInBreaks"); - - b.Property("MD5Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapID"); - - b.Property("Path"); - - b.Property("RulesetID"); - - b.Property("SpecialStyle"); - - b.Property("StackLeniency"); - - b.Property("StarDifficulty"); - - b.Property("Status"); - - b.Property("StoredBookmarks"); - - b.Property("TimelineZoom"); - - b.Property("Version"); - - b.Property("WidescreenStoryboard"); - - b.HasKey("ID"); - - b.HasIndex("BaseDifficultyID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("Hash"); - - b.HasIndex("MD5Hash"); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("BeatmapInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Artist"); - - b.Property("ArtistUnicode"); - - b.Property("AudioFile"); - - b.Property("AuthorString") - .HasColumnName("Author"); - - b.Property("BackgroundFile"); - - b.Property("PreviewTime"); - - b.Property("Source"); - - b.Property("Tags"); - - b.Property("Title"); - - b.Property("TitleUnicode"); - - b.HasKey("ID"); - - b.ToTable("BeatmapMetadata"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("BeatmapSetInfoID"); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.HasKey("ID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("FileInfoID"); - - b.ToTable("BeatmapSetFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("DateAdded"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapSetID"); - - b.Property("Protected"); - - b.Property("Status"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapSetID") - .IsUnique(); - - b.ToTable("BeatmapSetInfo"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Key") - .HasColumnName("Key"); - - b.Property("RulesetID"); - - b.Property("SkinInfoID"); - - b.Property("StringValue") - .HasColumnName("Value"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("SkinInfoID"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("Settings"); - }); - - modelBuilder.Entity("osu.Game.IO.FileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Hash"); - - b.Property("ReferenceCount"); - - b.HasKey("ID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("ReferenceCount"); - - b.ToTable("FileInfo"); - }); - - modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntAction") - .HasColumnName("Action"); - - b.Property("KeysString") - .HasColumnName("Keys"); - - b.Property("RulesetID"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("IntAction"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("KeyBinding"); - }); - - modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Available"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.Property("ShortName"); - - b.HasKey("ID"); - - b.HasIndex("Available"); - - b.HasIndex("ShortName") - .IsUnique(); - - b.ToTable("RulesetInfo"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("ScoreInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("ScoreInfoID"); - - b.ToTable("ScoreFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Accuracy") - .HasColumnType("DECIMAL(1,4)"); - - b.Property("BeatmapInfoID"); - - b.Property("Combo"); - - b.Property("Date"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MaxCombo"); - - b.Property("ModsJson") - .HasColumnName("Mods"); - - b.Property("OnlineScoreID"); - - b.Property("PP"); - - b.Property("Rank"); - - b.Property("RulesetID"); - - b.Property("StatisticsJson") - .HasColumnName("Statistics"); - - b.Property("TotalScore"); - - b.Property("UserID") - .HasColumnName("UserID"); - - b.Property("UserString") - .HasColumnName("User"); - - b.HasKey("ID"); - - b.HasIndex("BeatmapInfoID"); - - b.HasIndex("OnlineScoreID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("ScoreInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("SkinInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("SkinInfoID"); - - b.ToTable("SkinFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Creator"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("Name"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.ToTable("SkinInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") - .WithMany() - .HasForeignKey("BaseDifficultyID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") - .WithMany("Beatmaps") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("Beatmaps") - .HasForeignKey("MetadataID"); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") - .WithMany("Files") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("BeatmapSets") - .HasForeignKey("MetadataID"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Settings") - .HasForeignKey("SkinInfoID"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Scoring.ScoreInfo") - .WithMany("Files") - .HasForeignKey("ScoreInfoID"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") - .WithMany("Scores") - .HasForeignKey("BeatmapInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Files") - .HasForeignKey("SkinInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.cs b/osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.cs deleted file mode 100644 index f5963ebf5e..0000000000 --- a/osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace osu.Game.Migrations -{ - public partial class AddBPMAndLengthColumns : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "BPM", - table: "BeatmapInfo", - nullable: false, - defaultValue: 0.0); - - migrationBuilder.AddColumn( - name: "Length", - table: "BeatmapInfo", - nullable: false, - defaultValue: 0.0); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "BPM", - table: "BeatmapInfo"); - - migrationBuilder.DropColumn( - name: "Length", - table: "BeatmapInfo"); - } - } -} diff --git a/osu.Game/Migrations/20190913104727_AddBeatmapVideo.Designer.cs b/osu.Game/Migrations/20190913104727_AddBeatmapVideo.Designer.cs deleted file mode 100644 index 826233a2b0..0000000000 --- a/osu.Game/Migrations/20190913104727_AddBeatmapVideo.Designer.cs +++ /dev/null @@ -1,506 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using osu.Game.Database; - -namespace osu.Game.Migrations -{ - [DbContext(typeof(OsuDbContext))] - [Migration("20190913104727_AddBeatmapVideo")] - partial class AddBeatmapVideo - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.2.6-servicing-10079"); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("ApproachRate"); - - b.Property("CircleSize"); - - b.Property("DrainRate"); - - b.Property("OverallDifficulty"); - - b.Property("SliderMultiplier"); - - b.Property("SliderTickRate"); - - b.HasKey("ID"); - - b.ToTable("BeatmapDifficulty"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("AudioLeadIn"); - - b.Property("BPM"); - - b.Property("BaseDifficultyID"); - - b.Property("BeatDivisor"); - - b.Property("BeatmapSetInfoID"); - - b.Property("Countdown"); - - b.Property("DistanceSpacing"); - - b.Property("GridSize"); - - b.Property("Hash"); - - b.Property("Hidden"); - - b.Property("Length"); - - b.Property("LetterboxInBreaks"); - - b.Property("MD5Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapID"); - - b.Property("Path"); - - b.Property("RulesetID"); - - b.Property("SpecialStyle"); - - b.Property("StackLeniency"); - - b.Property("StarDifficulty"); - - b.Property("Status"); - - b.Property("StoredBookmarks"); - - b.Property("TimelineZoom"); - - b.Property("Version"); - - b.Property("WidescreenStoryboard"); - - b.HasKey("ID"); - - b.HasIndex("BaseDifficultyID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("Hash"); - - b.HasIndex("MD5Hash"); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("BeatmapInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Artist"); - - b.Property("ArtistUnicode"); - - b.Property("AudioFile"); - - b.Property("AuthorString") - .HasColumnName("Author"); - - b.Property("BackgroundFile"); - - b.Property("PreviewTime"); - - b.Property("Source"); - - b.Property("Tags"); - - b.Property("Title"); - - b.Property("TitleUnicode"); - - b.Property("VideoFile"); - - b.HasKey("ID"); - - b.ToTable("BeatmapMetadata"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("BeatmapSetInfoID"); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.HasKey("ID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("FileInfoID"); - - b.ToTable("BeatmapSetFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("DateAdded"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapSetID"); - - b.Property("Protected"); - - b.Property("Status"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapSetID") - .IsUnique(); - - b.ToTable("BeatmapSetInfo"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Key") - .HasColumnName("Key"); - - b.Property("RulesetID"); - - b.Property("SkinInfoID"); - - b.Property("StringValue") - .HasColumnName("Value"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("SkinInfoID"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("Settings"); - }); - - modelBuilder.Entity("osu.Game.IO.FileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Hash"); - - b.Property("ReferenceCount"); - - b.HasKey("ID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("ReferenceCount"); - - b.ToTable("FileInfo"); - }); - - modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntAction") - .HasColumnName("Action"); - - b.Property("KeysString") - .HasColumnName("Keys"); - - b.Property("RulesetID"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("IntAction"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("KeyBinding"); - }); - - modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Available"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.Property("ShortName"); - - b.HasKey("ID"); - - b.HasIndex("Available"); - - b.HasIndex("ShortName") - .IsUnique(); - - b.ToTable("RulesetInfo"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("ScoreInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("ScoreInfoID"); - - b.ToTable("ScoreFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Accuracy") - .HasColumnType("DECIMAL(1,4)"); - - b.Property("BeatmapInfoID"); - - b.Property("Combo"); - - b.Property("Date"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MaxCombo"); - - b.Property("ModsJson") - .HasColumnName("Mods"); - - b.Property("OnlineScoreID"); - - b.Property("PP"); - - b.Property("Rank"); - - b.Property("RulesetID"); - - b.Property("StatisticsJson") - .HasColumnName("Statistics"); - - b.Property("TotalScore"); - - b.Property("UserID") - .HasColumnName("UserID"); - - b.Property("UserString") - .HasColumnName("User"); - - b.HasKey("ID"); - - b.HasIndex("BeatmapInfoID"); - - b.HasIndex("OnlineScoreID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("ScoreInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("SkinInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("SkinInfoID"); - - b.ToTable("SkinFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Creator"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("Name"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.ToTable("SkinInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") - .WithMany() - .HasForeignKey("BaseDifficultyID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") - .WithMany("Beatmaps") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("Beatmaps") - .HasForeignKey("MetadataID"); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") - .WithMany("Files") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("BeatmapSets") - .HasForeignKey("MetadataID"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Settings") - .HasForeignKey("SkinInfoID"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Scoring.ScoreInfo") - .WithMany("Files") - .HasForeignKey("ScoreInfoID"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") - .WithMany("Scores") - .HasForeignKey("BeatmapInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Files") - .HasForeignKey("SkinInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/osu.Game/Migrations/20190913104727_AddBeatmapVideo.cs b/osu.Game/Migrations/20190913104727_AddBeatmapVideo.cs deleted file mode 100644 index 9ed0943acd..0000000000 --- a/osu.Game/Migrations/20190913104727_AddBeatmapVideo.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace osu.Game.Migrations -{ - public partial class AddBeatmapVideo : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "VideoFile", - table: "BeatmapMetadata", - nullable: true); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "VideoFile", - table: "BeatmapMetadata"); - } - } -} diff --git a/osu.Game/Migrations/20200302094919_RefreshVolumeBindings.Designer.cs b/osu.Game/Migrations/20200302094919_RefreshVolumeBindings.Designer.cs deleted file mode 100644 index 22316b0380..0000000000 --- a/osu.Game/Migrations/20200302094919_RefreshVolumeBindings.Designer.cs +++ /dev/null @@ -1,506 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using osu.Game.Database; - -namespace osu.Game.Migrations -{ - [DbContext(typeof(OsuDbContext))] - [Migration("20200302094919_RefreshVolumeBindings")] - partial class RefreshVolumeBindings - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.2.6-servicing-10079"); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("ApproachRate"); - - b.Property("CircleSize"); - - b.Property("DrainRate"); - - b.Property("OverallDifficulty"); - - b.Property("SliderMultiplier"); - - b.Property("SliderTickRate"); - - b.HasKey("ID"); - - b.ToTable("BeatmapDifficulty"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("AudioLeadIn"); - - b.Property("BPM"); - - b.Property("BaseDifficultyID"); - - b.Property("BeatDivisor"); - - b.Property("BeatmapSetInfoID"); - - b.Property("Countdown"); - - b.Property("DistanceSpacing"); - - b.Property("GridSize"); - - b.Property("Hash"); - - b.Property("Hidden"); - - b.Property("Length"); - - b.Property("LetterboxInBreaks"); - - b.Property("MD5Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapID"); - - b.Property("Path"); - - b.Property("RulesetID"); - - b.Property("SpecialStyle"); - - b.Property("StackLeniency"); - - b.Property("StarDifficulty"); - - b.Property("Status"); - - b.Property("StoredBookmarks"); - - b.Property("TimelineZoom"); - - b.Property("Version"); - - b.Property("WidescreenStoryboard"); - - b.HasKey("ID"); - - b.HasIndex("BaseDifficultyID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("Hash"); - - b.HasIndex("MD5Hash"); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("BeatmapInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Artist"); - - b.Property("ArtistUnicode"); - - b.Property("AudioFile"); - - b.Property("AuthorString") - .HasColumnName("Author"); - - b.Property("BackgroundFile"); - - b.Property("PreviewTime"); - - b.Property("Source"); - - b.Property("Tags"); - - b.Property("Title"); - - b.Property("TitleUnicode"); - - b.Property("VideoFile"); - - b.HasKey("ID"); - - b.ToTable("BeatmapMetadata"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("BeatmapSetInfoID"); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.HasKey("ID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("FileInfoID"); - - b.ToTable("BeatmapSetFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("DateAdded"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapSetID"); - - b.Property("Protected"); - - b.Property("Status"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapSetID") - .IsUnique(); - - b.ToTable("BeatmapSetInfo"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Key") - .HasColumnName("Key"); - - b.Property("RulesetID"); - - b.Property("SkinInfoID"); - - b.Property("StringValue") - .HasColumnName("Value"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("SkinInfoID"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("Settings"); - }); - - modelBuilder.Entity("osu.Game.IO.FileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Hash"); - - b.Property("ReferenceCount"); - - b.HasKey("ID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("ReferenceCount"); - - b.ToTable("FileInfo"); - }); - - modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntAction") - .HasColumnName("Action"); - - b.Property("KeysString") - .HasColumnName("Keys"); - - b.Property("RulesetID"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("IntAction"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("KeyBinding"); - }); - - modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Available"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.Property("ShortName"); - - b.HasKey("ID"); - - b.HasIndex("Available"); - - b.HasIndex("ShortName") - .IsUnique(); - - b.ToTable("RulesetInfo"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("ScoreInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("ScoreInfoID"); - - b.ToTable("ScoreFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Accuracy") - .HasColumnType("DECIMAL(1,4)"); - - b.Property("BeatmapInfoID"); - - b.Property("Combo"); - - b.Property("Date"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MaxCombo"); - - b.Property("ModsJson") - .HasColumnName("Mods"); - - b.Property("OnlineScoreID"); - - b.Property("PP"); - - b.Property("Rank"); - - b.Property("RulesetID"); - - b.Property("StatisticsJson") - .HasColumnName("Statistics"); - - b.Property("TotalScore"); - - b.Property("UserID") - .HasColumnName("UserID"); - - b.Property("UserString") - .HasColumnName("User"); - - b.HasKey("ID"); - - b.HasIndex("BeatmapInfoID"); - - b.HasIndex("OnlineScoreID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("ScoreInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("SkinInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("SkinInfoID"); - - b.ToTable("SkinFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Creator"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("Name"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.ToTable("SkinInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") - .WithMany() - .HasForeignKey("BaseDifficultyID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") - .WithMany("Beatmaps") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("Beatmaps") - .HasForeignKey("MetadataID"); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") - .WithMany("Files") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("BeatmapSets") - .HasForeignKey("MetadataID"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Settings") - .HasForeignKey("SkinInfoID"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Scoring.ScoreInfo") - .WithMany("Files") - .HasForeignKey("ScoreInfoID"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") - .WithMany("Scores") - .HasForeignKey("BeatmapInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Files") - .HasForeignKey("SkinInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/osu.Game/Migrations/20200302094919_RefreshVolumeBindings.cs b/osu.Game/Migrations/20200302094919_RefreshVolumeBindings.cs deleted file mode 100644 index ec4475971c..0000000000 --- a/osu.Game/Migrations/20200302094919_RefreshVolumeBindings.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace osu.Game.Migrations -{ - public partial class RefreshVolumeBindings : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.Sql("DELETE FROM KeyBinding WHERE action in (6,7)"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - } - } -} diff --git a/osu.Game/Migrations/20201019224408_AddEpilepsyWarning.Designer.cs b/osu.Game/Migrations/20201019224408_AddEpilepsyWarning.Designer.cs deleted file mode 100644 index 1c05de832e..0000000000 --- a/osu.Game/Migrations/20201019224408_AddEpilepsyWarning.Designer.cs +++ /dev/null @@ -1,508 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using osu.Game.Database; - -namespace osu.Game.Migrations -{ - [DbContext(typeof(OsuDbContext))] - [Migration("20201019224408_AddEpilepsyWarning")] - partial class AddEpilepsyWarning - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.2.6-servicing-10079"); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("ApproachRate"); - - b.Property("CircleSize"); - - b.Property("DrainRate"); - - b.Property("OverallDifficulty"); - - b.Property("SliderMultiplier"); - - b.Property("SliderTickRate"); - - b.HasKey("ID"); - - b.ToTable("BeatmapDifficulty"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("AudioLeadIn"); - - b.Property("BPM"); - - b.Property("BaseDifficultyID"); - - b.Property("BeatDivisor"); - - b.Property("BeatmapSetInfoID"); - - b.Property("Countdown"); - - b.Property("DistanceSpacing"); - - b.Property("EpilepsyWarning"); - - b.Property("GridSize"); - - b.Property("Hash"); - - b.Property("Hidden"); - - b.Property("Length"); - - b.Property("LetterboxInBreaks"); - - b.Property("MD5Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapID"); - - b.Property("Path"); - - b.Property("RulesetID"); - - b.Property("SpecialStyle"); - - b.Property("StackLeniency"); - - b.Property("StarDifficulty"); - - b.Property("Status"); - - b.Property("StoredBookmarks"); - - b.Property("TimelineZoom"); - - b.Property("Version"); - - b.Property("WidescreenStoryboard"); - - b.HasKey("ID"); - - b.HasIndex("BaseDifficultyID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("Hash"); - - b.HasIndex("MD5Hash"); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("BeatmapInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Artist"); - - b.Property("ArtistUnicode"); - - b.Property("AudioFile"); - - b.Property("AuthorString") - .HasColumnName("Author"); - - b.Property("BackgroundFile"); - - b.Property("PreviewTime"); - - b.Property("Source"); - - b.Property("Tags"); - - b.Property("Title"); - - b.Property("TitleUnicode"); - - b.Property("VideoFile"); - - b.HasKey("ID"); - - b.ToTable("BeatmapMetadata"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("BeatmapSetInfoID"); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.HasKey("ID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("FileInfoID"); - - b.ToTable("BeatmapSetFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("DateAdded"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapSetID"); - - b.Property("Protected"); - - b.Property("Status"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapSetID") - .IsUnique(); - - b.ToTable("BeatmapSetInfo"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Key") - .HasColumnName("Key"); - - b.Property("RulesetID"); - - b.Property("SkinInfoID"); - - b.Property("StringValue") - .HasColumnName("Value"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("SkinInfoID"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("Settings"); - }); - - modelBuilder.Entity("osu.Game.IO.FileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Hash"); - - b.Property("ReferenceCount"); - - b.HasKey("ID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("ReferenceCount"); - - b.ToTable("FileInfo"); - }); - - modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntAction") - .HasColumnName("Action"); - - b.Property("KeysString") - .HasColumnName("Keys"); - - b.Property("RulesetID"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("IntAction"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("KeyBinding"); - }); - - modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Available"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.Property("ShortName"); - - b.HasKey("ID"); - - b.HasIndex("Available"); - - b.HasIndex("ShortName") - .IsUnique(); - - b.ToTable("RulesetInfo"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("ScoreInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("ScoreInfoID"); - - b.ToTable("ScoreFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Accuracy") - .HasColumnType("DECIMAL(1,4)"); - - b.Property("BeatmapInfoID"); - - b.Property("Combo"); - - b.Property("Date"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MaxCombo"); - - b.Property("ModsJson") - .HasColumnName("Mods"); - - b.Property("OnlineScoreID"); - - b.Property("PP"); - - b.Property("Rank"); - - b.Property("RulesetID"); - - b.Property("StatisticsJson") - .HasColumnName("Statistics"); - - b.Property("TotalScore"); - - b.Property("UserID") - .HasColumnName("UserID"); - - b.Property("UserString") - .HasColumnName("User"); - - b.HasKey("ID"); - - b.HasIndex("BeatmapInfoID"); - - b.HasIndex("OnlineScoreID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("ScoreInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("SkinInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("SkinInfoID"); - - b.ToTable("SkinFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Creator"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("Name"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.ToTable("SkinInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") - .WithMany() - .HasForeignKey("BaseDifficultyID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") - .WithMany("Beatmaps") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("Beatmaps") - .HasForeignKey("MetadataID"); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") - .WithMany("Files") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("BeatmapSets") - .HasForeignKey("MetadataID"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Settings") - .HasForeignKey("SkinInfoID"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Scoring.ScoreInfo") - .WithMany("Files") - .HasForeignKey("ScoreInfoID"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") - .WithMany("Scores") - .HasForeignKey("BeatmapInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Files") - .HasForeignKey("SkinInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/osu.Game/Migrations/20201019224408_AddEpilepsyWarning.cs b/osu.Game/Migrations/20201019224408_AddEpilepsyWarning.cs deleted file mode 100644 index be6968aa5d..0000000000 --- a/osu.Game/Migrations/20201019224408_AddEpilepsyWarning.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace osu.Game.Migrations -{ - public partial class AddEpilepsyWarning : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "EpilepsyWarning", - table: "BeatmapInfo", - nullable: false, - defaultValue: false); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "EpilepsyWarning", - table: "BeatmapInfo"); - } - } -} diff --git a/osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.Designer.cs b/osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.Designer.cs deleted file mode 100644 index 2c100d39b9..0000000000 --- a/osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.Designer.cs +++ /dev/null @@ -1,506 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using osu.Game.Database; - -namespace osu.Game.Migrations -{ - [DbContext(typeof(OsuDbContext))] - [Migration("20210412045700_RefreshVolumeBindingsAgain")] - partial class RefreshVolumeBindingsAgain - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.2.6-servicing-10079"); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("ApproachRate"); - - b.Property("CircleSize"); - - b.Property("DrainRate"); - - b.Property("OverallDifficulty"); - - b.Property("SliderMultiplier"); - - b.Property("SliderTickRate"); - - b.HasKey("ID"); - - b.ToTable("BeatmapDifficulty"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("AudioLeadIn"); - - b.Property("BPM"); - - b.Property("BaseDifficultyID"); - - b.Property("BeatDivisor"); - - b.Property("BeatmapSetInfoID"); - - b.Property("Countdown"); - - b.Property("DistanceSpacing"); - - b.Property("GridSize"); - - b.Property("Hash"); - - b.Property("Hidden"); - - b.Property("Length"); - - b.Property("LetterboxInBreaks"); - - b.Property("MD5Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapID"); - - b.Property("Path"); - - b.Property("RulesetID"); - - b.Property("SpecialStyle"); - - b.Property("StackLeniency"); - - b.Property("StarDifficulty"); - - b.Property("Status"); - - b.Property("StoredBookmarks"); - - b.Property("TimelineZoom"); - - b.Property("Version"); - - b.Property("WidescreenStoryboard"); - - b.HasKey("ID"); - - b.HasIndex("BaseDifficultyID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("Hash"); - - b.HasIndex("MD5Hash"); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("BeatmapInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Artist"); - - b.Property("ArtistUnicode"); - - b.Property("AudioFile"); - - b.Property("AuthorString") - .HasColumnName("Author"); - - b.Property("BackgroundFile"); - - b.Property("PreviewTime"); - - b.Property("Source"); - - b.Property("Tags"); - - b.Property("Title"); - - b.Property("TitleUnicode"); - - b.Property("VideoFile"); - - b.HasKey("ID"); - - b.ToTable("BeatmapMetadata"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("BeatmapSetInfoID"); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.HasKey("ID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("FileInfoID"); - - b.ToTable("BeatmapSetFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("DateAdded"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapSetID"); - - b.Property("Protected"); - - b.Property("Status"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapSetID") - .IsUnique(); - - b.ToTable("BeatmapSetInfo"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Key") - .HasColumnName("Key"); - - b.Property("RulesetID"); - - b.Property("SkinInfoID"); - - b.Property("StringValue") - .HasColumnName("Value"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("SkinInfoID"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("Settings"); - }); - - modelBuilder.Entity("osu.Game.IO.FileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Hash"); - - b.Property("ReferenceCount"); - - b.HasKey("ID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("ReferenceCount"); - - b.ToTable("FileInfo"); - }); - - modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntAction") - .HasColumnName("Action"); - - b.Property("KeysString") - .HasColumnName("Keys"); - - b.Property("RulesetID"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("IntAction"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("KeyBinding"); - }); - - modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Available"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.Property("ShortName"); - - b.HasKey("ID"); - - b.HasIndex("Available"); - - b.HasIndex("ShortName") - .IsUnique(); - - b.ToTable("RulesetInfo"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("ScoreInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("ScoreInfoID"); - - b.ToTable("ScoreFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Accuracy") - .HasColumnType("DECIMAL(1,4)"); - - b.Property("BeatmapInfoID"); - - b.Property("Combo"); - - b.Property("Date"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MaxCombo"); - - b.Property("ModsJson") - .HasColumnName("Mods"); - - b.Property("OnlineScoreID"); - - b.Property("PP"); - - b.Property("Rank"); - - b.Property("RulesetID"); - - b.Property("StatisticsJson") - .HasColumnName("Statistics"); - - b.Property("TotalScore"); - - b.Property("UserID") - .HasColumnName("UserID"); - - b.Property("UserString") - .HasColumnName("User"); - - b.HasKey("ID"); - - b.HasIndex("BeatmapInfoID"); - - b.HasIndex("OnlineScoreID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("ScoreInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("SkinInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("SkinInfoID"); - - b.ToTable("SkinFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Creator"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("Name"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.ToTable("SkinInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") - .WithMany() - .HasForeignKey("BaseDifficultyID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") - .WithMany("Beatmaps") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("Beatmaps") - .HasForeignKey("MetadataID"); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") - .WithMany("Files") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("BeatmapSets") - .HasForeignKey("MetadataID"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Settings") - .HasForeignKey("SkinInfoID"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Scoring.ScoreInfo") - .WithMany("Files") - .HasForeignKey("ScoreInfoID"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") - .WithMany("Scores") - .HasForeignKey("BeatmapInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Files") - .HasForeignKey("SkinInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.cs b/osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.cs deleted file mode 100644 index 155d6670a8..0000000000 --- a/osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace osu.Game.Migrations -{ - public partial class RefreshVolumeBindingsAgain : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.Sql("DELETE FROM KeyBinding WHERE action in (6,7)"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - } - } -} diff --git a/osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.Designer.cs b/osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.Designer.cs deleted file mode 100644 index b808c648da..0000000000 --- a/osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.Designer.cs +++ /dev/null @@ -1,508 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using osu.Game.Database; - -namespace osu.Game.Migrations -{ - [DbContext(typeof(OsuDbContext))] - [Migration("20210511060743_AddSkinInstantiationInfo")] - partial class AddSkinInstantiationInfo - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.2.6-servicing-10079"); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("ApproachRate"); - - b.Property("CircleSize"); - - b.Property("DrainRate"); - - b.Property("OverallDifficulty"); - - b.Property("SliderMultiplier"); - - b.Property("SliderTickRate"); - - b.HasKey("ID"); - - b.ToTable("BeatmapDifficulty"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("AudioLeadIn"); - - b.Property("BPM"); - - b.Property("BaseDifficultyID"); - - b.Property("BeatDivisor"); - - b.Property("BeatmapSetInfoID"); - - b.Property("Countdown"); - - b.Property("DistanceSpacing"); - - b.Property("EpilepsyWarning"); - - b.Property("GridSize"); - - b.Property("Hash"); - - b.Property("Hidden"); - - b.Property("Length"); - - b.Property("LetterboxInBreaks"); - - b.Property("MD5Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapID"); - - b.Property("Path"); - - b.Property("RulesetID"); - - b.Property("SpecialStyle"); - - b.Property("StackLeniency"); - - b.Property("StarDifficulty"); - - b.Property("Status"); - - b.Property("StoredBookmarks"); - - b.Property("TimelineZoom"); - - b.Property("Version"); - - b.Property("WidescreenStoryboard"); - - b.HasKey("ID"); - - b.HasIndex("BaseDifficultyID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("Hash"); - - b.HasIndex("MD5Hash"); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("BeatmapInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Artist"); - - b.Property("ArtistUnicode"); - - b.Property("AudioFile"); - - b.Property("AuthorString") - .HasColumnName("Author"); - - b.Property("BackgroundFile"); - - b.Property("PreviewTime"); - - b.Property("Source"); - - b.Property("Tags"); - - b.Property("Title"); - - b.Property("TitleUnicode"); - - b.HasKey("ID"); - - b.ToTable("BeatmapMetadata"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("BeatmapSetInfoID"); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.HasKey("ID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("FileInfoID"); - - b.ToTable("BeatmapSetFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("DateAdded"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapSetID"); - - b.Property("Protected"); - - b.Property("Status"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapSetID") - .IsUnique(); - - b.ToTable("BeatmapSetInfo"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Key") - .HasColumnName("Key"); - - b.Property("RulesetID"); - - b.Property("SkinInfoID"); - - b.Property("StringValue") - .HasColumnName("Value"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("SkinInfoID"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("Settings"); - }); - - modelBuilder.Entity("osu.Game.IO.FileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Hash"); - - b.Property("ReferenceCount"); - - b.HasKey("ID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("ReferenceCount"); - - b.ToTable("FileInfo"); - }); - - modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntAction") - .HasColumnName("Action"); - - b.Property("KeysString") - .HasColumnName("Keys"); - - b.Property("RulesetID"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("IntAction"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("KeyBinding"); - }); - - modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Available"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.Property("ShortName"); - - b.HasKey("ID"); - - b.HasIndex("Available"); - - b.HasIndex("ShortName") - .IsUnique(); - - b.ToTable("RulesetInfo"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("ScoreInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("ScoreInfoID"); - - b.ToTable("ScoreFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Accuracy") - .HasColumnType("DECIMAL(1,4)"); - - b.Property("BeatmapInfoID"); - - b.Property("Combo"); - - b.Property("Date"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MaxCombo"); - - b.Property("ModsJson") - .HasColumnName("Mods"); - - b.Property("OnlineScoreID"); - - b.Property("PP"); - - b.Property("Rank"); - - b.Property("RulesetID"); - - b.Property("StatisticsJson") - .HasColumnName("Statistics"); - - b.Property("TotalScore"); - - b.Property("UserID") - .HasColumnName("UserID"); - - b.Property("UserString") - .HasColumnName("User"); - - b.HasKey("ID"); - - b.HasIndex("BeatmapInfoID"); - - b.HasIndex("OnlineScoreID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("ScoreInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("SkinInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("SkinInfoID"); - - b.ToTable("SkinFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Creator"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.ToTable("SkinInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") - .WithMany() - .HasForeignKey("BaseDifficultyID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") - .WithMany("Beatmaps") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("Beatmaps") - .HasForeignKey("MetadataID"); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") - .WithMany("Files") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("BeatmapSets") - .HasForeignKey("MetadataID"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Settings") - .HasForeignKey("SkinInfoID"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Scoring.ScoreInfo") - .WithMany("Files") - .HasForeignKey("ScoreInfoID"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") - .WithMany("Scores") - .HasForeignKey("BeatmapInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Files") - .HasForeignKey("SkinInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.cs b/osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.cs deleted file mode 100644 index 1d5b0769a4..0000000000 --- a/osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace osu.Game.Migrations -{ - public partial class AddSkinInstantiationInfo : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "InstantiationInfo", - table: "SkinInfo", - nullable: true); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "InstantiationInfo", - table: "SkinInfo"); - } - } -} diff --git a/osu.Game/Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.Designer.cs b/osu.Game/Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.Designer.cs deleted file mode 100644 index 89bab3a0fa..0000000000 --- a/osu.Game/Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.Designer.cs +++ /dev/null @@ -1,511 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using osu.Game.Database; - -namespace osu.Game.Migrations -{ - [DbContext(typeof(OsuDbContext))] - [Migration("20210514062639_AddAuthorIdToBeatmapMetadata")] - partial class AddAuthorIdToBeatmapMetadata - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.2.6-servicing-10079"); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("ApproachRate"); - - b.Property("CircleSize"); - - b.Property("DrainRate"); - - b.Property("OverallDifficulty"); - - b.Property("SliderMultiplier"); - - b.Property("SliderTickRate"); - - b.HasKey("ID"); - - b.ToTable("BeatmapDifficulty"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("AudioLeadIn"); - - b.Property("BPM"); - - b.Property("BaseDifficultyID"); - - b.Property("BeatDivisor"); - - b.Property("BeatmapSetInfoID"); - - b.Property("Countdown"); - - b.Property("DistanceSpacing"); - - b.Property("EpilepsyWarning"); - - b.Property("GridSize"); - - b.Property("Hash"); - - b.Property("Hidden"); - - b.Property("Length"); - - b.Property("LetterboxInBreaks"); - - b.Property("MD5Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapID"); - - b.Property("Path"); - - b.Property("RulesetID"); - - b.Property("SpecialStyle"); - - b.Property("StackLeniency"); - - b.Property("StarDifficulty"); - - b.Property("Status"); - - b.Property("StoredBookmarks"); - - b.Property("TimelineZoom"); - - b.Property("Version"); - - b.Property("WidescreenStoryboard"); - - b.HasKey("ID"); - - b.HasIndex("BaseDifficultyID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("Hash"); - - b.HasIndex("MD5Hash"); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("BeatmapInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Artist"); - - b.Property("ArtistUnicode"); - - b.Property("AudioFile"); - - b.Property("AuthorID") - .HasColumnName("AuthorID"); - - b.Property("AuthorString") - .HasColumnName("Author"); - - b.Property("BackgroundFile"); - - b.Property("PreviewTime"); - - b.Property("Source"); - - b.Property("Tags"); - - b.Property("Title"); - - b.Property("TitleUnicode"); - - b.HasKey("ID"); - - b.ToTable("BeatmapMetadata"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("BeatmapSetInfoID"); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.HasKey("ID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("FileInfoID"); - - b.ToTable("BeatmapSetFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("DateAdded"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapSetID"); - - b.Property("Protected"); - - b.Property("Status"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapSetID") - .IsUnique(); - - b.ToTable("BeatmapSetInfo"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Key") - .HasColumnName("Key"); - - b.Property("RulesetID"); - - b.Property("SkinInfoID"); - - b.Property("StringValue") - .HasColumnName("Value"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("SkinInfoID"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("Settings"); - }); - - modelBuilder.Entity("osu.Game.IO.FileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Hash"); - - b.Property("ReferenceCount"); - - b.HasKey("ID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("ReferenceCount"); - - b.ToTable("FileInfo"); - }); - - modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntAction") - .HasColumnName("Action"); - - b.Property("KeysString") - .HasColumnName("Keys"); - - b.Property("RulesetID"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("IntAction"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("KeyBinding"); - }); - - modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Available"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.Property("ShortName"); - - b.HasKey("ID"); - - b.HasIndex("Available"); - - b.HasIndex("ShortName") - .IsUnique(); - - b.ToTable("RulesetInfo"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("ScoreInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("ScoreInfoID"); - - b.ToTable("ScoreFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Accuracy") - .HasColumnType("DECIMAL(1,4)"); - - b.Property("BeatmapInfoID"); - - b.Property("Combo"); - - b.Property("Date"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MaxCombo"); - - b.Property("ModsJson") - .HasColumnName("Mods"); - - b.Property("OnlineScoreID"); - - b.Property("PP"); - - b.Property("Rank"); - - b.Property("RulesetID"); - - b.Property("StatisticsJson") - .HasColumnName("Statistics"); - - b.Property("TotalScore"); - - b.Property("UserID") - .HasColumnName("UserID"); - - b.Property("UserString") - .HasColumnName("User"); - - b.HasKey("ID"); - - b.HasIndex("BeatmapInfoID"); - - b.HasIndex("OnlineScoreID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("ScoreInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("SkinInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("SkinInfoID"); - - b.ToTable("SkinFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Creator"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.ToTable("SkinInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") - .WithMany() - .HasForeignKey("BaseDifficultyID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") - .WithMany("Beatmaps") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("Beatmaps") - .HasForeignKey("MetadataID"); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") - .WithMany("Files") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("BeatmapSets") - .HasForeignKey("MetadataID"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Settings") - .HasForeignKey("SkinInfoID"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Scoring.ScoreInfo") - .WithMany("Files") - .HasForeignKey("ScoreInfoID"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") - .WithMany("Scores") - .HasForeignKey("BeatmapInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Files") - .HasForeignKey("SkinInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/osu.Game/Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.cs b/osu.Game/Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.cs deleted file mode 100644 index 98fe9b5e13..0000000000 --- a/osu.Game/Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace osu.Game.Migrations -{ - public partial class AddAuthorIdToBeatmapMetadata : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "AuthorID", - table: "BeatmapMetadata", - nullable: false, - defaultValue: 0); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "AuthorID", - table: "BeatmapMetadata"); - } - } -} diff --git a/osu.Game/Migrations/20210824185035_AddCountdownSettings.Designer.cs b/osu.Game/Migrations/20210824185035_AddCountdownSettings.Designer.cs deleted file mode 100644 index afeb42130d..0000000000 --- a/osu.Game/Migrations/20210824185035_AddCountdownSettings.Designer.cs +++ /dev/null @@ -1,513 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using osu.Game.Database; - -namespace osu.Game.Migrations -{ - [DbContext(typeof(OsuDbContext))] - [Migration("20210824185035_AddCountdownSettings")] - partial class AddCountdownSettings - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.2.6-servicing-10079"); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("ApproachRate"); - - b.Property("CircleSize"); - - b.Property("DrainRate"); - - b.Property("OverallDifficulty"); - - b.Property("SliderMultiplier"); - - b.Property("SliderTickRate"); - - b.HasKey("ID"); - - b.ToTable("BeatmapDifficulty"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("AudioLeadIn"); - - b.Property("BPM"); - - b.Property("BaseDifficultyID"); - - b.Property("BeatDivisor"); - - b.Property("BeatmapSetInfoID"); - - b.Property("Countdown"); - - b.Property("CountdownOffset"); - - b.Property("DistanceSpacing"); - - b.Property("EpilepsyWarning"); - - b.Property("GridSize"); - - b.Property("Hash"); - - b.Property("Hidden"); - - b.Property("Length"); - - b.Property("LetterboxInBreaks"); - - b.Property("MD5Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapID"); - - b.Property("Path"); - - b.Property("RulesetID"); - - b.Property("SpecialStyle"); - - b.Property("StackLeniency"); - - b.Property("StarDifficulty"); - - b.Property("Status"); - - b.Property("StoredBookmarks"); - - b.Property("TimelineZoom"); - - b.Property("Version"); - - b.Property("WidescreenStoryboard"); - - b.HasKey("ID"); - - b.HasIndex("BaseDifficultyID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("Hash"); - - b.HasIndex("MD5Hash"); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("BeatmapInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Artist"); - - b.Property("ArtistUnicode"); - - b.Property("AudioFile"); - - b.Property("AuthorID") - .HasColumnName("AuthorID"); - - b.Property("AuthorString") - .HasColumnName("Author"); - - b.Property("BackgroundFile"); - - b.Property("PreviewTime"); - - b.Property("Source"); - - b.Property("Tags"); - - b.Property("Title"); - - b.Property("TitleUnicode"); - - b.HasKey("ID"); - - b.ToTable("BeatmapMetadata"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("BeatmapSetInfoID"); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.HasKey("ID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("FileInfoID"); - - b.ToTable("BeatmapSetFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("DateAdded"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapSetID"); - - b.Property("Protected"); - - b.Property("Status"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapSetID") - .IsUnique(); - - b.ToTable("BeatmapSetInfo"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Key") - .HasColumnName("Key"); - - b.Property("RulesetID"); - - b.Property("SkinInfoID"); - - b.Property("StringValue") - .HasColumnName("Value"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("SkinInfoID"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("Settings"); - }); - - modelBuilder.Entity("osu.Game.IO.FileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Hash"); - - b.Property("ReferenceCount"); - - b.HasKey("ID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("ReferenceCount"); - - b.ToTable("FileInfo"); - }); - - modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntAction") - .HasColumnName("Action"); - - b.Property("KeysString") - .HasColumnName("Keys"); - - b.Property("RulesetID"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("IntAction"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("KeyBinding"); - }); - - modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Available"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.Property("ShortName"); - - b.HasKey("ID"); - - b.HasIndex("Available"); - - b.HasIndex("ShortName") - .IsUnique(); - - b.ToTable("RulesetInfo"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("ScoreInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("ScoreInfoID"); - - b.ToTable("ScoreFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Accuracy") - .HasColumnType("DECIMAL(1,4)"); - - b.Property("BeatmapInfoID"); - - b.Property("Combo"); - - b.Property("Date"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MaxCombo"); - - b.Property("ModsJson") - .HasColumnName("Mods"); - - b.Property("OnlineScoreID"); - - b.Property("PP"); - - b.Property("Rank"); - - b.Property("RulesetID"); - - b.Property("StatisticsJson") - .HasColumnName("Statistics"); - - b.Property("TotalScore"); - - b.Property("UserID") - .HasColumnName("UserID"); - - b.Property("UserString") - .HasColumnName("User"); - - b.HasKey("ID"); - - b.HasIndex("BeatmapInfoID"); - - b.HasIndex("OnlineScoreID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("ScoreInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("SkinInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("SkinInfoID"); - - b.ToTable("SkinFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Creator"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.ToTable("SkinInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") - .WithMany() - .HasForeignKey("BaseDifficultyID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") - .WithMany("Beatmaps") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("Beatmaps") - .HasForeignKey("MetadataID"); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") - .WithMany("Files") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("BeatmapSets") - .HasForeignKey("MetadataID"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Settings") - .HasForeignKey("SkinInfoID"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Scoring.ScoreInfo") - .WithMany("Files") - .HasForeignKey("ScoreInfoID"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") - .WithMany("Scores") - .HasForeignKey("BeatmapInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Files") - .HasForeignKey("SkinInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/osu.Game/Migrations/20210824185035_AddCountdownSettings.cs b/osu.Game/Migrations/20210824185035_AddCountdownSettings.cs deleted file mode 100644 index 564f5f4520..0000000000 --- a/osu.Game/Migrations/20210824185035_AddCountdownSettings.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace osu.Game.Migrations -{ - public partial class AddCountdownSettings : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "CountdownOffset", - table: "BeatmapInfo", - nullable: false, - defaultValue: 0); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "CountdownOffset", - table: "BeatmapInfo"); - } - } -} diff --git a/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.Designer.cs b/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.Designer.cs deleted file mode 100644 index 6e53d7fae0..0000000000 --- a/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.Designer.cs +++ /dev/null @@ -1,515 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using osu.Game.Database; - -namespace osu.Game.Migrations -{ - [DbContext(typeof(OsuDbContext))] - [Migration("20210912144011_AddSamplesMatchPlaybackRate")] - partial class AddSamplesMatchPlaybackRate - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.2.6-servicing-10079"); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("ApproachRate"); - - b.Property("CircleSize"); - - b.Property("DrainRate"); - - b.Property("OverallDifficulty"); - - b.Property("SliderMultiplier"); - - b.Property("SliderTickRate"); - - b.HasKey("ID"); - - b.ToTable("BeatmapDifficulty"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("AudioLeadIn"); - - b.Property("BPM"); - - b.Property("BaseDifficultyID"); - - b.Property("BeatDivisor"); - - b.Property("BeatmapSetInfoID"); - - b.Property("Countdown"); - - b.Property("CountdownOffset"); - - b.Property("DistanceSpacing"); - - b.Property("EpilepsyWarning"); - - b.Property("GridSize"); - - b.Property("Hash"); - - b.Property("Hidden"); - - b.Property("Length"); - - b.Property("LetterboxInBreaks"); - - b.Property("MD5Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapID"); - - b.Property("Path"); - - b.Property("RulesetID"); - - b.Property("SamplesMatchPlaybackRate"); - - b.Property("SpecialStyle"); - - b.Property("StackLeniency"); - - b.Property("StarDifficulty"); - - b.Property("Status"); - - b.Property("StoredBookmarks"); - - b.Property("TimelineZoom"); - - b.Property("Version"); - - b.Property("WidescreenStoryboard"); - - b.HasKey("ID"); - - b.HasIndex("BaseDifficultyID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("Hash"); - - b.HasIndex("MD5Hash"); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("BeatmapInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Artist"); - - b.Property("ArtistUnicode"); - - b.Property("AudioFile"); - - b.Property("AuthorID") - .HasColumnName("AuthorID"); - - b.Property("AuthorString") - .HasColumnName("Author"); - - b.Property("BackgroundFile"); - - b.Property("PreviewTime"); - - b.Property("Source"); - - b.Property("Tags"); - - b.Property("Title"); - - b.Property("TitleUnicode"); - - b.HasKey("ID"); - - b.ToTable("BeatmapMetadata"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("BeatmapSetInfoID"); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.HasKey("ID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("FileInfoID"); - - b.ToTable("BeatmapSetFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("DateAdded"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapSetID"); - - b.Property("Protected"); - - b.Property("Status"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapSetID") - .IsUnique(); - - b.ToTable("BeatmapSetInfo"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Key") - .HasColumnName("Key"); - - b.Property("RulesetID"); - - b.Property("SkinInfoID"); - - b.Property("StringValue") - .HasColumnName("Value"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("SkinInfoID"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("Settings"); - }); - - modelBuilder.Entity("osu.Game.IO.FileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Hash"); - - b.Property("ReferenceCount"); - - b.HasKey("ID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("ReferenceCount"); - - b.ToTable("FileInfo"); - }); - - modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntAction") - .HasColumnName("Action"); - - b.Property("KeysString") - .HasColumnName("Keys"); - - b.Property("RulesetID"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("IntAction"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("KeyBinding"); - }); - - modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Available"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.Property("ShortName"); - - b.HasKey("ID"); - - b.HasIndex("Available"); - - b.HasIndex("ShortName") - .IsUnique(); - - b.ToTable("RulesetInfo"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("ScoreInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("ScoreInfoID"); - - b.ToTable("ScoreFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Accuracy") - .HasColumnType("DECIMAL(1,4)"); - - b.Property("BeatmapInfoID"); - - b.Property("Combo"); - - b.Property("Date"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MaxCombo"); - - b.Property("ModsJson") - .HasColumnName("Mods"); - - b.Property("OnlineScoreID"); - - b.Property("PP"); - - b.Property("Rank"); - - b.Property("RulesetID"); - - b.Property("StatisticsJson") - .HasColumnName("Statistics"); - - b.Property("TotalScore"); - - b.Property("UserID") - .HasColumnName("UserID"); - - b.Property("UserString") - .HasColumnName("User"); - - b.HasKey("ID"); - - b.HasIndex("BeatmapInfoID"); - - b.HasIndex("OnlineScoreID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("ScoreInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("SkinInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("SkinInfoID"); - - b.ToTable("SkinFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Creator"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.ToTable("SkinInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") - .WithMany() - .HasForeignKey("BaseDifficultyID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") - .WithMany("Beatmaps") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("Beatmaps") - .HasForeignKey("MetadataID"); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") - .WithMany("Files") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("BeatmapSets") - .HasForeignKey("MetadataID"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Settings") - .HasForeignKey("SkinInfoID"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Scoring.ScoreInfo") - .WithMany("Files") - .HasForeignKey("ScoreInfoID"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") - .WithMany("Scores") - .HasForeignKey("BeatmapInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Files") - .HasForeignKey("SkinInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.cs b/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.cs deleted file mode 100644 index bf3f855d5f..0000000000 --- a/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace osu.Game.Migrations -{ - public partial class AddSamplesMatchPlaybackRate : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "SamplesMatchPlaybackRate", - table: "BeatmapInfo", - nullable: false, - defaultValue: false); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "SamplesMatchPlaybackRate", - table: "BeatmapInfo"); - } - } -} diff --git a/osu.Game/Migrations/20211020081609_ResetSkinHashes.cs b/osu.Game/Migrations/20211020081609_ResetSkinHashes.cs deleted file mode 100644 index 6d53c019ec..0000000000 --- a/osu.Game/Migrations/20211020081609_ResetSkinHashes.cs +++ /dev/null @@ -1,23 +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 Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using osu.Game.Database; - -namespace osu.Game.Migrations -{ - [DbContext(typeof(OsuDbContext))] - [Migration("20211020081609_ResetSkinHashes")] - public partial class ResetSkinHashes : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.Sql($"UPDATE SkinInfo SET Hash = null"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - } - } -} diff --git a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs deleted file mode 100644 index 036c26cb0a..0000000000 --- a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs +++ /dev/null @@ -1,513 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using osu.Game.Database; - -namespace osu.Game.Migrations -{ - [DbContext(typeof(OsuDbContext))] - partial class OsuDbContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.2.6-servicing-10079"); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("ApproachRate"); - - b.Property("CircleSize"); - - b.Property("DrainRate"); - - b.Property("OverallDifficulty"); - - b.Property("SliderMultiplier"); - - b.Property("SliderTickRate"); - - b.HasKey("ID"); - - b.ToTable("BeatmapDifficulty"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("AudioLeadIn"); - - b.Property("BPM"); - - b.Property("BaseDifficultyID"); - - b.Property("BeatDivisor"); - - b.Property("BeatmapSetInfoID"); - - b.Property("Countdown"); - - b.Property("CountdownOffset"); - - b.Property("DistanceSpacing"); - - b.Property("EpilepsyWarning"); - - b.Property("GridSize"); - - b.Property("Hash"); - - b.Property("Hidden"); - - b.Property("Length"); - - b.Property("LetterboxInBreaks"); - - b.Property("MD5Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapID"); - - b.Property("Path"); - - b.Property("RulesetID"); - - b.Property("SamplesMatchPlaybackRate"); - - b.Property("SpecialStyle"); - - b.Property("StackLeniency"); - - b.Property("StarDifficulty"); - - b.Property("Status"); - - b.Property("StoredBookmarks"); - - b.Property("TimelineZoom"); - - b.Property("Version"); - - b.Property("WidescreenStoryboard"); - - b.HasKey("ID"); - - b.HasIndex("BaseDifficultyID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("Hash"); - - b.HasIndex("MD5Hash"); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("BeatmapInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Artist"); - - b.Property("ArtistUnicode"); - - b.Property("AudioFile"); - - b.Property("AuthorID") - .HasColumnName("AuthorID"); - - b.Property("AuthorString") - .HasColumnName("Author"); - - b.Property("BackgroundFile"); - - b.Property("PreviewTime"); - - b.Property("Source"); - - b.Property("Tags"); - - b.Property("Title"); - - b.Property("TitleUnicode"); - - b.HasKey("ID"); - - b.ToTable("BeatmapMetadata"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("BeatmapSetInfoID"); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.HasKey("ID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("FileInfoID"); - - b.ToTable("BeatmapSetFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("DateAdded"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapSetID"); - - b.Property("Protected"); - - b.Property("Status"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapSetID") - .IsUnique(); - - b.ToTable("BeatmapSetInfo"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Key") - .HasColumnName("Key"); - - b.Property("RulesetID"); - - b.Property("SkinInfoID"); - - b.Property("StringValue") - .HasColumnName("Value"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("SkinInfoID"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("Settings"); - }); - - modelBuilder.Entity("osu.Game.IO.FileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Hash"); - - b.Property("ReferenceCount"); - - b.HasKey("ID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("ReferenceCount"); - - b.ToTable("FileInfo"); - }); - - modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntAction") - .HasColumnName("Action"); - - b.Property("KeysString") - .HasColumnName("Keys"); - - b.Property("RulesetID"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("IntAction"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("KeyBinding"); - }); - - modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Available"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.Property("ShortName"); - - b.HasKey("ID"); - - b.HasIndex("Available"); - - b.HasIndex("ShortName") - .IsUnique(); - - b.ToTable("RulesetInfo"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("ScoreInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("ScoreInfoID"); - - b.ToTable("ScoreFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Accuracy") - .HasColumnType("DECIMAL(1,4)"); - - b.Property("BeatmapInfoID"); - - b.Property("Combo"); - - b.Property("Date"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MaxCombo"); - - b.Property("ModsJson") - .HasColumnName("Mods"); - - b.Property("OnlineScoreID"); - - b.Property("PP"); - - b.Property("Rank"); - - b.Property("RulesetID"); - - b.Property("StatisticsJson") - .HasColumnName("Statistics"); - - b.Property("TotalScore"); - - b.Property("UserID") - .HasColumnName("UserID"); - - b.Property("UserString") - .HasColumnName("User"); - - b.HasKey("ID"); - - b.HasIndex("BeatmapInfoID"); - - b.HasIndex("OnlineScoreID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("ScoreInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("SkinInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("SkinInfoID"); - - b.ToTable("SkinFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Creator"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.ToTable("SkinInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") - .WithMany() - .HasForeignKey("BaseDifficultyID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") - .WithMany("Beatmaps") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("Beatmaps") - .HasForeignKey("MetadataID"); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") - .WithMany("Files") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("BeatmapSets") - .HasForeignKey("MetadataID"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Settings") - .HasForeignKey("SkinInfoID"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Scoring.ScoreInfo") - .WithMany("Files") - .HasForeignKey("ScoreInfoID"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") - .WithMany("Scores") - .HasForeignKey("BeatmapInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Files") - .HasForeignKey("SkinInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); -#pragma warning restore 612, 618 - } - } -} From 6ddd2d59d3a2c84eadc13918812b3e14201a45fb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 Jan 2022 20:44:26 +0900 Subject: [PATCH 463/996] Remove EF helper methods --- osu.Game/Database/EntityFrameworkLive.cs | 41 ------------------- .../Database/EntityFrameworkLiveExtensions.cs | 14 ------- 2 files changed, 55 deletions(-) delete mode 100644 osu.Game/Database/EntityFrameworkLive.cs delete mode 100644 osu.Game/Database/EntityFrameworkLiveExtensions.cs diff --git a/osu.Game/Database/EntityFrameworkLive.cs b/osu.Game/Database/EntityFrameworkLive.cs deleted file mode 100644 index 25c0778746..0000000000 --- a/osu.Game/Database/EntityFrameworkLive.cs +++ /dev/null @@ -1,41 +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; - -#nullable enable - -namespace osu.Game.Database -{ - public class EntityFrameworkLive : ILive where T : class - { - public EntityFrameworkLive(T item) - { - IsManaged = true; // no way to really know. - Value = item; - } - - public Guid ID => throw new InvalidOperationException(); - - public void PerformRead(Action perform) - { - perform(Value); - } - - public TReturn PerformRead(Func perform) - { - return perform(Value); - } - - public void PerformWrite(Action perform) - { - perform(Value); - } - - public bool IsManaged { get; } - - public T Value { get; } - - public bool Equals(ILive? other) => ID == other?.ID; - } -} diff --git a/osu.Game/Database/EntityFrameworkLiveExtensions.cs b/osu.Game/Database/EntityFrameworkLiveExtensions.cs deleted file mode 100644 index cd0673675e..0000000000 --- a/osu.Game/Database/EntityFrameworkLiveExtensions.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. - -namespace osu.Game.Database -{ - public static class EntityFrameworkLiveExtensions - { - public static ILive ToEntityFrameworkLive(this T item) - where T : class - { - return new EntityFrameworkLive(item); - } - } -} From 6b0bf38c93cb994f2c8d144e26d4001e5ad01060 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 Jan 2022 20:47:53 +0900 Subject: [PATCH 464/996] Use a single EF context to avoid scores getting cascade deleted along the way --- osu.Game/Database/EFToRealmMigrator.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index 8f5de80e3b..d479f81ead 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -34,16 +34,12 @@ namespace osu.Game.Database public void Run() { using (var ef = efContextFactory.GetForWrite()) + { migrateSettings(ef); - - using (var ef = efContextFactory.GetForWrite()) migrateSkins(ef); - - using (var ef = efContextFactory.GetForWrite()) migrateBeatmaps(ef); - - using (var ef = efContextFactory.GetForWrite()) migrateScores(ef); + } // Delete the database permanently. // Will cause future startups to not attempt migration. From 43e5bd731cbfa0fff4d2a0825e10ceaefb56d599 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Tue, 18 Jan 2022 21:57:12 +0800 Subject: [PATCH 465/996] Compare performance to a perfect play --- .../Statistics/PerformanceStatistic.cs | 97 +++++++++++++++++-- .../Statistics/PerformanceStatisticTooltip.cs | 38 ++++---- 2 files changed, 107 insertions(+), 28 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs index 4fd6964a68..493f6a2dc9 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs @@ -2,19 +2,24 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using System.Threading; +using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Cursor; +using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; namespace osu.Game.Screens.Ranking.Expanded.Statistics { - public class PerformanceStatistic : StatisticDisplay, IHasCustomTooltip + public class PerformanceStatistic : StatisticDisplay, IHasCustomTooltip { private readonly ScoreInfo score; @@ -24,6 +29,15 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics private RollingCounter counter; + [Resolved] + private ScorePerformanceCache performanceCache { get; set; } + + [Resolved] + private BeatmapDifficultyCache difficultyCache { get; set; } + + [Resolved] + private BeatmapManager beatmapManager { get; set; } + public PerformanceStatistic(ScoreInfo score) : base("PP") { @@ -31,18 +45,74 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics } [BackgroundDependencyLoader] - private void load(ScorePerformanceCache performanceCache) + private void load() { - performanceCache.CalculatePerformanceAsync(score, cancellationTokenSource.Token) - .ContinueWith(t => Schedule(() => setPerformanceValue(t.GetResultSafely())), cancellationTokenSource.Token); + Task.WhenAll( + // actual performance + performanceCache.CalculatePerformanceAsync(score, cancellationTokenSource.Token), + // performance for a perfect play + getPerfectPerformance(score) + ).ContinueWith(attr => + { + PerformanceAttributes[] result = attr.GetResultSafely(); + setPerformanceValue(new PerformanceBreakdown { Performance = result[0], PerfectPerformance = result[1] }); + }); } - private void setPerformanceValue(PerformanceAttributes pp) + private async Task getPerfectPerformance(ScoreInfo originalScore) { - if (pp != null) + ScoreInfo perfectScore = await getPerfectScore(originalScore).ConfigureAwait(false); + return await performanceCache.CalculatePerformanceAsync(perfectScore, cancellationTokenSource.Token).ConfigureAwait(false); + } + + private Task getPerfectScore(ScoreInfo originalScore) + { + return Task.Factory.StartNew(() => { - TooltipContent = pp; - performance.Value = (int)Math.Round(pp.Total, MidpointRounding.AwayFromZero); + var beatmap = beatmapManager.GetWorkingBeatmap(originalScore.BeatmapInfo).GetPlayableBeatmap(originalScore.Ruleset, originalScore.Mods); + ScoreInfo perfectPlay = originalScore.DeepClone(); + perfectPlay.Accuracy = 1; + perfectPlay.Passed = true; + + // create statistics assuming all hit objects have perfect hit result + var statistics = beatmap.HitObjects + .Select(ho => ho.CreateJudgement().MaxResult) + .GroupBy(hr => hr, (hr, list) => (hitResult: hr, count: list.Count())) + .ToDictionary(pair => pair.hitResult, pair => pair.count); + perfectPlay.Statistics = statistics; + + // calculate max combo + var difficulty = difficultyCache.GetDifficultyAsync( + beatmap.BeatmapInfo, + originalScore.Ruleset, + originalScore.Mods, + cancellationTokenSource.Token + ).GetResultSafely(); + perfectPlay.MaxCombo = difficulty?.MaxCombo ?? originalScore.MaxCombo; + + // calculate total score + ScoreProcessor scoreProcessor = originalScore.Ruleset.CreateInstance().CreateScoreProcessor(); + perfectPlay.TotalScore = (long)scoreProcessor.GetImmediateScore(ScoringMode.Standardised, perfectPlay.MaxCombo, statistics); + + // compute rank achieved + // default to SS, then adjust the rank with mods + perfectPlay.Rank = ScoreRank.X; + + foreach (IApplicableToScoreProcessor mod in perfectPlay.Mods.OfType()) + { + perfectPlay.Rank = mod.AdjustRank(perfectPlay.Rank, 1); + } + + return perfectPlay; + }, cancellationTokenSource.Token); + } + + private void setPerformanceValue(PerformanceBreakdown breakdown) + { + if (breakdown != null) + { + TooltipContent = breakdown; + performance.Value = (int)Math.Round(breakdown.Performance.Total, MidpointRounding.AwayFromZero); } } @@ -64,8 +134,15 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics Origin = Anchor.TopCentre }; - public ITooltip GetCustomTooltip() => new PerformanceStatisticTooltip(); + public ITooltip GetCustomTooltip() => new PerformanceStatisticTooltip(); - public PerformanceAttributes TooltipContent { get; private set; } + public PerformanceBreakdown TooltipContent { get; private set; } + + public class PerformanceBreakdown + { + public PerformanceAttributes Performance { get; set; } + + public PerformanceAttributes PerfectPerformance { get; set; } + } } } diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs index deef30124c..564c195a3d 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs @@ -1,11 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; -using System.Globalization; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; @@ -19,7 +18,9 @@ using osuTK.Graphics; namespace osu.Game.Screens.Ranking.Expanded.Statistics { - public class PerformanceStatisticTooltip : VisibilityContainer, ITooltip + using static PerformanceStatistic; + + public class PerformanceStatisticTooltip : VisibilityContainer, ITooltip { private readonly Box background; private Colour4 totalColour; @@ -59,27 +60,30 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics protected override void PopIn() { // Don't display the tooltip if "Total" is the only item - if (lastAttributes.GetAttributesForDisplay().Count() > 1) + if (currentPerformance.Performance.GetAttributesForDisplay().Count() > 1) this.FadeIn(200, Easing.OutQuint); } protected override void PopOut() => this.FadeOut(200, Easing.OutQuint); - private PerformanceAttributes lastAttributes; + private PerformanceBreakdown currentPerformance; - public void SetContent(PerformanceAttributes attributes) + public void SetContent(PerformanceBreakdown performance) { - if (attributes == lastAttributes) + if (performance == currentPerformance) return; - lastAttributes = attributes; + currentPerformance = performance; - UpdateDisplay(attributes); + UpdateDisplay(performance); } - private Drawable createAttributeItem(PerformanceDisplayAttribute attribute, double attributeSum) + private Drawable createAttributeItem(PerformanceDisplayAttribute attribute, PerformanceDisplayAttribute perfectAttribute) { bool isTotal = attribute.PropertyName == nameof(PerformanceAttributes.Total); + float fraction = (float)(attribute.Value / perfectAttribute.Value); + if (float.IsNaN(fraction)) + fraction = 0; return new GridContainer { AutoSizeAxes = Axes.Both, @@ -113,7 +117,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics Width = 130, Height = 5, BackgroundColour = Color4.White.Opacity(0.5f), - Length = (float)(attribute.Value / attributeSum), + Length = fraction, Margin = new MarginPadding { Left = 5, Right = 5 } }, new OsuSpriteText @@ -121,7 +125,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, Font = OsuFont.GetFont(weight: FontWeight.SemiBold), - Text = ((int)Math.Round(attribute.Value, MidpointRounding.AwayFromZero)).ToString(CultureInfo.CurrentCulture), + Text = fraction.ToLocalisableString("0%"), Colour = isTotal ? totalColour : textColour } } @@ -129,19 +133,17 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics }; } - protected virtual void UpdateDisplay(PerformanceAttributes attributes) + protected virtual void UpdateDisplay(PerformanceBreakdown performance) { Content.Clear(); - var displayAttributes = attributes.GetAttributesForDisplay(); + var displayAttributes = performance.Performance.GetAttributesForDisplay(); - double attributeSum = displayAttributes - .Where(attr => attr.PropertyName != nameof(PerformanceAttributes.Total)) - .Sum(attr => attr.Value); + var perfectDisplayAttributes = performance.PerfectPerformance.GetAttributesForDisplay(); foreach (PerformanceDisplayAttribute attr in displayAttributes) { - Content.Add(createAttributeItem(attr, attributeSum)); + Content.Add(createAttributeItem(attr, perfectDisplayAttributes.First(a => a.PropertyName == attr.PropertyName))); } } From 7f65f3a47f954ee85e2172c35178450ace95b677 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 Jan 2022 22:57:39 +0900 Subject: [PATCH 466/996] Remove all usage of `BaseDifficulty` (and access `Difficulty` instead) --- osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs | 2 +- osu.Game.Rulesets.Catch.Tests/TestSceneBananaShower.cs | 2 +- osu.Game.Rulesets.Catch.Tests/TestSceneCatchStacker.cs | 2 +- .../TestSceneDrawableHitObjects.cs | 2 +- osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs | 2 +- osu.Game.Rulesets.Catch.Tests/TestSceneJuiceStream.cs | 2 +- osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs | 6 +++--- osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs | 2 +- .../Editor/Checks/CheckTooShortSpinnersTest.cs | 2 +- .../Mods/TestSceneOsuModDifficultyAdjust.cs | 2 +- .../TestSceneHitCircleLongCombo.cs | 2 +- osu.Game.Rulesets.Osu.Tests/TestSceneNoSpinnerStacking.cs | 2 +- osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs | 2 +- .../TestSceneStartTimeOrderedHitPolicy.cs | 2 +- .../DrawableTaikoRulesetTestScene.cs | 2 +- .../Skinning/TestSceneDrawableTaikoMascot.cs | 2 +- osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs | 2 +- osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs | 2 +- osu.Game.Tests/Resources/TestResources.cs | 2 +- .../Gameplay/TestSceneGameplaySampleTriggerSource.cs | 2 +- .../Multiplayer/TestSceneMultiplayerMatchSongSelect.cs | 2 +- .../Visual/Navigation/TestScenePresentBeatmap.cs | 4 ++-- osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs | 4 ++-- .../Visual/SongSelect/TestSceneAdvancedStats.cs | 4 ++-- .../Visual/SongSelect/TestSceneBeatmapInfoWedge.cs | 2 +- .../UserInterface/TestSceneModDifficultyAdjustSettings.cs | 2 +- osu.Game/Beatmaps/Beatmap.cs | 8 ++++---- osu.Game/Beatmaps/BeatmapInfo.cs | 7 ------- osu.Game/Beatmaps/BeatmapManager.cs | 2 +- osu.Game/Beatmaps/BeatmapModelManager.cs | 2 +- osu.Game/Beatmaps/DummyWorkingBeatmap.cs | 2 +- osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs | 2 +- osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs | 8 ++++---- osu.Game/Stores/BeatmapImporter.cs | 2 +- 34 files changed, 44 insertions(+), 51 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs index f552c3c27b..1014158fc1 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Catch.Tests { BeatmapInfo = new BeatmapInfo { - BaseDifficulty = new BeatmapDifficulty { CircleSize = 6, SliderMultiplier = 3 }, + Difficulty = new BeatmapDifficulty { CircleSize = 6, SliderMultiplier = 3 }, Ruleset = ruleset } }; diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneBananaShower.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneBananaShower.cs index e89a95ae37..96ac5c4bf2 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneBananaShower.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneBananaShower.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Catch.Tests { BeatmapInfo = new BeatmapInfo { - BaseDifficulty = new BeatmapDifficulty { CircleSize = 6 }, + Difficulty = new BeatmapDifficulty { CircleSize = 6 }, Ruleset = ruleset } }; diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchStacker.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchStacker.cs index 1ff31697b8..0a4ef49e19 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchStacker.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchStacker.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Catch.Tests { BeatmapInfo = new BeatmapInfo { - BaseDifficulty = new BeatmapDifficulty { CircleSize = 6 }, + Difficulty = new BeatmapDifficulty { CircleSize = 6 }, Ruleset = ruleset } }; diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs index 23f6222eb6..27196bc30c 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Catch.Tests HitObjects = new List { new Fruit() }, BeatmapInfo = new BeatmapInfo { - BaseDifficulty = new BeatmapDifficulty(), + Difficulty = new BeatmapDifficulty(), Metadata = new BeatmapMetadata { Artist = @"Unknown", diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs index 163fee49fb..a5b44dc605 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs @@ -63,7 +63,7 @@ namespace osu.Game.Rulesets.Catch.Tests BeatmapInfo = { Ruleset = ruleset, - BaseDifficulty = new BeatmapDifficulty { CircleSize = 3.6f } + Difficulty = new BeatmapDifficulty { CircleSize = 3.6f } } }; diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneJuiceStream.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneJuiceStream.cs index 269e783899..4601234669 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneJuiceStream.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneJuiceStream.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Catch.Tests { BeatmapInfo = new BeatmapInfo { - BaseDifficulty = new BeatmapDifficulty { CircleSize = 5, SliderMultiplier = 2 }, + Difficulty = new BeatmapDifficulty { CircleSize = 5, SliderMultiplier = 2 }, Ruleset = ruleset }, HitObjects = new List diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs index 4387bc6b3b..f973cb5ed3 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs @@ -264,7 +264,7 @@ namespace osu.Game.Rulesets.Mania.Tests }, BeatmapInfo = { - BaseDifficulty = new BeatmapDifficulty + Difficulty = new BeatmapDifficulty { SliderTickRate = 4, OverallDifficulty = 10, @@ -306,7 +306,7 @@ namespace osu.Game.Rulesets.Mania.Tests }, BeatmapInfo = { - BaseDifficulty = new BeatmapDifficulty { SliderTickRate = tick_rate }, + Difficulty = new BeatmapDifficulty { SliderTickRate = tick_rate }, Ruleset = new ManiaRuleset().RulesetInfo }, }; @@ -383,7 +383,7 @@ namespace osu.Game.Rulesets.Mania.Tests }, BeatmapInfo = { - BaseDifficulty = new BeatmapDifficulty { SliderTickRate = 4 }, + Difficulty = new BeatmapDifficulty { SliderTickRate = 4 }, Ruleset = new ManiaRuleset().RulesetInfo }, }; diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index 9d0aaec2ba..47e0e6d7b1 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps public static int GetColumnCountForNonConvert(BeatmapInfo beatmapInfo) { - double roundedCircleSize = Math.Round(beatmapInfo.BaseDifficulty.CircleSize); + double roundedCircleSize = Math.Round(beatmapInfo.Difficulty.CircleSize); return (int)Math.Max(1, roundedCircleSize); } diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckTooShortSpinnersTest.cs b/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckTooShortSpinnersTest.cs index 787807a8ea..1f3d4297f1 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckTooShortSpinnersTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckTooShortSpinnersTest.cs @@ -107,7 +107,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks var beatmap = new Beatmap { HitObjects = hitObjects, - BeatmapInfo = new BeatmapInfo { BaseDifficulty = new BeatmapDifficulty(beatmapDifficulty) } + BeatmapInfo = new BeatmapInfo { Difficulty = new BeatmapDifficulty(beatmapDifficulty) } }; return new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap)); diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs index db8546c71b..9d06ff5801 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { BeatmapInfo = new BeatmapInfo { - BaseDifficulty = new BeatmapDifficulty + Difficulty = new BeatmapDifficulty { CircleSize = 8 } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLongCombo.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLongCombo.cs index 8cf29ddfbf..4e17c4c363 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLongCombo.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLongCombo.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Tests { BeatmapInfo = new BeatmapInfo { - BaseDifficulty = new BeatmapDifficulty { CircleSize = 6 }, + Difficulty = new BeatmapDifficulty { CircleSize = 6 }, Ruleset = ruleset } }; diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneNoSpinnerStacking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneNoSpinnerStacking.cs index ef05bcd320..5e92bac986 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneNoSpinnerStacking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneNoSpinnerStacking.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Tests { BeatmapInfo = new BeatmapInfo { - BaseDifficulty = new BeatmapDifficulty { OverallDifficulty = 10 }, + Difficulty = new BeatmapDifficulty { OverallDifficulty = 10 }, Ruleset = ruleset } }; diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs index f3392724ec..2368cc7365 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs @@ -358,7 +358,7 @@ namespace osu.Game.Rulesets.Osu.Tests }, BeatmapInfo = { - BaseDifficulty = new BeatmapDifficulty { SliderTickRate = 3 }, + Difficulty = new BeatmapDifficulty { SliderTickRate = 3 }, Ruleset = new OsuRuleset().RulesetInfo }, }); diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneStartTimeOrderedHitPolicy.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneStartTimeOrderedHitPolicy.cs index 2d43e1b95e..53fa3624b8 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneStartTimeOrderedHitPolicy.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneStartTimeOrderedHitPolicy.cs @@ -364,7 +364,7 @@ namespace osu.Game.Rulesets.Osu.Tests HitObjects = hitObjects, BeatmapInfo = { - BaseDifficulty = new BeatmapDifficulty { SliderTickRate = 3 }, + Difficulty = new BeatmapDifficulty { SliderTickRate = 3 }, Ruleset = new OsuRuleset().RulesetInfo }, }); diff --git a/osu.Game.Rulesets.Taiko.Tests/DrawableTaikoRulesetTestScene.cs b/osu.Game.Rulesets.Taiko.Tests/DrawableTaikoRulesetTestScene.cs index 4bdb85ba60..85d5d6419c 100644 --- a/osu.Game.Rulesets.Taiko.Tests/DrawableTaikoRulesetTestScene.cs +++ b/osu.Game.Rulesets.Taiko.Tests/DrawableTaikoRulesetTestScene.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Taiko.Tests HitObjects = new List { new Hit { Type = HitType.Centre } }, BeatmapInfo = new BeatmapInfo { - BaseDifficulty = new BeatmapDifficulty(), + Difficulty = new BeatmapDifficulty(), Metadata = new BeatmapMetadata { Artist = @"Unknown", diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs index b976735223..ffdf0f725d 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs @@ -158,7 +158,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning HitObjects = new List { new Hit { Type = HitType.Centre } }, BeatmapInfo = new BeatmapInfo { - BaseDifficulty = new BeatmapDifficulty(), + Difficulty = new BeatmapDifficulty(), Metadata = new BeatmapMetadata { Artist = "Unknown", diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs index 9ac7838821..3d4b05b52b 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs @@ -128,7 +128,7 @@ namespace osu.Game.Tests.Beatmaps.Formats { MD5Hash = md5Hash, Ruleset = new OsuRuleset().RulesetInfo, - BaseDifficulty = new BeatmapDifficulty() + Difficulty = new BeatmapDifficulty() } }); } diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs index 3d78043c73..fa4c4bee8e 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs @@ -18,7 +18,7 @@ namespace osu.Game.Tests.NonVisual.Filtering { Ruleset = new RulesetInfo { OnlineID = 0 }, StarRating = 4.0d, - BaseDifficulty = new BeatmapDifficulty + Difficulty = new BeatmapDifficulty { ApproachRate = 5.0f, DrainRate = 3.0f, diff --git a/osu.Game.Tests/Resources/TestResources.cs b/osu.Game.Tests/Resources/TestResources.cs index 1d99a5c20d..4c8b943279 100644 --- a/osu.Game.Tests/Resources/TestResources.cs +++ b/osu.Game.Tests/Resources/TestResources.cs @@ -133,7 +133,7 @@ namespace osu.Game.Tests.Resources Hash = Guid.NewGuid().ToString().ComputeMD5Hash(), Ruleset = rulesetInfo, Metadata = metadata, - BaseDifficulty = new BeatmapDifficulty + Difficulty = new BeatmapDifficulty { OverallDifficulty = diff, } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs index fccc1a377c..ac39395567 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs @@ -29,7 +29,7 @@ namespace osu.Game.Tests.Visual.Gameplay { BeatmapInfo = new BeatmapInfo { - BaseDifficulty = new BeatmapDifficulty { CircleSize = 6, SliderMultiplier = 3 }, + Difficulty = new BeatmapDifficulty { CircleSize = 6, SliderMultiplier = 3 }, Ruleset = ruleset } }; diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index 181b0c71f2..34b9485002 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -76,7 +76,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Length = length, BPM = bpm, Metadata = metadata, - BaseDifficulty = new BeatmapDifficulty() + Difficulty = new BeatmapDifficulty() }; beatmaps.Add(beatmap); diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs b/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs index 69b30ec6a0..606d3f929a 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs @@ -114,14 +114,14 @@ namespace osu.Game.Tests.Visual.Navigation { OnlineID = i * 1024, Metadata = metadata, - BaseDifficulty = new BeatmapDifficulty(), + Difficulty = new BeatmapDifficulty(), Ruleset = ruleset ?? new OsuRuleset().RulesetInfo }, new BeatmapInfo { OnlineID = i * 2048, Metadata = metadata, - BaseDifficulty = new BeatmapDifficulty(), + Difficulty = new BeatmapDifficulty(), Ruleset = ruleset ?? new OsuRuleset().RulesetInfo }, } diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs index f9649db135..3e354d5188 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs @@ -44,7 +44,7 @@ namespace osu.Game.Tests.Visual.Navigation AuthorString = "SomeAuthor", Title = "import" }, - BaseDifficulty = new BeatmapDifficulty(), + Difficulty = new BeatmapDifficulty(), Ruleset = new OsuRuleset().RulesetInfo }, new BeatmapInfo @@ -56,7 +56,7 @@ namespace osu.Game.Tests.Visual.Navigation AuthorString = "SomeAuthor", Title = "import" }, - BaseDifficulty = new BeatmapDifficulty(), + Difficulty = new BeatmapDifficulty(), Ruleset = new OsuRuleset().RulesetInfo }, } diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs index bc3e8553f7..7ceae0a69b 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs @@ -38,7 +38,7 @@ namespace osu.Game.Tests.Visual.SongSelect private BeatmapInfo exampleBeatmapInfo => new BeatmapInfo { Ruleset = rulesets.AvailableRulesets.First(), - BaseDifficulty = new BeatmapDifficulty + Difficulty = new BeatmapDifficulty { CircleSize = 7.2f, DrainRate = 3, @@ -68,7 +68,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("set beatmap", () => advancedStats.BeatmapInfo = new BeatmapInfo { Ruleset = rulesets.GetRuleset(3) ?? throw new InvalidOperationException(), - BaseDifficulty = new BeatmapDifficulty + Difficulty = new BeatmapDifficulty { CircleSize = 5, DrainRate = 4.3f, diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs index 9ad5242df4..3d65efc059 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs @@ -216,7 +216,7 @@ namespace osu.Game.Tests.Visual.SongSelect Ruleset = ruleset, StarRating = 6, DifficultyName = $"{ruleset.ShortName}Version", - BaseDifficulty = new BeatmapDifficulty() + Difficulty = new BeatmapDifficulty() }, HitObjects = objects }; diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs index f8652573f4..8e1f426f7b 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs @@ -267,7 +267,7 @@ namespace osu.Game.Tests.Visual.UserInterface { BeatmapInfo = new BeatmapInfo { - BaseDifficulty = new BeatmapDifficulty + Difficulty = new BeatmapDifficulty { OverallDifficulty = value, CircleSize = value, diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index ccecd69d21..69a86f4928 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -28,7 +28,7 @@ namespace osu.Game.Beatmaps difficulty = value; if (beatmapInfo != null) - beatmapInfo.BaseDifficulty = difficulty.Clone(); + beatmapInfo.Difficulty = difficulty.Clone(); } } @@ -41,8 +41,8 @@ namespace osu.Game.Beatmaps { beatmapInfo = value; - if (beatmapInfo?.BaseDifficulty != null) - Difficulty = beatmapInfo.BaseDifficulty.Clone(); + if (beatmapInfo?.Difficulty != null) + Difficulty = beatmapInfo.Difficulty.Clone(); } } @@ -57,7 +57,7 @@ namespace osu.Game.Beatmaps AuthorString = @"Unknown Creator", }, DifficultyName = @"Normal", - BaseDifficulty = Difficulty, + Difficulty = Difficulty, }; } diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index b0e10c2c38..b91f05cd11 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -165,13 +165,6 @@ namespace osu.Game.Beatmaps } } - [Ignored] - public BeatmapDifficulty BaseDifficulty - { - get => Difficulty; - set => Difficulty = value; - } - [Ignored] public string? Path => File?.Filename; diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index f901b0fbc1..564f15361d 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -93,7 +93,7 @@ namespace osu.Game.Beatmaps { new BeatmapInfo { - BaseDifficulty = new BeatmapDifficulty(), + Difficulty = new BeatmapDifficulty(), Ruleset = ruleset, Metadata = metadata, WidescreenStoryboard = true, diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs index 73ada09ef6..3822c6e121 100644 --- a/osu.Game/Beatmaps/BeatmapModelManager.cs +++ b/osu.Game/Beatmaps/BeatmapModelManager.cs @@ -58,7 +58,7 @@ namespace osu.Game.Beatmaps // Warning: The directionality here is important. Changes have to be copied *from* beatmapContent (which comes from editor and is being saved) // *to* the beatmapInfo (which is a database model and needs to receive values without the taiko slider velocity multiplier for correct operation). // CopyTo() will undo such adjustments, while CopyFrom() will not. - beatmapContent.Difficulty.CopyTo(beatmapInfo.BaseDifficulty); + beatmapContent.Difficulty.CopyTo(beatmapInfo.Difficulty); // All changes to metadata are made in the provided beatmapInfo, so this should be copied to the `IBeatmap` before encoding. beatmapContent.BeatmapInfo = beatmapInfo; diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs index 9ea8517764..6e879d09d5 100644 --- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs @@ -32,7 +32,7 @@ namespace osu.Game.Beatmaps Title = "no beatmaps available!" }, BeatmapSet = new BeatmapSetInfo(), - BaseDifficulty = new BeatmapDifficulty + Difficulty = new BeatmapDifficulty { DrainRate = 0, CircleSize = 0, diff --git a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs index 5d7565e56b..45873a321a 100644 --- a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs +++ b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs @@ -74,7 +74,7 @@ namespace osu.Game.Rulesets.Mods return; } - var difficulty = beatmap.Value.BeatmapInfo.BaseDifficulty; + var difficulty = beatmap.Value.BeatmapInfo.Difficulty; // generally should always be implemented, else the slider will have a zero default. if (difficultyBindable.ReadCurrentFromDifficulty == null) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index d2c7c75da8..d54a3bb54e 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -39,10 +39,10 @@ namespace osu.Game.Screens.Select.Carousel } match &= !criteria.StarDifficulty.HasFilter || criteria.StarDifficulty.IsInRange(BeatmapInfo.StarRating); - match &= !criteria.ApproachRate.HasFilter || criteria.ApproachRate.IsInRange(BeatmapInfo.BaseDifficulty.ApproachRate); - match &= !criteria.DrainRate.HasFilter || criteria.DrainRate.IsInRange(BeatmapInfo.BaseDifficulty.DrainRate); - match &= !criteria.CircleSize.HasFilter || criteria.CircleSize.IsInRange(BeatmapInfo.BaseDifficulty.CircleSize); - match &= !criteria.OverallDifficulty.HasFilter || criteria.OverallDifficulty.IsInRange(BeatmapInfo.BaseDifficulty.OverallDifficulty); + match &= !criteria.ApproachRate.HasFilter || criteria.ApproachRate.IsInRange(BeatmapInfo.Difficulty.ApproachRate); + match &= !criteria.DrainRate.HasFilter || criteria.DrainRate.IsInRange(BeatmapInfo.Difficulty.DrainRate); + match &= !criteria.CircleSize.HasFilter || criteria.CircleSize.IsInRange(BeatmapInfo.Difficulty.CircleSize); + match &= !criteria.OverallDifficulty.HasFilter || criteria.OverallDifficulty.IsInRange(BeatmapInfo.Difficulty.OverallDifficulty); match &= !criteria.Length.HasFilter || criteria.Length.IsInRange(BeatmapInfo.Length); match &= !criteria.BPM.HasFilter || criteria.BPM.IsInRange(BeatmapInfo.BPM); diff --git a/osu.Game/Stores/BeatmapImporter.cs b/osu.Game/Stores/BeatmapImporter.cs index 5303bd6fba..d285a6b61c 100644 --- a/osu.Game/Stores/BeatmapImporter.cs +++ b/osu.Game/Stores/BeatmapImporter.cs @@ -218,7 +218,7 @@ namespace osu.Game.Stores } var decodedInfo = decoded.BeatmapInfo; - var decodedDifficulty = decodedInfo.BaseDifficulty; + var decodedDifficulty = decodedInfo.Difficulty; var ruleset = realm.All().FirstOrDefault(r => r.OnlineID == decodedInfo.RulesetID); From a5b53c01c813a94fadadd5f3102990f0aece2137 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Tue, 18 Jan 2022 21:59:51 +0800 Subject: [PATCH 467/996] Add comments and tidy up --- .../Ranking/Expanded/Statistics/PerformanceStatistic.cs | 6 ++++++ .../Expanded/Statistics/PerformanceStatisticTooltip.cs | 8 ++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs index 493f6a2dc9..f00eb9d71f 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs @@ -140,8 +140,14 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics public class PerformanceBreakdown { + /// + /// Actual gameplay performance. + /// public PerformanceAttributes Performance { get; set; } + /// + /// Performance of a perfect play for comparison. + /// public PerformanceAttributes PerfectPerformance { get; set; } } } diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs index 564c195a3d..7209db53f6 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs @@ -23,7 +23,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics public class PerformanceStatisticTooltip : VisibilityContainer, ITooltip { private readonly Box background; - private Colour4 totalColour; + private Colour4 titleColor; private Colour4 textColour; protected override Container Content { get; } @@ -53,7 +53,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics private void load(OsuColour colours) { background.Colour = colours.Gray3; - totalColour = colours.Blue; + titleColor = colours.Blue; textColour = colours.BlueLighter; } @@ -107,7 +107,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics Anchor = Anchor.CentreLeft, Font = OsuFont.GetFont(weight: FontWeight.Regular), Text = attribute.DisplayName, - Colour = isTotal ? totalColour : textColour + Colour = isTotal ? titleColor : textColour }, new Bar { @@ -126,7 +126,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics Anchor = Anchor.CentreLeft, Font = OsuFont.GetFont(weight: FontWeight.SemiBold), Text = fraction.ToLocalisableString("0%"), - Colour = isTotal ? totalColour : textColour + Colour = isTotal ? titleColor : textColour } } } From 31e03e99cd08f1bfd3c648461a6c17e959b0495a Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Tue, 18 Jan 2022 22:11:43 +0800 Subject: [PATCH 468/996] Improve display of "total PP" --- osu.Game/Rulesets/Difficulty/PerformanceAttributes.cs | 2 +- .../Expanded/Statistics/PerformanceStatisticTooltip.cs | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Difficulty/PerformanceAttributes.cs b/osu.Game/Rulesets/Difficulty/PerformanceAttributes.cs index 9bdb5f8f6f..f210c669a6 100644 --- a/osu.Game/Rulesets/Difficulty/PerformanceAttributes.cs +++ b/osu.Game/Rulesets/Difficulty/PerformanceAttributes.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Difficulty /// public virtual IEnumerable GetAttributesForDisplay() { - yield return new PerformanceDisplayAttribute(nameof(Total), "Total", Total); + yield return new PerformanceDisplayAttribute(nameof(Total), "Final PP", Total); } } } diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs index 7209db53f6..528ccafd41 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.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 osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; @@ -84,6 +85,11 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics float fraction = (float)(attribute.Value / perfectAttribute.Value); if (float.IsNaN(fraction)) fraction = 0; + string text = fraction.ToLocalisableString("0%").ToString(); + + if (isTotal) + text = (int)Math.Round(attribute.Value, MidpointRounding.AwayFromZero) + "/" + (int)Math.Round(perfectAttribute.Value, MidpointRounding.AwayFromZero); + return new GridContainer { AutoSizeAxes = Axes.Both, @@ -111,12 +117,12 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics }, new Bar { - Alpha = isTotal ? 0 : 1, Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, Width = 130, Height = 5, BackgroundColour = Color4.White.Opacity(0.5f), + Colour = isTotal ? titleColor : textColour, Length = fraction, Margin = new MarginPadding { Left = 5, Right = 5 } }, @@ -125,7 +131,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, Font = OsuFont.GetFont(weight: FontWeight.SemiBold), - Text = fraction.ToLocalisableString("0%"), + Text = text, Colour = isTotal ? titleColor : textColour } } From 3596c6ed5d59707c08296ff0ddbc08200a557659 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 Jan 2022 23:25:30 +0900 Subject: [PATCH 469/996] Add some missing `IgnoredAttributes` to reduce automapper overhead --- osu.Game/Beatmaps/BeatmapInfo.cs | 1 + osu.Game/Beatmaps/BeatmapMetadata.cs | 1 + osu.Game/Beatmaps/BeatmapSetInfo.cs | 1 + 3 files changed, 3 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index b0e10c2c38..9ad9e9b1c9 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -64,6 +64,7 @@ namespace osu.Game.Beatmaps [Ignored] public RealmNamedFileUsage? File => BeatmapSet?.Files.FirstOrDefault(f => f.File.Hash == Hash); + [Ignored] public BeatmapOnlineStatus Status { get => (BeatmapOnlineStatus)StatusInt; diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs index 6349476550..89f507581b 100644 --- a/osu.Game/Beatmaps/BeatmapMetadata.cs +++ b/osu.Game/Beatmaps/BeatmapMetadata.cs @@ -47,6 +47,7 @@ namespace osu.Game.Beatmaps #region Compatibility properties + [Ignored] public string AuthorString { get => Author.Username; diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index f6ca184ea3..a934d1a2e3 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -32,6 +32,7 @@ namespace osu.Game.Beatmaps public IList Files { get; } = null!; + [Ignored] public BeatmapOnlineStatus Status { get => (BeatmapOnlineStatus)StatusInt; From 67bf95bc91d8cda84cd53b1f521ebc8b1e78c7e1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 Jan 2022 23:30:40 +0900 Subject: [PATCH 470/996] Remove all usage of `AuthorString` --- .../TestSceneDrawableHitObjects.cs | 2 +- .../DrawableTaikoRulesetTestScene.cs | 2 +- .../Skinning/TestSceneDrawableTaikoMascot.cs | 2 +- .../NonVisual/Filtering/FilterMatchingTest.cs | 2 +- osu.Game.Tests/Resources/TestResources.cs | 2 +- .../TestSceneMultiplayerMatchSongSelect.cs | 2 +- .../Visual/Navigation/TestScenePresentBeatmap.cs | 2 +- .../Visual/Navigation/TestScenePresentScore.cs | 4 ++-- .../Visual/SongSelect/TestSceneBeatmapCarousel.cs | 2 +- .../Visual/SongSelect/TestSceneBeatmapInfoWedge.cs | 4 ++-- osu.Game/Beatmaps/Beatmap.cs | 2 +- osu.Game/Beatmaps/BeatmapMetadata.cs | 11 ----------- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 2 +- osu.Game/Database/EFToRealmMigrator.cs | 1 - osu.Game/Screens/Edit/Setup/MetadataSection.cs | 2 +- 15 files changed, 15 insertions(+), 27 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs index 23f6222eb6..f078a4353d 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Catch.Tests { Artist = @"Unknown", Title = @"You're breathtaking", - AuthorString = @"Everyone", + Author = { Username = @"Everyone" }, }, Ruleset = new CatchRuleset().RulesetInfo }, diff --git a/osu.Game.Rulesets.Taiko.Tests/DrawableTaikoRulesetTestScene.cs b/osu.Game.Rulesets.Taiko.Tests/DrawableTaikoRulesetTestScene.cs index 4bdb85ba60..54bc6f1912 100644 --- a/osu.Game.Rulesets.Taiko.Tests/DrawableTaikoRulesetTestScene.cs +++ b/osu.Game.Rulesets.Taiko.Tests/DrawableTaikoRulesetTestScene.cs @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Taiko.Tests { Artist = @"Unknown", Title = @"Sample Beatmap", - AuthorString = @"peppy", + Author = { Username = @"peppy" }, }, Ruleset = new TaikoRuleset().RulesetInfo }, diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs index b976735223..e73fb6bb1f 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs @@ -163,7 +163,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning { Artist = "Unknown", Title = "Sample Beatmap", - AuthorString = "Craftplacer", + Author = { Username = "Craftplacer" }, }, Ruleset = new TaikoRuleset().RulesetInfo }, diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs index 3d78043c73..7ac26699a5 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs @@ -30,7 +30,7 @@ namespace osu.Game.Tests.NonVisual.Filtering ArtistUnicode = "check unicode too", Title = "Title goes here", TitleUnicode = "Title goes here", - AuthorString = "The Author", + Author = { Username = "The Author" }, Source = "unit tests", Tags = "look for tags too", }, diff --git a/osu.Game.Tests/Resources/TestResources.cs b/osu.Game.Tests/Resources/TestResources.cs index 1d99a5c20d..e1da31abcb 100644 --- a/osu.Game.Tests/Resources/TestResources.cs +++ b/osu.Game.Tests/Resources/TestResources.cs @@ -89,7 +89,7 @@ namespace osu.Game.Tests.Resources // Create random metadata, then we can check if sorting works based on these Artist = "Some Artist " + RNG.Next(0, 9), Title = $"Some Song (set id {setId}) {Guid.NewGuid()}", - AuthorString = "Some Guy " + RNG.Next(0, 9), + Author = { Username = "Some Guy " + RNG.Next(0, 9) }, }; var beatmapSet = new BeatmapSetInfo diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index 181b0c71f2..ae7705b97d 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -52,7 +52,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Artist = "Some Artist", Title = "Some Beatmap", - AuthorString = "Some Author" + Author = { Username = "Some Author" }, }; var beatmapSetInfo = new BeatmapSetInfo diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs b/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs index 69b30ec6a0..1b82094603 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs @@ -100,7 +100,7 @@ namespace osu.Game.Tests.Visual.Navigation var metadata = new BeatmapMetadata { Artist = "SomeArtist", - AuthorString = "SomeAuthor", + Author = { Username = "SomeAuthor" }, Title = $"import {i}" }; diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs index f9649db135..b8e49d155e 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs @@ -41,7 +41,7 @@ namespace osu.Game.Tests.Visual.Navigation Metadata = new BeatmapMetadata { Artist = "SomeArtist", - AuthorString = "SomeAuthor", + Author = { Username = "SomeAuthor" }, Title = "import" }, BaseDifficulty = new BeatmapDifficulty(), @@ -53,7 +53,7 @@ namespace osu.Game.Tests.Visual.Navigation Metadata = new BeatmapMetadata { Artist = "SomeArtist", - AuthorString = "SomeAuthor", + Author = { Username = "SomeAuthor" }, Title = "import" }, BaseDifficulty = new BeatmapDifficulty(), diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 0f5bea10e8..0298c3bea9 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -409,7 +409,7 @@ namespace osu.Game.Tests.Visual.SongSelect set.Beatmaps.ForEach(b => b.Metadata.Artist = zzz_string); if (i == 16) - set.Beatmaps.ForEach(b => b.Metadata.AuthorString = zzz_string); + set.Beatmaps.ForEach(b => b.Metadata.Author.Username = zzz_string); sets.Add(set); } diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs index 9ad5242df4..efacc395f1 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs @@ -208,7 +208,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Metadata = new BeatmapMetadata { - AuthorString = $"{ruleset.ShortName}Author", + Author = { Username = $"{ruleset.ShortName}Author" }, Artist = $"{ruleset.ShortName}Artist", Source = $"{ruleset.ShortName}Source", Title = $"{ruleset.ShortName}Title" @@ -230,7 +230,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Metadata = new BeatmapMetadata { - AuthorString = "WWWWWWWWWWWWWWW", + Author = { Username = "WWWWWWWWWWWWWWW" }, Artist = "Verrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrry long Artist", Source = "Verrrrry long Source", Title = "Verrrrry long Title" diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index ccecd69d21..a9eb7da5c9 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -54,7 +54,7 @@ namespace osu.Game.Beatmaps { Artist = @"Unknown", Title = @"Unknown", - AuthorString = @"Unknown Creator", + Author = { Username = @"Unknown Creator" }, }, DifficultyName = @"Normal", BaseDifficulty = Difficulty, diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs index 89f507581b..a3385e3abe 100644 --- a/osu.Game/Beatmaps/BeatmapMetadata.cs +++ b/osu.Game/Beatmaps/BeatmapMetadata.cs @@ -45,17 +45,6 @@ namespace osu.Game.Beatmaps IUser IBeatmapMetadataInfo.Author => Author; - #region Compatibility properties - - [Ignored] - public string AuthorString - { - get => Author.Username; - set => Author.Username = value; - } - - #endregion - public override string ToString() => this.GetDisplayTitle(); } } diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index e5db9d045a..893eb8ab78 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -251,7 +251,7 @@ namespace osu.Game.Beatmaps.Formats break; case @"Creator": - metadata.AuthorString = pair.Value; + metadata.Author.Username = pair.Value; break; case @"Version": diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index 7683accc5c..bd7a7677f2 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -144,7 +144,6 @@ namespace osu.Game.Database PreviewTime = metadata.PreviewTime, AudioFile = metadata.AudioFile, BackgroundFile = metadata.BackgroundFile, - AuthorString = metadata.AuthorString, }; } diff --git a/osu.Game/Screens/Edit/Setup/MetadataSection.cs b/osu.Game/Screens/Edit/Setup/MetadataSection.cs index 0d2b093a2e..f0ca3e1bbc 100644 --- a/osu.Game/Screens/Edit/Setup/MetadataSection.cs +++ b/osu.Game/Screens/Edit/Setup/MetadataSection.cs @@ -110,7 +110,7 @@ namespace osu.Game.Screens.Edit.Setup Beatmap.Metadata.TitleUnicode = TitleTextBox.Current.Value; Beatmap.Metadata.Title = RomanisedTitleTextBox.Current.Value; - Beatmap.Metadata.AuthorString = creatorTextBox.Current.Value; + Beatmap.Metadata.Author.Username = creatorTextBox.Current.Value; Beatmap.BeatmapInfo.DifficultyName = difficultyTextBox.Current.Value; Beatmap.Metadata.Source = sourceTextBox.Current.Value; Beatmap.Metadata.Tags = tagsTextBox.Current.Value; From f2b151023eb32294fb00171e45a13ab332bd08f2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Jan 2022 00:03:19 +0900 Subject: [PATCH 471/996] Create separate automapper configurations to reduce cyclic detach overhead This optimises the `BeatmapSetInfo` detach operation by avoiding detaching `BeatmapSetInfo.Beatmaps[].BeatmapSetInfo` a second time over. --- osu.Game/Database/RealmObjectExtensions.cs | 67 +++++++++++++++------- 1 file changed, 45 insertions(+), 22 deletions(-) diff --git a/osu.Game/Database/RealmObjectExtensions.cs b/osu.Game/Database/RealmObjectExtensions.cs index 4ddf0077ca..5ab7154486 100644 --- a/osu.Game/Database/RealmObjectExtensions.cs +++ b/osu.Game/Database/RealmObjectExtensions.cs @@ -74,21 +74,10 @@ namespace osu.Game.Database private static readonly IMapper mapper = new MapperConfiguration(c => { - c.ShouldMapField = fi => false; + applyCommonConfiguration(c); - // This is specifically to avoid mapping explicit interface implementations. - // If we want to limit this further, we can avoid mapping properties with no setter that are not IList<>. - // Takes a bit of effort to determine whether this is the case though, see https://stackoverflow.com/questions/951536/how-do-i-tell-whether-a-type-implements-ilist - c.ShouldMapProperty = pi => pi.GetMethod?.IsPublic == true; - - c.CreateMap(); - c.CreateMap(); - c.CreateMap(); - c.CreateMap(); - c.CreateMap(); - c.CreateMap(); - c.CreateMap(); - c.CreateMap(); + // This can be further optimised to reduce cyclic retrievals, similar to the optimised set mapper below. + // Only hasn't been done yet as we detach at the point of BeatmapInfo less often. c.CreateMap() .MaxDepth(2) .AfterMap((s, d) => @@ -102,13 +91,28 @@ namespace osu.Game.Database } } }); - c.CreateMap() - .MaxDepth(2) - .AfterMap((s, d) => - { - foreach (var beatmap in d.Beatmaps) - beatmap.BeatmapSet = d; - }); + }).CreateMapper(); + + /// + /// A slightly optimised mapper that avoids double-fetches in cyclic reference. + /// + private static readonly IMapper beatmap_set_mapper = new MapperConfiguration(c => + { + applyCommonConfiguration(c); + + c.CreateMap() + .MaxDepth(1) + .ForMember(b => b.BeatmapSet, cc => cc.Ignore()); + }).CreateMapper(); + + private static void applyCommonConfiguration(IMapperConfigurationExpression c) + { + c.ShouldMapField = fi => false; + + // This is specifically to avoid mapping explicit interface implementations. + // If we want to limit this further, we can avoid mapping properties with no setter that are not IList<>. + // Takes a bit of effort to determine whether this is the case though, see https://stackoverflow.com/questions/951536/how-do-i-tell-whether-a-type-implements-ilist + c.ShouldMapProperty = pi => pi.GetMethod?.IsPublic == true; c.Internal().ForAllMaps((typeMap, expression) => { @@ -118,7 +122,23 @@ namespace osu.Game.Database m.Ignore(); }); }); - }).CreateMapper(); + + c.CreateMap(); + c.CreateMap(); + c.CreateMap(); + c.CreateMap(); + c.CreateMap(); + c.CreateMap(); + c.CreateMap(); + c.CreateMap(); + c.CreateMap() + .MaxDepth(2) + .AfterMap((s, d) => + { + foreach (var beatmap in d.Beatmaps) + beatmap.BeatmapSet = d; + }); + } /// /// Create a detached copy of the each item in the collection. @@ -153,6 +173,9 @@ namespace osu.Game.Database if (!item.IsManaged) return item; + if (item is BeatmapSetInfo) + return beatmap_set_mapper.Map(item); + return mapper.Map(item); } From 60b80c88b60b356abc0ab3e27ef9b09817b34b30 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Jan 2022 00:49:16 +0900 Subject: [PATCH 472/996] Avoid file retrieval overhead when detaching `BeatmapSetInfo` It seems that no usages of `BeatmapSetInfo` detaches require files - a `WorkingBeatmap` is always obtained before doing further lookups. Therefore we can omit this include unless the detaching object is a `BeatmapInfo`. A refetch is performed when retrieving a `WorkingBeatmap` to complete the equation. --- osu.Game/Beatmaps/BeatmapManager.cs | 18 +++++++++++++++- osu.Game/Database/RealmObjectExtensions.cs | 24 +++++++++++++++------- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index f901b0fbc1..d983c087a0 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -299,7 +299,23 @@ namespace osu.Game.Beatmaps #region Implementation of IWorkingBeatmapCache - public WorkingBeatmap GetWorkingBeatmap(BeatmapInfo? importedBeatmap) => workingBeatmapCache.GetWorkingBeatmap(importedBeatmap); + public WorkingBeatmap GetWorkingBeatmap(BeatmapInfo? importedBeatmap) + { + // Detached sets don't come with files. + // If we seem to be missing files, now is a good time to re-fetch. + if (importedBeatmap?.BeatmapSet?.Files.Count == 0) + { + using (var realm = contextFactory.CreateContext()) + { + var refetch = realm.Find(importedBeatmap.ID)?.Detach(); + + if (refetch != null) + importedBeatmap = refetch; + } + } + + return workingBeatmapCache.GetWorkingBeatmap(importedBeatmap); + } public WorkingBeatmap GetWorkingBeatmap(ILive? importedBeatmap) { diff --git a/osu.Game/Database/RealmObjectExtensions.cs b/osu.Game/Database/RealmObjectExtensions.cs index 5ab7154486..140f41c5d8 100644 --- a/osu.Game/Database/RealmObjectExtensions.cs +++ b/osu.Game/Database/RealmObjectExtensions.cs @@ -76,6 +76,14 @@ namespace osu.Game.Database { applyCommonConfiguration(c); + c.CreateMap() + .MaxDepth(2) + .AfterMap((s, d) => + { + foreach (var beatmap in d.Beatmaps) + beatmap.BeatmapSet = d; + }); + // This can be further optimised to reduce cyclic retrievals, similar to the optimised set mapper below. // Only hasn't been done yet as we detach at the point of BeatmapInfo less often. c.CreateMap() @@ -100,6 +108,15 @@ namespace osu.Game.Database { applyCommonConfiguration(c); + c.CreateMap() + .MaxDepth(2) + .ForMember(b => b.Files, cc => cc.Ignore()) + .AfterMap((s, d) => + { + foreach (var beatmap in d.Beatmaps) + beatmap.BeatmapSet = d; + }); + c.CreateMap() .MaxDepth(1) .ForMember(b => b.BeatmapSet, cc => cc.Ignore()); @@ -131,13 +148,6 @@ namespace osu.Game.Database c.CreateMap(); c.CreateMap(); c.CreateMap(); - c.CreateMap() - .MaxDepth(2) - .AfterMap((s, d) => - { - foreach (var beatmap in d.Beatmaps) - beatmap.BeatmapSet = d; - }); } /// From 1db2135d7022f16c58f4131c1ffaec6494002be0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Jan 2022 01:05:22 +0900 Subject: [PATCH 473/996] Update tests to match new behaviour --- osu.Game.Tests/Database/BeatmapImporterTests.cs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index 1bba73bea0..227314cffd 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -57,8 +57,9 @@ namespace osu.Game.Tests.Database { detachedBeatmapSet = live.Detach(); - Assert.AreEqual(live.Files.Count, detachedBeatmapSet.Files.Count); - Assert.AreEqual(live.Files.Select(f => f.File).Count(), detachedBeatmapSet.Files.Select(f => f.File).Count()); + // files are omitted + Assert.AreEqual(0, detachedBeatmapSet.Files.Count); + Assert.AreEqual(live.Beatmaps.Count, detachedBeatmapSet.Beatmaps.Count); Assert.AreEqual(live.Beatmaps.Select(f => f.Difficulty).Count(), detachedBeatmapSet.Beatmaps.Select(f => f.Difficulty).Count()); Assert.AreEqual(live.Metadata, detachedBeatmapSet.Metadata); @@ -67,11 +68,9 @@ namespace osu.Game.Tests.Database Debug.Assert(detachedBeatmapSet != null); // Check detached instances can all be accessed without throwing. - Assert.NotNull(detachedBeatmapSet.Files.Count); - Assert.NotZero(detachedBeatmapSet.Files.Select(f => f.File).Count()); + Assert.AreEqual(0, detachedBeatmapSet.Files.Count); Assert.NotNull(detachedBeatmapSet.Beatmaps.Count); Assert.NotZero(detachedBeatmapSet.Beatmaps.Select(f => f.Difficulty).Count()); - Assert.NotNull(detachedBeatmapSet.Beatmaps.First().Path); Assert.NotNull(detachedBeatmapSet.Metadata); // Check cyclic reference to beatmap set @@ -96,9 +95,12 @@ namespace osu.Game.Tests.Database Assert.NotNull(beatmapSet); Debug.Assert(beatmapSet != null); - BeatmapSetInfo? detachedBeatmapSet = null; + // Detach at the BeatmapInfo point, similar to what GetWorkingBeatmap does. + BeatmapInfo? detachedBeatmap = null; - beatmapSet.PerformRead(s => detachedBeatmapSet = s.Detach()); + beatmapSet.PerformRead(s => detachedBeatmap = s.Beatmaps.First().Detach()); + + BeatmapSetInfo? detachedBeatmapSet = detachedBeatmap?.BeatmapSet; Debug.Assert(detachedBeatmapSet != null); From 96d07e20edad425dd505fc70ec216f7c8160b2ab Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Jan 2022 02:17:43 +0900 Subject: [PATCH 474/996] Revert nunit test adaptor version bump until console output bug is resolved Tests have started to output too much log content, causing viewing CI failures to be painfully impossible. Roll back for now. Fix may be related to https://github.com/nunit/nunit3-vs-adapter/issues/941, although we don't use filter. --- .../osu.Game.Rulesets.EmptyFreeform.Tests.csproj | 2 +- .../osu.Game.Rulesets.Pippidon.Tests.csproj | 2 +- .../osu.Game.Rulesets.EmptyScrolling.Tests.csproj | 2 +- .../osu.Game.Rulesets.Pippidon.Tests.csproj | 2 +- osu.Game.Benchmarks/osu.Game.Benchmarks.csproj | 2 +- .../osu.Game.Rulesets.Catch.Tests.csproj | 2 +- .../osu.Game.Rulesets.Mania.Tests.csproj | 2 +- osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj | 2 +- .../osu.Game.Rulesets.Taiko.Tests.csproj | 2 +- osu.Game.Tests/osu.Game.Tests.csproj | 2 +- osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj | 2 +- 11 files changed, 11 insertions(+), 11 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 a7078f1c09..3c6aaa39ca 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 @@ -12,7 +12,7 @@ - + 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 c133b0e3f8..0719dd30df 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 @@ -12,7 +12,7 @@ - + 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 92b48470e8..d0db43cc81 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 @@ -12,7 +12,7 @@ - + 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 c133b0e3f8..0719dd30df 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 @@ -12,7 +12,7 @@ - + diff --git a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj index a8599f2cb6..57b914bee6 100644 --- a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj +++ b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj @@ -9,7 +9,7 @@ - + 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 d5496b7479..13f2e25f05 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 @@ -4,7 +4,7 @@ - + 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 2f12b1535e..d51a6da4f9 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 @@ -4,7 +4,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 e5b2e070d8..fea2e408f6 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 @@ -5,7 +5,7 @@ - + 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 e48d80323a..ad3713e047 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 @@ -4,7 +4,7 @@ - + diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index c64ef918e3..3b115d43e5 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -6,7 +6,7 @@ - + diff --git a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj index fb09a7be1e..130fcfaca1 100644 --- a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj +++ b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj @@ -7,7 +7,7 @@ - + WinExe From 761e161eec0d774aaae10ed4e3c5c042003385a9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Jan 2022 09:44:21 +0900 Subject: [PATCH 475/996] Add comment explaining ignore rule --- osu.Game/Database/RealmObjectExtensions.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Database/RealmObjectExtensions.cs b/osu.Game/Database/RealmObjectExtensions.cs index 5ab7154486..097e620e12 100644 --- a/osu.Game/Database/RealmObjectExtensions.cs +++ b/osu.Game/Database/RealmObjectExtensions.cs @@ -102,6 +102,7 @@ namespace osu.Game.Database c.CreateMap() .MaxDepth(1) + // This is not required as it will be populated in the `AfterMap` call from the `BeatmapInfo`'s parent. .ForMember(b => b.BeatmapSet, cc => cc.Ignore()); }).CreateMapper(); From 3bc091fe6d33cda1291e908b4818b0ddcf8ead64 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Jan 2022 09:46:45 +0900 Subject: [PATCH 476/996] Add ignore rules on more helper properties --- osu.Game/Input/Bindings/RealmKeyBinding.cs | 2 ++ osu.Game/Scoring/ScoreInfo.cs | 1 + 2 files changed, 3 insertions(+) diff --git a/osu.Game/Input/Bindings/RealmKeyBinding.cs b/osu.Game/Input/Bindings/RealmKeyBinding.cs index 6a408847fe..32813ada16 100644 --- a/osu.Game/Input/Bindings/RealmKeyBinding.cs +++ b/osu.Game/Input/Bindings/RealmKeyBinding.cs @@ -20,12 +20,14 @@ namespace osu.Game.Input.Bindings public int? Variant { get; set; } + [Ignored] public KeyCombination KeyCombination { get => KeyCombinationString; set => KeyCombinationString = value.ToString(); } + [Ignored] public object Action { get => ActionInt; diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index e1328d8a06..b268e2cd91 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -104,6 +104,7 @@ namespace osu.Game.Scoring } } + [Ignored] public ScoreRank Rank { get => (ScoreRank)RankInt; From 64a023665e9944f93615de503de190a64144bcf2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Jan 2022 10:16:54 +0900 Subject: [PATCH 477/996] Avoid taking more than one backup per migration run --- osu.Game/Database/EFToRealmMigrator.cs | 30 +++++++++++++++++++------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index d479f81ead..b7f3eebe7a 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -24,6 +24,8 @@ namespace osu.Game.Database private readonly RealmContextFactory realmContextFactory; private readonly OsuConfigManager config; + private bool hasTakenBackup; + public EFToRealmMigrator(DatabaseContextFactory efContextFactory, RealmContextFactory realmContextFactory, OsuConfigManager config) { this.efContextFactory = efContextFactory; @@ -70,8 +72,16 @@ namespace osu.Game.Database using (var realm = realmContextFactory.CreateContext()) { Logger.Log($"Found {existingBeatmapSets.Count} beatmaps in EF", LoggingTarget.Database); - string migration = $"before_beatmap_migration_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}"; - efContextFactory.CreateBackup($"client.{migration}.db"); + + if (!hasTakenBackup) + { + string migration = $"before_beatmap_migration_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}"; + + efContextFactory.CreateBackup($"client.{migration}.db"); + realmContextFactory.CreateBackup($"client.{migration}.realm"); + + hasTakenBackup = true; + } // only migrate data if the realm database is empty. // note that this cannot be written as: `realm.All().All(s => s.Protected)`, because realm does not support `.All()`. @@ -81,8 +91,6 @@ namespace osu.Game.Database } else { - realmContextFactory.CreateBackup($"client.{migration}.realm"); - using (var transaction = realm.BeginWrite()) { foreach (var beatmapSet in existingBeatmapSets) @@ -195,8 +203,16 @@ namespace osu.Game.Database using (var realm = realmContextFactory.CreateContext()) { Logger.Log($"Found {existingScores.Count} scores in EF", LoggingTarget.Database); - string migration = $"before_score_migration_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}"; - efContextFactory.CreateBackup($"client.{migration}.db"); + + if (!hasTakenBackup) + { + string migration = $"before_score_migration_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}"; + + efContextFactory.CreateBackup($"client.{migration}.db"); + realmContextFactory.CreateBackup($"client.{migration}.realm"); + + hasTakenBackup = true; + } // only migrate data if the realm database is empty. // note that this cannot be written as: `realm.All().All(s => s.Protected)`, because realm does not support `.All()`. @@ -206,8 +222,6 @@ namespace osu.Game.Database } else { - realmContextFactory.CreateBackup($"client.{migration}.realm"); - using (var transaction = realm.BeginWrite()) { foreach (var score in existingScores) From 04e9ffa966afd6b95698591de468006302328441 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Jan 2022 10:20:43 +0900 Subject: [PATCH 478/996] Freshen some comments --- osu.Game/Database/EFToRealmMigrator.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index b7f3eebe7a..6b911ebad0 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -84,7 +84,7 @@ namespace osu.Game.Database } // only migrate data if the realm database is empty. - // note that this cannot be written as: `realm.All().All(s => s.Protected)`, because realm does not support `.All()`. + // note that this cannot be written as: `realm.All().All(s => s.Protected)`, because realm does not support `.All()`. if (realm.All().Any(s => !s.Protected)) { Logger.Log("Skipping migration as realm already has beatmaps loaded", LoggingTarget.Database); @@ -215,7 +215,6 @@ namespace osu.Game.Database } // only migrate data if the realm database is empty. - // note that this cannot be written as: `realm.All().All(s => s.Protected)`, because realm does not support `.All()`. if (realm.All().Any()) { Logger.Log("Skipping migration as realm already has scores loaded", LoggingTarget.Database); From e1a35714be3720178f2a56ef88bcdb038946f04a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Jan 2022 10:30:17 +0900 Subject: [PATCH 479/996] Add notification for debug builds when database migration occurs --- osu.Game/Database/DatabaseContextFactory.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/DatabaseContextFactory.cs b/osu.Game/Database/DatabaseContextFactory.cs index cc690a9fda..635c4373cd 100644 --- a/osu.Game/Database/DatabaseContextFactory.cs +++ b/osu.Game/Database/DatabaseContextFactory.cs @@ -5,6 +5,7 @@ using System.IO; using System.Linq; using System.Threading; using Microsoft.EntityFrameworkCore.Storage; +using osu.Framework.Development; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Statistics; @@ -146,11 +147,15 @@ namespace osu.Game.Database Database = { AutoTransactionsEnabled = false } }; - public void CreateBackup(string filename) + public void CreateBackup(string backupFilename) { - Logger.Log($"Creating full EF database backup at {filename}", LoggingTarget.Database); + Logger.Log($"Creating full EF database backup at {backupFilename}", LoggingTarget.Database); + + if (DebugUtils.IsDebugBuild) + Logger.Log("Your development database has been fully migrated to realm. If you switch back to a pre-realm branch and need your previous database, rename the backup file back to \"client.db\".\n\nNote that doing this can potentially leave your file store in a bad state.", level: LogLevel.Important); + using (var source = storage.GetStream(DATABASE_NAME)) - using (var destination = storage.GetStream(filename, FileAccess.Write, FileMode.CreateNew)) + using (var destination = storage.GetStream(backupFilename, FileAccess.Write, FileMode.CreateNew)) source.CopyTo(destination); } From 195534a1d2e0a890be7b16feb3e936ed2fe426c4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Jan 2022 10:31:09 +0900 Subject: [PATCH 480/996] Only output "successful" messages when copy actually occurred --- osu.Game/Database/EFToRealmMigrator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index 6b911ebad0..76e5219f87 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -148,10 +148,10 @@ namespace osu.Game.Database } transaction.Commit(); + Logger.Log($"Successfully migrated {existingBeatmapSets.Count} beatmaps to realm", LoggingTarget.Database); } } - Logger.Log($"Successfully migrated {existingBeatmapSets.Count} beatmaps to realm", LoggingTarget.Database); ef.Context.RemoveRange(existingBeatmapSets); // Intentionally don't clean up the files, so they don't get purged by EF. } @@ -257,10 +257,10 @@ namespace osu.Game.Database } transaction.Commit(); + Logger.Log($"Successfully migrated {existingScores.Count} scores to realm", LoggingTarget.Database); } } - Logger.Log($"Successfully migrated {existingScores.Count} scores to realm", LoggingTarget.Database); db.Context.RemoveRange(existingScores); // Intentionally don't clean up the files, so they don't get purged by EF. } From 261b4988f9552b63a951efe3041e34a0b1f377a8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Jan 2022 10:58:59 +0900 Subject: [PATCH 481/996] Only catch `RealmExceptions` to avoid blocking the nested `TimeoutException` --- osu.Game/Database/RealmContextFactory.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 52f515a4bb..901f28a449 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -21,6 +21,7 @@ using osu.Game.Stores; using osu.Game.Rulesets; using osu.Game.Scoring; using Realms; +using Realms.Exceptions; #nullable enable @@ -427,7 +428,7 @@ namespace osu.Game.Database throw new TimeoutException(@"Took too long to acquire lock"); } } - catch (Exception e) + catch (RealmException e) { // Compact may fail if the realm is in a bad state. // We still want to continue with the blocking operation, though. From c52899b1fb0f6a6e52d73ee93d74247b931bc6f4 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 19 Jan 2022 11:56:44 +0900 Subject: [PATCH 482/996] Rename property --- osu.Game/Database/RealmContextFactory.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 10c2438df9..8be8eab567 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -361,13 +361,13 @@ namespace osu.Game.Database private string? getRulesetShortNameFromLegacyID(long rulesetId) => efContextFactory?.Get().RulesetInfo.FirstOrDefault(r => r.ID == rulesetId)?.ShortName; - public void CreateBackup(string filename) + public void CreateBackup(string backupFilename) { using (BlockAllOperations()) { - Logger.Log($"Creating full realm database backup at {filename}", LoggingTarget.Database); + Logger.Log($"Creating full realm database backup at {backupFilename}", LoggingTarget.Database); using (var source = storage.GetStream(Filename)) - using (var destination = storage.GetStream(filename, FileAccess.Write, FileMode.CreateNew)) + using (var destination = storage.GetStream(backupFilename, FileAccess.Write, FileMode.CreateNew)) source.CopyTo(destination); } } From 2363130f8b2d791f98d3d7023e75b964b6240a49 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Jan 2022 12:33:56 +0900 Subject: [PATCH 483/996] Add back `BaseDifficulty` with obsoletion counter to account for custom rulesets --- osu.Game/Beatmaps/BeatmapInfo.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index b91f05cd11..f4d8697042 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -165,6 +165,14 @@ namespace osu.Game.Beatmaps } } + [Ignored] + [Obsolete("Use BeatmapInfo.Difficulty instead.")] // can be removed 20220719 + public BeatmapDifficulty BaseDifficulty + { + get => Difficulty; + set => Difficulty = value; + } + [Ignored] public string? Path => File?.Filename; From 9be5bf38c639a0736687b52e41e60d55329b7088 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Jan 2022 13:20:52 +0900 Subject: [PATCH 484/996] Simplify binding/invalidation in `TopLocalRank` --- .../Select/Carousel/DrawableCarouselBeatmap.cs | 1 - .../Screens/Select/Carousel/TopLocalRank.cs | 17 ++++++----------- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index a5fabd9237..3576b77ae8 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -159,7 +159,6 @@ namespace osu.Game.Screens.Select.Carousel new TopLocalRank(beatmapInfo) { Scale = new Vector2(0.8f), - Size = new Vector2(40, 20) }, starCounter = new StarCounter { diff --git a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs index 4542ab897b..7ac99f4935 100644 --- a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs +++ b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs @@ -13,6 +13,7 @@ using osu.Game.Online.API; using osu.Game.Online.Leaderboards; using osu.Game.Rulesets; using osu.Game.Scoring; +using osuTK; using Realms; namespace osu.Game.Screens.Select.Carousel @@ -32,12 +33,12 @@ namespace osu.Game.Screens.Select.Carousel private IDisposable scoreSubscription; - private bool rankUpdatePending; - public TopLocalRank(BeatmapInfo beatmapInfo) : base(null) { this.beatmapInfo = beatmapInfo; + + Size = new Vector2(40, 20); } protected override void LoadComplete() @@ -46,10 +47,6 @@ namespace osu.Game.Screens.Select.Carousel ruleset.BindValueChanged(_ => { - rankUpdatePending = true; - // Required since presence is changed via IsPresent override - Invalidate(Invalidation.Presence); - scoreSubscription?.Dispose(); scoreSubscription = realmFactory.Context.All() .Filter($"{nameof(ScoreInfo.User)}.{nameof(RealmUser.OnlineID)} == $0" @@ -59,16 +56,14 @@ namespace osu.Game.Screens.Select.Carousel .OrderByDescending(s => s.TotalScore) .QueryAsyncWithNotifications((items, changes, ___) => { - if (changes == null) - rankUpdatePending = false; - Rank = items.FirstOrDefault()?.Rank; + // Required since presence is changed via IsPresent override + Invalidate(Invalidation.Presence); }); }, true); } - // We're present if a rank is set, or if there is a pending rank update (IsPresent = true is required for the scheduler to run). - public override bool IsPresent => base.IsPresent && (Rank != null || rankUpdatePending); + public override bool IsPresent => base.IsPresent && Rank != null; protected override void Dispose(bool isDisposing) { From 201f2d78135a03a9b4345761f5141ae0dccaeeda Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Jan 2022 13:48:00 +0900 Subject: [PATCH 485/996] Add comprehensive test coverage for `TopLocalRank` --- .../SongSelect/TestSceneTopLocalRank.cs | 143 ++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs new file mode 100644 index 0000000000..3aa5a759e6 --- /dev/null +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs @@ -0,0 +1,143 @@ +// 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.Allocation; +using osu.Framework.Audio; +using osu.Framework.Extensions; +using osu.Framework.Graphics; +using osu.Framework.Platform; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Scoring; +using osu.Game.Screens.Select.Carousel; +using osu.Game.Tests.Resources; +using osuTK; + +namespace osu.Game.Tests.Visual.SongSelect +{ + public class TestSceneTopLocalRank : OsuTestScene + { + private RulesetStore rulesets; + private BeatmapManager beatmapManager; + private ScoreManager scoreManager; + private TopLocalRank topLocalRank; + + [BackgroundDependencyLoader] + private void load(GameHost host, AudioManager audio) + { + Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); + Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(scoreManager = new ScoreManager(rulesets, () => beatmapManager, LocalStorage, ContextFactory, Scheduler)); + Dependencies.Cache(ContextFactory); + + beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); + } + + private BeatmapInfo importedBeatmap => beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps.First(b => b.Ruleset.ShortName == "osu"); + + [SetUpSteps] + public void SetUpSteps() + { + AddStep("Delete all scores", () => scoreManager.Delete()); + + AddStep("Create local rank", () => + { + Add(topLocalRank = new TopLocalRank(importedBeatmap) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(10), + }); + }); + } + + [Test] + public void TestBasicImportDelete() + { + ScoreInfo testScoreInfo = null; + + AddAssert("Initially not present", () => !topLocalRank.IsPresent); + + AddStep("Add score for current user", () => + { + testScoreInfo = TestResources.CreateTestScoreInfo(importedBeatmap); + + testScoreInfo.User = API.LocalUser.Value; + testScoreInfo.Rank = ScoreRank.B; + + scoreManager.Import(testScoreInfo); + }); + + AddUntilStep("Became present", () => topLocalRank.IsPresent); + AddAssert("Correct rank", () => topLocalRank.Rank == ScoreRank.B); + + AddStep("Delete score", () => + { + scoreManager.Delete(testScoreInfo); + }); + + AddUntilStep("Became not present", () => !topLocalRank.IsPresent); + } + + [Test] + public void TestRulesetChange() + { + ScoreInfo testScoreInfo; + + AddStep("Add score for current user", () => + { + testScoreInfo = TestResources.CreateTestScoreInfo(importedBeatmap); + + testScoreInfo.User = API.LocalUser.Value; + testScoreInfo.Rank = ScoreRank.B; + + scoreManager.Import(testScoreInfo); + }); + + AddUntilStep("Wait for initial presence", () => topLocalRank.IsPresent); + + AddStep("Change ruleset", () => Ruleset.Value = rulesets.GetRuleset("fruits")); + AddUntilStep("Became not present", () => !topLocalRank.IsPresent); + + AddStep("Change ruleset back", () => Ruleset.Value = rulesets.GetRuleset("osu")); + AddUntilStep("Became present", () => topLocalRank.IsPresent); + } + + [Test] + public void TestHigherScoreSet() + { + ScoreInfo testScoreInfo = null; + + AddAssert("Initially not present", () => !topLocalRank.IsPresent); + + AddStep("Add score for current user", () => + { + testScoreInfo = TestResources.CreateTestScoreInfo(importedBeatmap); + + testScoreInfo.User = API.LocalUser.Value; + testScoreInfo.Rank = ScoreRank.B; + + scoreManager.Import(testScoreInfo); + }); + + AddUntilStep("Became present", () => topLocalRank.IsPresent); + AddAssert("Correct rank", () => topLocalRank.Rank == ScoreRank.B); + + AddStep("Add higher score for current user", () => + { + var testScoreInfo2 = TestResources.CreateTestScoreInfo(importedBeatmap); + + testScoreInfo2.User = API.LocalUser.Value; + testScoreInfo2.Rank = ScoreRank.S; + testScoreInfo2.TotalScore = testScoreInfo.TotalScore + 1; + + scoreManager.Import(testScoreInfo2); + }); + + AddAssert("Correct rank", () => topLocalRank.Rank == ScoreRank.S); + } + } +} From 493a970ed5985d7507b859451043feeb97499a21 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Wed, 19 Jan 2022 14:08:21 +0900 Subject: [PATCH 486/996] Remove unused AudioManager --- .../OnlinePlay/Multiplayer/Participants/ParticipantsList.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantsList.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantsList.cs index fe40a4bfe6..afb2111023 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantsList.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantsList.cs @@ -3,7 +3,6 @@ using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Audio; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Containers; @@ -17,7 +16,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants private FillFlowContainer panels; [BackgroundDependencyLoader] - private void load(AudioManager audio) + private void load() { InternalChild = new OsuContextMenuContainer { From 664b4fdaf09e86e4b55d6c4e0ddf9c3929aac172 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Jan 2022 14:17:56 +0900 Subject: [PATCH 487/996] Fix legacy score imports not correctly getting classic mod assigned --- osu.Game/Scoring/ScoreInfo.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index b268e2cd91..fe99d5d9a8 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -191,9 +191,8 @@ namespace osu.Game.Scoring } set { - apiMods = null; + clearAllMods(); mods = value; - updateModsJson(); } } @@ -220,14 +219,19 @@ namespace osu.Game.Scoring } set { + clearAllMods(); apiMods = value; - mods = null; - - // We potentially can't update this yet due to Ruleset being late-bound, so instead update on read as necessary. updateModsJson(); } } + private void clearAllMods() + { + ModsJson = string.Empty; + mods = null; + apiMods = null; + } + private void updateModsJson() { ModsJson = JsonConvert.SerializeObject(APIMods); From bb6f40d16e53816a86443a397c6d1747ed3e7180 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Jan 2022 14:25:40 +0900 Subject: [PATCH 488/996] Add test coverage of all mod storages containing classic mod post-import --- osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs index 3d4b05b52b..2ba8c51a10 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs @@ -12,6 +12,7 @@ using osu.Game.Replays; using osu.Game.Rulesets; using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Mania; +using osu.Game.Rulesets.Mania.Mods; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Replays; using osu.Game.Rulesets.Osu.UI; @@ -51,6 +52,11 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(829_931, score.ScoreInfo.TotalScore); Assert.AreEqual(3, score.ScoreInfo.MaxCombo); + + Assert.IsTrue(score.ScoreInfo.Mods.Any(m => m is ManiaModClassic)); + Assert.IsTrue(score.ScoreInfo.APIMods.Any(m => m.Acronym == "CL")); + Assert.IsTrue(score.ScoreInfo.ModsJson.Contains("CL")); + Assert.IsTrue(Precision.AlmostEquals(0.8889, score.ScoreInfo.Accuracy, 0.0001)); Assert.AreEqual(ScoreRank.B, score.ScoreInfo.Rank); From 18886f5d2ebdd2061e390854920df1d1c718aba0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Jan 2022 14:28:16 +0900 Subject: [PATCH 489/996] Use empty string instead of empty array for `ModsJson` when no mods are present Not really correct, but this is how most scores have been stored until now so let's do this for consistency. --- osu.Game/Scoring/ScoreInfo.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index fe99d5d9a8..cfc37e956d 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -234,7 +234,9 @@ namespace osu.Game.Scoring private void updateModsJson() { - ModsJson = JsonConvert.SerializeObject(APIMods); + ModsJson = APIMods.Length > 0 + ? JsonConvert.SerializeObject(APIMods) + : string.Empty; } public IEnumerable GetStatisticsForDisplay() From a002dacdce3bdb66d5f44cdee6a2423e954a1bc4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Jan 2022 14:32:56 +0900 Subject: [PATCH 490/996] Add test coverage of various `ScoreInfo` mod set operations --- osu.Game.Tests/NonVisual/ScoreInfoTest.cs | 41 +++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/osu.Game.Tests/NonVisual/ScoreInfoTest.cs b/osu.Game.Tests/NonVisual/ScoreInfoTest.cs index 6e5718cd4c..ea2f50f3bb 100644 --- a/osu.Game.Tests/NonVisual/ScoreInfoTest.cs +++ b/osu.Game.Tests/NonVisual/ScoreInfoTest.cs @@ -2,6 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; +using osu.Game.Online.API; +using osu.Game.Rulesets.Mania; +using osu.Game.Rulesets.Mania.Mods; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; @@ -29,5 +34,41 @@ namespace osu.Game.Tests.NonVisual Assert.That(scoreCopy.Rank, Is.EqualTo(ScoreRank.B)); Assert.That(score.Rank, Is.EqualTo(ScoreRank.X)); } + + [Test] + public void TestModsInitiallyEmpty() + { + var score = new ScoreInfo(); + + Assert.That(score.Mods, Is.Empty); + Assert.That(score.APIMods, Is.Empty); + Assert.That(score.ModsJson, Is.Empty); + } + + [Test] + public void TestModsUpdatedCorrectly() + { + var score = new ScoreInfo + { + Mods = new Mod[] { new ManiaModClassic() }, + Ruleset = new ManiaRuleset().RulesetInfo, + }; + + Assert.That(score.Mods, Contains.Item(new ManiaModClassic())); + Assert.That(score.APIMods, Contains.Item(new APIMod(new ManiaModClassic()))); + Assert.That(score.ModsJson, Contains.Substring("CL")); + + score.APIMods = new[] { new APIMod(new ManiaModDoubleTime()) }; + + Assert.That(score.Mods, Contains.Item(new ManiaModDoubleTime())); + Assert.That(score.APIMods, Contains.Item(new APIMod(new ManiaModDoubleTime()))); + Assert.That(score.ModsJson, Contains.Substring("DT")); + + score.Mods = new Mod[] { new ManiaModClassic() }; + + Assert.That(score.Mods, Contains.Item(new ManiaModClassic())); + Assert.That(score.APIMods, Contains.Item(new APIMod(new ManiaModClassic()))); + Assert.That(score.ModsJson, Contains.Substring("CL")); + } } } From 03ac91a3ee43144ea32c097dfb0f71e86773a75d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Jan 2022 14:56:44 +0900 Subject: [PATCH 491/996] Consider all points in a group to meet redundancy check --- .../Components/Timelines/Summary/Parts/GroupVisualisation.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs index 83759bbc2b..88587399f2 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs @@ -62,6 +62,6 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts /// For display purposes, check whether the proposed group is made redundant by this visualisation group. /// public bool IsVisuallyRedundant(ControlPointGroup other) => - other.ControlPoints.Any(c => InternalChildren.OfType().Any(c2 => c2.IsVisuallyRedundant(c))); + other.ControlPoints.All(c => InternalChildren.OfType().Any(c2 => c2.IsVisuallyRedundant(c))); } } From 5f5765d6a2270b2536973e84d244be6421e9c61d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Jan 2022 14:57:01 +0900 Subject: [PATCH 492/996] Reduce redundancy time range to create a bit more visual blending on the timeline --- .../Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs index a33bb73681..f1edb7dc7e 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs @@ -36,7 +36,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts // as an optimisation, don't add a visualisation if there are already groups with the same types in close proximity. // for newly added control points (ie. lazer editor first where group is added empty) we always skip for simplicity. // that is fine, because cases where this is causing a performance issue are mostly where external tools were used to create an insane number of points. - if (Children.Any(g => Math.Abs(g.Group.Time - group.Time) < 1000 && g.IsVisuallyRedundant(group))) + if (Children.Any(g => Math.Abs(g.Group.Time - group.Time) < 500 && g.IsVisuallyRedundant(group))) continue; Add(new GroupVisualisation(group)); From faec62be5128f5e54832d7ea7256f3d0301de91f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Jan 2022 15:11:36 +0900 Subject: [PATCH 493/996] Force a realm refresh after migration This really shouldn't have much effect as it will be run in the first `Update` method and is probably a noop (we are already pointing to the newest version due to just performing writes), but seems like a safe addition. In general `Realm.Refresh()` only really does anything when there's multithreaded usage and notifications to be sent. --- osu.Game/Database/EFToRealmMigrator.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index 1de6c25070..0a7d42e808 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -42,6 +42,9 @@ namespace osu.Game.Database migrateScores(ef); } + Logger.Log("Refreshing realm...", LoggingTarget.Database); + realmContextFactory.Refresh(); + // Delete the database permanently. // Will cause future startups to not attempt migration. Logger.Log("Migration successful, deleting EF database", LoggingTarget.Database); From 973836484c5b168684677f2d3aebf2cd4af659b3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Jan 2022 15:52:59 +0900 Subject: [PATCH 494/996] Avoid using a write context for EF migration This reduces a stall delay as EF tries to process changes over tracked objects during disposal of the context. --- osu.Game/Database/EFToRealmMigrator.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index 0a7d42e808..908f1b0294 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -34,7 +34,7 @@ namespace osu.Game.Database public void Run() { - using (var ef = efContextFactory.GetForWrite()) + using (var ef = efContextFactory.Get()) { migrateSettings(ef); migrateSkins(ef); @@ -51,10 +51,10 @@ namespace osu.Game.Database efContextFactory.ResetDatabase(); } - private void migrateBeatmaps(DatabaseWriteUsage ef) + private void migrateBeatmaps(OsuDbContext ef) { // can be removed 20220730. - var existingBeatmapSets = ef.Context.EFBeatmapSetInfo + var existingBeatmapSets = ef.EFBeatmapSetInfo .Include(s => s.Beatmaps).ThenInclude(b => b.RulesetInfo) .Include(s => s.Beatmaps).ThenInclude(b => b.Metadata) .Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty) @@ -180,10 +180,10 @@ namespace osu.Game.Database }; } - private void migrateScores(DatabaseWriteUsage db) + private void migrateScores(OsuDbContext db) { // can be removed 20220730. - var existingScores = db.Context.ScoreInfo + var existingScores = db.ScoreInfo .Include(s => s.Ruleset) .Include(s => s.BeatmapInfo) .Include(s => s.Files) @@ -263,10 +263,10 @@ namespace osu.Game.Database } } - private void migrateSkins(DatabaseWriteUsage db) + private void migrateSkins(OsuDbContext db) { // can be removed 20220530. - var existingSkins = db.Context.SkinInfo + var existingSkins = db.SkinInfo .Include(s => s.Files) .ThenInclude(f => f.FileInfo) .ToList(); @@ -335,10 +335,10 @@ namespace osu.Game.Database } } - private void migrateSettings(DatabaseWriteUsage db) + private void migrateSettings(OsuDbContext db) { // migrate ruleset settings. can be removed 20220315. - var existingSettings = db.Context.DatabasedSetting.ToList(); + var existingSettings = db.DatabasedSetting.ToList(); // previous entries in EF are removed post migration. if (!existingSettings.Any()) From 42736c99951341a28224b207d185a9e8803bb74f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Jan 2022 16:01:17 +0900 Subject: [PATCH 495/996] Add transactional committing of scores/beatmaps This helps slightly with performance, allows better monitoring via realm studio, but most importantly greatly reduces filesize. fully compacted: 109M transaction size 100: 115M transaction size 1000: 123M transaction size 10000: 164M single transaction: 183M With a transaction size of 100 there is a performance reduction, so 1000 seems to be the best middle-ground. --- osu.Game/Database/EFToRealmMigrator.cs | 42 +++++++++++++++++++++----- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index 908f1b0294..a7cfd71ff9 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -94,10 +94,20 @@ namespace osu.Game.Database } else { - using (var transaction = realm.BeginWrite()) + var transaction = realm.BeginWrite(); + int written = 0; + + try { foreach (var beatmapSet in existingBeatmapSets) { + if (++written % 1000 == 0) + { + transaction.Commit(); + transaction = realm.BeginWrite(); + Logger.Log($"Migrated {written}/{count} beatmaps...", LoggingTarget.Database); + } + var realmBeatmapSet = new BeatmapSetInfo { OnlineID = beatmapSet.OnlineID ?? -1, @@ -149,10 +159,13 @@ namespace osu.Game.Database realm.Add(realmBeatmapSet); } - - transaction.Commit(); - Logger.Log($"Successfully migrated {count} beatmaps to realm", LoggingTarget.Database); } + finally + { + transaction.Commit(); + } + + Logger.Log($"Successfully migrated {count} beatmaps to realm", LoggingTarget.Database); } } } @@ -221,10 +234,20 @@ namespace osu.Game.Database } else { - using (var transaction = realm.BeginWrite()) + var transaction = realm.BeginWrite(); + int written = 0; + + try { foreach (var score in existingScores) { + if (++written % 1000 == 0) + { + transaction.Commit(); + transaction = realm.BeginWrite(); + Logger.Log($"Migrated {written}/{count} scores...", LoggingTarget.Database); + } + var realmScore = new ScoreInfo { Hash = score.Hash, @@ -255,10 +278,13 @@ namespace osu.Game.Database realm.Add(realmScore); } - - transaction.Commit(); - Logger.Log($"Successfully migrated {count} scores to realm", LoggingTarget.Database); } + finally + { + transaction.Commit(); + } + + Logger.Log($"Successfully migrated {count} scores to realm", LoggingTarget.Database); } } } From fd5198d667f256ab59781d686721fd9507256ff7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Jan 2022 16:22:17 +0900 Subject: [PATCH 496/996] Avoid using parameterless constructors in migration code Minor performance improvement (less garbage). --- osu.Game/Database/EFToRealmMigrator.cs | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index a7cfd71ff9..5e49a44733 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -122,7 +122,10 @@ namespace osu.Game.Database foreach (var beatmap in beatmapSet.Beatmaps) { - var realmBeatmap = new BeatmapInfo + var ruleset = realm.Find(beatmap.RulesetInfo.ShortName); + var metadata = getBestMetadata(beatmap.Metadata, beatmapSet.Metadata); + + var realmBeatmap = new BeatmapInfo(ruleset, new BeatmapDifficulty(beatmap.BaseDifficulty), metadata) { DifficultyName = beatmap.DifficultyName, Status = beatmap.Status, @@ -148,9 +151,6 @@ namespace osu.Game.Database CountdownOffset = beatmap.CountdownOffset, MaxCombo = beatmap.MaxCombo, Bookmarks = beatmap.Bookmarks, - Ruleset = realm.Find(beatmap.RulesetInfo.ShortName), - Difficulty = new BeatmapDifficulty(beatmap.BaseDifficulty), - Metadata = getBestMetadata(beatmap.Metadata, beatmapSet.Metadata), BeatmapSet = realmBeatmapSet, }; @@ -180,7 +180,7 @@ namespace osu.Game.Database TitleUnicode = metadata.TitleUnicode, Artist = metadata.Artist, ArtistUnicode = metadata.ArtistUnicode, - Author = new RealmUser + Author = { OnlineID = metadata.Author.Id, Username = metadata.Author.Username, @@ -248,7 +248,15 @@ namespace osu.Game.Database Logger.Log($"Migrated {written}/{count} scores...", LoggingTarget.Database); } - var realmScore = new ScoreInfo + var beatmap = realm.All().First(b => b.Hash == score.BeatmapInfo.Hash); + var ruleset = realm.Find(score.Ruleset.ShortName); + var user = new RealmUser + { + OnlineID = score.User.OnlineID, + Username = score.User.Username + }; + + var realmScore = new ScoreInfo(beatmap, ruleset, user) { Hash = score.Hash, DeletePending = score.DeletePending, @@ -262,8 +270,8 @@ namespace osu.Game.Database HasReplay = ((IScoreInfo)score).HasReplay, Date = score.Date, PP = score.PP, - BeatmapInfo = realm.All().First(b => b.Hash == score.BeatmapInfo.Hash), - Ruleset = realm.Find(score.Ruleset.ShortName), + BeatmapInfo = beatmap, + Ruleset = ruleset, Rank = score.Rank, HitEvents = score.HitEvents, Passed = score.Passed, From aa93042aa334c0111ddf279c73833c5b1f08add9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Jan 2022 16:28:16 +0900 Subject: [PATCH 497/996] Centralise backup code and also run on skin/settings migrations --- osu.Game/Database/EFToRealmMigrator.cs | 36 +++++++++++++------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index 1de6c25070..23eb794837 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -73,15 +73,7 @@ namespace osu.Game.Database { Logger.Log($"Found {count} beatmaps in EF", LoggingTarget.Database); - if (!hasTakenBackup) - { - string migration = $"before_beatmap_migration_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}"; - - efContextFactory.CreateBackup($"client.{migration}.db"); - realmContextFactory.CreateBackup($"client.{migration}.realm"); - - hasTakenBackup = true; - } + ensureBackup(); // only migrate data if the realm database is empty. // note that this cannot be written as: `realm.All().All(s => s.Protected)`, because realm does not support `.All()`. @@ -201,15 +193,7 @@ namespace osu.Game.Database { Logger.Log($"Found {count} scores in EF", LoggingTarget.Database); - if (!hasTakenBackup) - { - string migration = $"before_score_migration_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}"; - - efContextFactory.CreateBackup($"client.{migration}.db"); - realmContextFactory.CreateBackup($"client.{migration}.realm"); - - hasTakenBackup = true; - } + ensureBackup(); // only migrate data if the realm database is empty. if (realm.All().Any()) @@ -272,6 +256,8 @@ namespace osu.Game.Database if (!existingSkins.Any()) return; + ensureBackup(); + var userSkinChoice = config.GetBindable(OsuSetting.Skin); int.TryParse(userSkinChoice.Value, out int userSkinInt); @@ -342,6 +328,7 @@ namespace osu.Game.Database return; Logger.Log("Beginning settings migration to realm", LoggingTarget.Database); + ensureBackup(); using (var realm = realmContextFactory.CreateContext()) using (var transaction = realm.BeginWrite()) @@ -377,5 +364,18 @@ namespace osu.Game.Database private string? getRulesetShortNameFromLegacyID(long rulesetId) => efContextFactory.Get().RulesetInfo.FirstOrDefault(r => r.ID == rulesetId)?.ShortName; + + private void ensureBackup() + { + if (!hasTakenBackup) + { + string migration = $"before_final_migration_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}"; + + efContextFactory.CreateBackup($"client.{migration}.db"); + realmContextFactory.CreateBackup($"client.{migration}.realm"); + + hasTakenBackup = true; + } + } } } From fad66d7682323f8fbee433c5fa9a4b9a2083a547 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Jan 2022 16:31:27 +0900 Subject: [PATCH 498/996] Backup collections alongside main databases when migrating to realm --- osu.Game/Database/EFToRealmMigrator.cs | 10 +++++++++- osu.Game/OsuGameBase.cs | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index 23eb794837..4c5dfe79cf 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -2,9 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.IO; using System.Linq; using Microsoft.EntityFrameworkCore; using osu.Framework.Logging; +using osu.Framework.Platform; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Models; @@ -22,14 +24,16 @@ namespace osu.Game.Database private readonly DatabaseContextFactory efContextFactory; private readonly RealmContextFactory realmContextFactory; private readonly OsuConfigManager config; + private readonly Storage storage; private bool hasTakenBackup; - public EFToRealmMigrator(DatabaseContextFactory efContextFactory, RealmContextFactory realmContextFactory, OsuConfigManager config) + public EFToRealmMigrator(DatabaseContextFactory efContextFactory, RealmContextFactory realmContextFactory, OsuConfigManager config, Storage storage) { this.efContextFactory = efContextFactory; this.realmContextFactory = realmContextFactory; this.config = config; + this.storage = storage; } public void Run() @@ -374,6 +378,10 @@ namespace osu.Game.Database efContextFactory.CreateBackup($"client.{migration}.db"); realmContextFactory.CreateBackup($"client.{migration}.realm"); + using (var source = storage.GetStream("collection.db")) + using (var destination = storage.GetStream($"collection.{migration}.db", FileAccess.Write, FileMode.CreateNew)) + source.CopyTo(destination); + hasTakenBackup = true; } } diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index b24fdf2bfe..fb09dac1b1 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -195,7 +195,7 @@ namespace osu.Game // A non-null context factory means there's still content to migrate. if (efContextFactory != null) - new EFToRealmMigrator(efContextFactory, realmFactory, LocalConfig).Run(); + new EFToRealmMigrator(efContextFactory, realmFactory, LocalConfig, Storage).Run(); dependencies.CacheAs(Storage); From 0d708efb734571e8c2617b830b07c8406cdf5e31 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Wed, 19 Jan 2022 15:33:33 +0800 Subject: [PATCH 499/996] Split off `PerformanceBreakdown` and its own calculation logic --- .../Difficulty/PerformanceBreakdown.cs | 21 +++++ .../PerformanceBreakdownCalculator.cs | 90 +++++++++++++++++++ .../Statistics/PerformanceStatistic.cs | 80 +---------------- .../Statistics/PerformanceStatisticTooltip.cs | 2 - 4 files changed, 115 insertions(+), 78 deletions(-) create mode 100644 osu.Game/Rulesets/Difficulty/PerformanceBreakdown.cs create mode 100644 osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs diff --git a/osu.Game/Rulesets/Difficulty/PerformanceBreakdown.cs b/osu.Game/Rulesets/Difficulty/PerformanceBreakdown.cs new file mode 100644 index 0000000000..273d8613c5 --- /dev/null +++ b/osu.Game/Rulesets/Difficulty/PerformanceBreakdown.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. + +namespace osu.Game.Rulesets.Difficulty +{ + /// + /// Data for generating a performance breakdown by comparing performance to a perfect play. + /// + public class PerformanceBreakdown + { + /// + /// Actual gameplay performance. + /// + public PerformanceAttributes Performance { get; set; } + + /// + /// Performance of a perfect play for comparison. + /// + public PerformanceAttributes PerfectPerformance { get; set; } + } +} diff --git a/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs b/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs new file mode 100644 index 0000000000..fad7937a99 --- /dev/null +++ b/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs @@ -0,0 +1,90 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using JetBrains.Annotations; +using osu.Framework.Extensions; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; + +namespace osu.Game.Rulesets.Difficulty +{ + public class PerformanceBreakdownCalculator + { + private readonly BeatmapManager beatmapManager; + private readonly BeatmapDifficultyCache difficultyCache; + private readonly ScorePerformanceCache performanceCache; + + public PerformanceBreakdownCalculator(BeatmapManager beatmapManager, BeatmapDifficultyCache difficultyCache, ScorePerformanceCache performanceCache) + { + this.beatmapManager = beatmapManager; + this.difficultyCache = difficultyCache; + this.performanceCache = performanceCache; + } + + [ItemCanBeNull] + public async Task CalculateAsync(ScoreInfo score, CancellationToken cancellationToken = default) + { + PerformanceAttributes performance = await performanceCache.CalculatePerformanceAsync(score, cancellationToken).ConfigureAwait(false); + + ScoreInfo perfectScore = await getPerfectScore(score, cancellationToken).ConfigureAwait(false); + if (perfectScore == null) + return null; + + PerformanceAttributes perfectPerformance = await performanceCache.CalculatePerformanceAsync(perfectScore, cancellationToken).ConfigureAwait(false); + + return new PerformanceBreakdown { Performance = performance, PerfectPerformance = perfectPerformance }; + } + + [ItemCanBeNull] + private Task getPerfectScore(ScoreInfo score, CancellationToken cancellationToken = default) + { + return Task.Factory.StartNew(() => + { + IBeatmap beatmap = beatmapManager.GetWorkingBeatmap(score.BeatmapInfo).GetPlayableBeatmap(score.Ruleset, score.Mods); + ScoreInfo perfectPlay = score.DeepClone(); + perfectPlay.Accuracy = 1; + perfectPlay.Passed = true; + + // calculate max combo + var difficulty = difficultyCache.GetDifficultyAsync( + beatmap.BeatmapInfo, + score.Ruleset, + score.Mods, + cancellationToken + ).GetResultSafely(); + + if (difficulty == null) + return null; + + perfectPlay.MaxCombo = difficulty.Value.MaxCombo; + + // create statistics assuming all hit objects have perfect hit result + var statistics = beatmap.HitObjects + .Select(ho => ho.CreateJudgement().MaxResult) + .GroupBy(hr => hr, (hr, list) => (hitResult: hr, count: list.Count())) + .ToDictionary(pair => pair.hitResult, pair => pair.count); + perfectPlay.Statistics = statistics; + + // calculate total score + ScoreProcessor scoreProcessor = score.Ruleset.CreateInstance().CreateScoreProcessor(); + perfectPlay.TotalScore = (long)scoreProcessor.GetImmediateScore(ScoringMode.Standardised, perfectPlay.MaxCombo, statistics); + + // compute rank achieved + // default to SS, then adjust the rank with mods + perfectPlay.Rank = ScoreRank.X; + + foreach (IApplicableToScoreProcessor mod in perfectPlay.Mods.OfType()) + { + perfectPlay.Rank = mod.AdjustRank(perfectPlay.Rank, 1); + } + + return perfectPlay; + }, cancellationToken); + } + } +} diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs index f00eb9d71f..f934dd1836 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs @@ -2,9 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Linq; using System.Threading; -using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions; @@ -13,13 +11,11 @@ using osu.Framework.Graphics.Cursor; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Difficulty; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; namespace osu.Game.Screens.Ranking.Expanded.Statistics { - public class PerformanceStatistic : StatisticDisplay, IHasCustomTooltip + public class PerformanceStatistic : StatisticDisplay, IHasCustomTooltip { private readonly ScoreInfo score; @@ -47,64 +43,9 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics [BackgroundDependencyLoader] private void load() { - Task.WhenAll( - // actual performance - performanceCache.CalculatePerformanceAsync(score, cancellationTokenSource.Token), - // performance for a perfect play - getPerfectPerformance(score) - ).ContinueWith(attr => - { - PerformanceAttributes[] result = attr.GetResultSafely(); - setPerformanceValue(new PerformanceBreakdown { Performance = result[0], PerfectPerformance = result[1] }); - }); - } - - private async Task getPerfectPerformance(ScoreInfo originalScore) - { - ScoreInfo perfectScore = await getPerfectScore(originalScore).ConfigureAwait(false); - return await performanceCache.CalculatePerformanceAsync(perfectScore, cancellationTokenSource.Token).ConfigureAwait(false); - } - - private Task getPerfectScore(ScoreInfo originalScore) - { - return Task.Factory.StartNew(() => - { - var beatmap = beatmapManager.GetWorkingBeatmap(originalScore.BeatmapInfo).GetPlayableBeatmap(originalScore.Ruleset, originalScore.Mods); - ScoreInfo perfectPlay = originalScore.DeepClone(); - perfectPlay.Accuracy = 1; - perfectPlay.Passed = true; - - // create statistics assuming all hit objects have perfect hit result - var statistics = beatmap.HitObjects - .Select(ho => ho.CreateJudgement().MaxResult) - .GroupBy(hr => hr, (hr, list) => (hitResult: hr, count: list.Count())) - .ToDictionary(pair => pair.hitResult, pair => pair.count); - perfectPlay.Statistics = statistics; - - // calculate max combo - var difficulty = difficultyCache.GetDifficultyAsync( - beatmap.BeatmapInfo, - originalScore.Ruleset, - originalScore.Mods, - cancellationTokenSource.Token - ).GetResultSafely(); - perfectPlay.MaxCombo = difficulty?.MaxCombo ?? originalScore.MaxCombo; - - // calculate total score - ScoreProcessor scoreProcessor = originalScore.Ruleset.CreateInstance().CreateScoreProcessor(); - perfectPlay.TotalScore = (long)scoreProcessor.GetImmediateScore(ScoringMode.Standardised, perfectPlay.MaxCombo, statistics); - - // compute rank achieved - // default to SS, then adjust the rank with mods - perfectPlay.Rank = ScoreRank.X; - - foreach (IApplicableToScoreProcessor mod in perfectPlay.Mods.OfType()) - { - perfectPlay.Rank = mod.AdjustRank(perfectPlay.Rank, 1); - } - - return perfectPlay; - }, cancellationTokenSource.Token); + new PerformanceBreakdownCalculator(beatmapManager, difficultyCache, performanceCache) + .CalculateAsync(score, cancellationTokenSource.Token) + .ContinueWith(t => setPerformanceValue(t.GetResultSafely())); } private void setPerformanceValue(PerformanceBreakdown breakdown) @@ -137,18 +78,5 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics public ITooltip GetCustomTooltip() => new PerformanceStatisticTooltip(); public PerformanceBreakdown TooltipContent { get; private set; } - - public class PerformanceBreakdown - { - /// - /// Actual gameplay performance. - /// - public PerformanceAttributes Performance { get; set; } - - /// - /// Performance of a perfect play for comparison. - /// - public PerformanceAttributes PerfectPerformance { get; set; } - } } } diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs index 528ccafd41..930bfba96a 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs @@ -19,8 +19,6 @@ using osuTK.Graphics; namespace osu.Game.Screens.Ranking.Expanded.Statistics { - using static PerformanceStatistic; - public class PerformanceStatisticTooltip : VisibilityContainer, ITooltip { private readonly Box background; From 200fcb6f833e4cbbef4bcb485e051859b6d793cb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Jan 2022 16:59:49 +0900 Subject: [PATCH 500/996] Detach beatmap set before checking hidden state --- osu.Game/Screens/Select/BeatmapCarousel.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index dc67a4eb45..ea10e47cff 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -677,11 +677,11 @@ namespace osu.Game.Screens.Select private CarouselBeatmapSet createCarouselSet(BeatmapSetInfo beatmapSet) { + beatmapSet = beatmapSet.Detach(); + if (beatmapSet.Beatmaps.All(b => b.Hidden)) return null; - beatmapSet = beatmapSet.Detach(); - var set = new CarouselBeatmapSet(beatmapSet) { GetRecommendedBeatmap = beatmaps => GetRecommendedBeatmap?.Invoke(beatmaps) From dd42c89260c26d1cd719242c78388217f23a2f6d Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Wed, 19 Jan 2022 16:08:45 +0800 Subject: [PATCH 501/996] Feed more info to the temporary score processor for more accurate total score --- osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs b/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs index fad7937a99..17cb1de303 100644 --- a/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs @@ -72,6 +72,8 @@ namespace osu.Game.Rulesets.Difficulty // calculate total score ScoreProcessor scoreProcessor = score.Ruleset.CreateInstance().CreateScoreProcessor(); + scoreProcessor.HighestCombo.Value = perfectPlay.MaxCombo; + scoreProcessor.Mods.Value = perfectPlay.Mods; perfectPlay.TotalScore = (long)scoreProcessor.GetImmediateScore(ScoringMode.Standardised, perfectPlay.MaxCombo, statistics); // compute rank achieved From 2789986699b4f4fc77da1fe946af96e617cde4f6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Jan 2022 17:47:46 +0900 Subject: [PATCH 502/996] Use asynchronous loading for beatmap carousel again --- osu.Game/Screens/Select/BeatmapCarousel.cs | 34 ++++++++++++++++++++-- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index ea10e47cff..7f9b19443a 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -181,6 +181,11 @@ namespace osu.Game.Screens.Select RightClickScrollingEnabled.ValueChanged += enabled => Scroll.RightMouseScrollbar = enabled.NewValue; RightClickScrollingEnabled.TriggerChange(); + + using (var realm = realmFactory.CreateContext()) + { + loadBeatmapSets(getBeatmapSets(realm)); + } } [Resolved] @@ -190,7 +195,7 @@ namespace osu.Game.Screens.Select { base.LoadComplete(); - subscriptionSets = realmFactory.Context.All().Where(s => !s.DeletePending && !s.Protected).QueryAsyncWithNotifications(beatmapSetsChanged); + subscriptionSets = getBeatmapSets(realmFactory.Context).QueryAsyncWithNotifications(beatmapSetsChanged); subscriptionBeatmaps = realmFactory.Context.All().Where(b => !b.Hidden).QueryAsyncWithNotifications(beatmapsChanged); // Can't use main subscriptions because we can't lookup deleted indices. @@ -220,8 +225,29 @@ namespace osu.Game.Screens.Select if (changes == null) { - // initial load - loadBeatmapSets(sender); + // During initial population, we must manually account for the fact that our original query was done on an async thread. + // Since then, there may have been imports or deletions. + // Here we manually catch up on any changes. + var populatedSets = new HashSet(); + foreach (var s in beatmapSets) + populatedSets.Add(s.BeatmapSet.ID); + + var realmSets = new HashSet(); + foreach (var s in sender) + realmSets.Add(s.ID); + + foreach (var s in realmSets) + { + if (!populatedSets.Contains(s)) + UpdateBeatmapSet(realmFactory.Context.Find(s)); + } + + foreach (var s in populatedSets) + { + if (!realmSets.Contains(s)) + RemoveBeatmapSet(realmFactory.Context.Find(s)); + } + return; } @@ -242,6 +268,8 @@ namespace osu.Game.Screens.Select UpdateBeatmapSet(sender[i].BeatmapSet); } + private IRealmCollection getBeatmapSets(Realm realm) => realm.All().Where(s => !s.DeletePending && !s.Protected).AsRealmCollection(); + public void RemoveBeatmapSet(BeatmapSetInfo beatmapSet) => Schedule(() => { var existingSet = beatmapSets.FirstOrDefault(b => b.BeatmapSet.Equals(beatmapSet)); From 6c46fd6931a73f3329964c2e97ba5b650832edc5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 Jan 2022 00:14:00 +0900 Subject: [PATCH 503/996] Fix some failing tests due to realm beatmaps overwriting test beatmaps --- osu.Game/Screens/Select/BeatmapCarousel.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 7f9b19443a..2dc9bfba0f 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -182,9 +182,10 @@ namespace osu.Game.Screens.Select RightClickScrollingEnabled.ValueChanged += enabled => Scroll.RightMouseScrollbar = enabled.NewValue; RightClickScrollingEnabled.TriggerChange(); - using (var realm = realmFactory.CreateContext()) + if (!loadedTestBeatmaps) { - loadBeatmapSets(getBeatmapSets(realm)); + using (var realm = realmFactory.CreateContext()) + loadBeatmapSets(getBeatmapSets(realm)); } } From fea4400c030f6eabad7bf476a0c536e473fa07db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 19 Jan 2022 18:49:37 +0100 Subject: [PATCH 504/996] Remove unused using directive --- osu.Game.Tests/NonVisual/ScoreInfoTest.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/NonVisual/ScoreInfoTest.cs b/osu.Game.Tests/NonVisual/ScoreInfoTest.cs index ea2f50f3bb..e0acc6d8db 100644 --- a/osu.Game.Tests/NonVisual/ScoreInfoTest.cs +++ b/osu.Game.Tests/NonVisual/ScoreInfoTest.cs @@ -6,7 +6,6 @@ using osu.Game.Online.API; using osu.Game.Rulesets.Mania; using osu.Game.Rulesets.Mania.Mods; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; From a27a3b308cd204cbaddd52f7fbaf20020925d635 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 Jan 2022 04:34:00 +0900 Subject: [PATCH 505/996] Remove duplicate setters --- osu.Game/Database/EFToRealmMigrator.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index 5e49a44733..717d18b9e5 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -263,15 +263,12 @@ namespace osu.Game.Database OnlineID = score.OnlineID ?? -1, ModsJson = score.ModsJson, StatisticsJson = score.StatisticsJson, - User = score.User, TotalScore = score.TotalScore, MaxCombo = score.MaxCombo, Accuracy = score.Accuracy, HasReplay = ((IScoreInfo)score).HasReplay, Date = score.Date, PP = score.PP, - BeatmapInfo = beatmap, - Ruleset = ruleset, Rank = score.Rank, HitEvents = score.HitEvents, Passed = score.Passed, From d925e44b40c087b1e3ca0f0c1e6030f7b372513b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 19 Jan 2022 22:02:01 +0100 Subject: [PATCH 506/996] Add test coverage for beatmap card expanded content clipping --- .../Online/TestSceneBeatmapListingOverlay.cs | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs index d0e3340f2a..a056e0cd2c 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs @@ -8,6 +8,7 @@ using NUnit.Framework; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Game.Beatmaps.Drawables.Cards; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Online.API.Requests; @@ -292,6 +293,33 @@ namespace osu.Game.Tests.Visual.Online noPlaceholderShown(); } + [Test] + public void TestExpandedCardContentNotClipped() + { + AddAssert("is visible", () => overlay.State.Value == Visibility.Visible); + + AddStep("show result with many difficulties", () => + { + var beatmapSet = CreateAPIBeatmapSet(Ruleset.Value); + beatmapSet.Beatmaps = Enumerable.Repeat(beatmapSet.Beatmaps.First(), 100).ToArray(); + fetchFor(beatmapSet); + }); + assertAllCardsOfType(1); + + AddStep("hover extra info row", () => + { + var difficultyArea = this.ChildrenOfType().Single(); + InputManager.MoveMouseTo(difficultyArea); + }); + AddUntilStep("wait for expanded", () => this.ChildrenOfType().Single().Expanded.Value); + AddAssert("expanded content not clipped", () => + { + var cardContainer = this.ChildrenOfType>().Single().Parent; + var expandedContent = this.ChildrenOfType().Single(); + return expandedContent.ScreenSpaceDrawQuad.GetVertices().ToArray().All(v => cardContainer.ScreenSpaceDrawQuad.Contains(v)); + }); + } + private static int searchCount; private void fetchFor(params APIBeatmapSet[] beatmaps) From 33ab356dc53439c5a69f3d322c3e011e5069f11c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 19 Jan 2022 22:10:05 +0100 Subject: [PATCH 507/996] Fix expanded card content being clipped on beatmap listing overlay --- osu.Game/Overlays/BeatmapListingOverlay.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index a8e5201aa3..fbed234cc7 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -187,8 +187,10 @@ namespace osu.Game.Overlays Alpha = 0, Margin = new MarginPadding { - Vertical = 15, - Bottom = ExpandedContentScrollContainer.HEIGHT + Top = 15, + // the + 20 adjustment is roughly eyeballed in order to fit all of the expanded content height after it's scaled + // as well as provide visual balance to the top margin. + Bottom = ExpandedContentScrollContainer.HEIGHT + 20 }, ChildrenEnumerable = newCards }; From 77748a5f9308e8434d6e607381eed758c4428000 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 19 Jan 2022 22:21:05 +0100 Subject: [PATCH 508/996] Show scrollbar on expanded card content where applicable --- .../Drawables/Cards/ExpandedContentScrollContainer.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/Cards/ExpandedContentScrollContainer.cs b/osu.Game/Beatmaps/Drawables/Cards/ExpandedContentScrollContainer.cs index edf4c5328c..2e1ad6b516 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/ExpandedContentScrollContainer.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/ExpandedContentScrollContainer.cs @@ -12,16 +12,12 @@ namespace osu.Game.Beatmaps.Drawables.Cards { public const float HEIGHT = 200; - public ExpandedContentScrollContainer() - { - ScrollbarVisible = false; - } - protected override void Update() { base.Update(); Height = Math.Min(Content.DrawHeight, HEIGHT); + ScrollbarVisible = allowScroll; } private bool allowScroll => !Precision.AlmostEquals(DrawSize, Content.DrawSize); From 247c557eaf3a2abe25d693a0a7797d5d4ab0ce3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 19 Jan 2022 22:30:49 +0100 Subject: [PATCH 509/996] Fix expanded content scrollbar inadvertently hiding expanded content --- .../Cards/ExpandedContentScrollContainer.cs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/osu.Game/Beatmaps/Drawables/Cards/ExpandedContentScrollContainer.cs b/osu.Game/Beatmaps/Drawables/Cards/ExpandedContentScrollContainer.cs index 2e1ad6b516..adde72d1e8 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/ExpandedContentScrollContainer.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/ExpandedContentScrollContainer.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Graphics; using osu.Framework.Input.Events; using osu.Framework.Utils; using osu.Game.Graphics.Containers; @@ -12,6 +13,8 @@ namespace osu.Game.Beatmaps.Drawables.Cards { public const float HEIGHT = 200; + protected override ScrollbarContainer CreateScrollbar(Direction direction) => new ExpandedContentScrollbar(direction); + protected override void Update() { base.Update(); @@ -53,5 +56,22 @@ namespace osu.Game.Beatmaps.Drawables.Cards return base.OnScroll(e); } + + private class ExpandedContentScrollbar : OsuScrollbar + { + public ExpandedContentScrollbar(Direction scrollDir) + : base(scrollDir) + { + } + + protected override bool OnHover(HoverEvent e) + { + base.OnHover(e); + // do not handle hover, as handling hover would make the beatmap card's expanded content not-hovered + // and therefore cause it to hide when trying to drag the scroll bar. + // see: `BeatmapCardContent.dropdownContent` and its `Unhovered` handler. + return false; + } + } } } From 4cad5890c608d5b5a8d3497e99a4b029444de668 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 19 Jan 2022 23:12:35 +0100 Subject: [PATCH 510/996] Add test coverage for news sidebar showing wrong headers --- .../Visual/Online/TestSceneNewsSidebar.cs | 76 +++++++++++++++++++ .../Overlays/News/Sidebar/MonthSection.cs | 9 ++- 2 files changed, 83 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsSidebar.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsSidebar.cs index b000553a7b..382d76676a 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneNewsSidebar.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsSidebar.cs @@ -38,6 +38,14 @@ namespace osu.Game.Tests.Visual.Online AddUntilStep("No month sections were created", () => !sidebar.ChildrenOfType().Any()); } + [Test] + public void TestMetadataWithMultipleYears() + { + AddStep("Add data spanning multiple years", () => sidebar.Metadata.Value = metadata_with_multiple_years); + AddUntilStep("2022 month sections exist", () => sidebar.ChildrenOfType().Any(s => s.Year == 2022)); + AddUntilStep("2021 month sections exist", () => sidebar.ChildrenOfType().Any(s => s.Year == 2021)); + } + [Test] public void TestYearsPanelVisibility() { @@ -133,6 +141,74 @@ namespace osu.Game.Tests.Visual.Online NewsPosts = Array.Empty() }; + // see https://osu.ppy.sh/docs/index.html#get-news-listing: + // "NewsPost collections queried by year will also include posts published in November and December of the previous year if the current date is the same year and before April." + private static readonly APINewsSidebar metadata_with_multiple_years = new APINewsSidebar + { + CurrentYear = 2022, + Years = new[] + { + 2022, + 2021, + 2020, + 2019, + 2018, + 2017, + 2016, + 2015, + 2014, + 2013 + }, + NewsPosts = new List + { + new APINewsPost + { + Title = "(Mar 2022) Short title", + PublishedAt = new DateTime(2022, 3, 1) + }, + new APINewsPost + { + Title = "(Mar 2022) Oh boy that's a long post title I wonder if it will break anything", + PublishedAt = new DateTime(2022, 3, 1) + }, + new APINewsPost + { + Title = "(Feb 2022) Medium title, nothing to see here", + PublishedAt = new DateTime(2022, 2, 1) + }, + new APINewsPost + { + Title = "(Feb 2022) Short title", + PublishedAt = new DateTime(2022, 2, 1) + }, + new APINewsPost + { + Title = "(Jan 2022) Oh boy that's a long post title I wonder if it will break anything", + PublishedAt = new DateTime(2022, 1, 1) + }, + new APINewsPost + { + Title = "(Jan 2022) Medium title, nothing to see here", + PublishedAt = new DateTime(2022, 1, 1) + }, + new APINewsPost + { + Title = "(Jan 2022) Short title", + PublishedAt = new DateTime(2022, 1, 1) + }, + new APINewsPost + { + Title = "(Dec 2021) Surprise, the last year's not gone yet", + PublishedAt = new DateTime(2021, 12, 1) + }, + new APINewsPost + { + Title = "(Nov 2021) Same goes for November", + PublishedAt = new DateTime(2021, 11, 1) + } + } + }; + private class TestNewsSidebar : NewsSidebar { public Action YearChanged; diff --git a/osu.Game/Overlays/News/Sidebar/MonthSection.cs b/osu.Game/Overlays/News/Sidebar/MonthSection.cs index 948f312f15..aa83f89689 100644 --- a/osu.Game/Overlays/News/Sidebar/MonthSection.cs +++ b/osu.Game/Overlays/News/Sidebar/MonthSection.cs @@ -24,16 +24,21 @@ namespace osu.Game.Overlays.News.Sidebar { public class MonthSection : CompositeDrawable { + public int Year { get; private set; } + public int Month { get; private set; } + public readonly BindableBool Expanded = new BindableBool(); + private const int animation_duration = 250; private Sample sampleOpen; private Sample sampleClose; - public readonly BindableBool Expanded = new BindableBool(); - public MonthSection(int month, int year, IEnumerable posts) { Debug.Assert(posts.All(p => p.PublishedAt.Month == month && p.PublishedAt.Year == year)); + Year = year; + Month = month; + RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; Masking = true; From b8184a3005ab8f412ecc1790893b533e1beb429f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 19 Jan 2022 23:13:30 +0100 Subject: [PATCH 511/996] Fix news sidebar assuming returned posts are always from given year --- osu.Game/Overlays/News/Sidebar/NewsSidebar.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/News/Sidebar/NewsSidebar.cs b/osu.Game/Overlays/News/Sidebar/NewsSidebar.cs index fe965385d8..829fc5b3eb 100644 --- a/osu.Game/Overlays/News/Sidebar/NewsSidebar.cs +++ b/osu.Game/Overlays/News/Sidebar/NewsSidebar.cs @@ -56,19 +56,17 @@ namespace osu.Game.Overlays.News.Sidebar if (allPosts?.Any() != true) return; - var lookup = metadata.NewValue.NewsPosts.ToLookup(post => post.PublishedAt.Month); + var lookup = metadata.NewValue.NewsPosts.ToLookup(post => (post.PublishedAt.Month, post.PublishedAt.Year)); var keys = lookup.Select(kvp => kvp.Key); - var sortedKeys = keys.OrderByDescending(k => k).ToList(); - - int year = metadata.NewValue.CurrentYear; + var sortedKeys = keys.OrderByDescending(k => k.Year).ThenByDescending(k => k.Month).ToList(); for (int i = 0; i < sortedKeys.Count; i++) { - int month = sortedKeys[i]; - var posts = lookup[month]; + var key = sortedKeys[i]; + var posts = lookup[key]; - monthsFlow.Add(new MonthSection(month, year, posts) + monthsFlow.Add(new MonthSection(key.Month, key.Year, posts) { Expanded = { Value = i == 0 } }); From 261fae68735ef6df0b43c9d1599869972ba18a1c Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 20 Jan 2022 08:39:33 +0800 Subject: [PATCH 512/996] Move checks out of PopIn() --- .../Ranking/Expanded/Statistics/PerformanceStatistic.cs | 4 +++- .../Expanded/Statistics/PerformanceStatisticTooltip.cs | 7 +------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs index f934dd1836..bda147cf22 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using System.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -50,7 +51,8 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics private void setPerformanceValue(PerformanceBreakdown breakdown) { - if (breakdown != null) + // Don't display the tooltip if "Total" is the only item + if (breakdown != null && breakdown.Performance.GetAttributesForDisplay().Count() > 1) { TooltipContent = breakdown; performance.Value = (int)Math.Round(breakdown.Performance.Total, MidpointRounding.AwayFromZero); diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs index 930bfba96a..44e5c366bb 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs @@ -56,12 +56,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics textColour = colours.BlueLighter; } - protected override void PopIn() - { - // Don't display the tooltip if "Total" is the only item - if (currentPerformance.Performance.GetAttributesForDisplay().Count() > 1) - this.FadeIn(200, Easing.OutQuint); - } + protected override void PopIn() => this.FadeIn(200, Easing.OutQuint); protected override void PopOut() => this.FadeOut(200, Easing.OutQuint); From 42d904acee23490e51086cbae21fd9bf3aff9045 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 20 Jan 2022 12:50:28 +0800 Subject: [PATCH 513/996] Remove blocking calls and add back Task.WhenAll --- .../PerformanceBreakdownCalculator.cs | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs b/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs index 17cb1de303..3c9c00ff3b 100644 --- a/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs @@ -5,7 +5,6 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; -using osu.Framework.Extensions; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; @@ -29,21 +28,30 @@ namespace osu.Game.Rulesets.Difficulty [ItemCanBeNull] public async Task CalculateAsync(ScoreInfo score, CancellationToken cancellationToken = default) { - PerformanceAttributes performance = await performanceCache.CalculatePerformanceAsync(score, cancellationToken).ConfigureAwait(false); + PerformanceAttributes[] performanceArray = await Task.WhenAll( + // compute actual performance + performanceCache.CalculatePerformanceAsync(score, cancellationToken), + // compute performance for perfect play + getPerfectPerformance(score, cancellationToken) + ).ConfigureAwait(false); + return new PerformanceBreakdown { Performance = performanceArray[0], PerfectPerformance = performanceArray[1] }; + } + + [ItemCanBeNull] + private async Task getPerfectPerformance(ScoreInfo score, CancellationToken cancellationToken = default) + { ScoreInfo perfectScore = await getPerfectScore(score, cancellationToken).ConfigureAwait(false); if (perfectScore == null) return null; - PerformanceAttributes perfectPerformance = await performanceCache.CalculatePerformanceAsync(perfectScore, cancellationToken).ConfigureAwait(false); - - return new PerformanceBreakdown { Performance = performance, PerfectPerformance = perfectPerformance }; + return await performanceCache.CalculatePerformanceAsync(perfectScore, cancellationToken).ConfigureAwait(false); } [ItemCanBeNull] private Task getPerfectScore(ScoreInfo score, CancellationToken cancellationToken = default) { - return Task.Factory.StartNew(() => + return Task.Run(async () => { IBeatmap beatmap = beatmapManager.GetWorkingBeatmap(score.BeatmapInfo).GetPlayableBeatmap(score.Ruleset, score.Mods); ScoreInfo perfectPlay = score.DeepClone(); @@ -51,12 +59,12 @@ namespace osu.Game.Rulesets.Difficulty perfectPlay.Passed = true; // calculate max combo - var difficulty = difficultyCache.GetDifficultyAsync( + var difficulty = await difficultyCache.GetDifficultyAsync( beatmap.BeatmapInfo, score.Ruleset, score.Mods, cancellationToken - ).GetResultSafely(); + ).ConfigureAwait(false); if (difficulty == null) return null; From 6c97fbd3f28f8374822e41950ca293a3d5a1e62f Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 20 Jan 2022 13:06:00 +0800 Subject: [PATCH 514/996] Calculate perfect performance without caching --- .../PerformanceBreakdownCalculator.cs | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs b/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs index 3c9c00ff3b..5cf63d0102 100644 --- a/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs @@ -39,20 +39,11 @@ namespace osu.Game.Rulesets.Difficulty } [ItemCanBeNull] - private async Task getPerfectPerformance(ScoreInfo score, CancellationToken cancellationToken = default) - { - ScoreInfo perfectScore = await getPerfectScore(score, cancellationToken).ConfigureAwait(false); - if (perfectScore == null) - return null; - - return await performanceCache.CalculatePerformanceAsync(perfectScore, cancellationToken).ConfigureAwait(false); - } - - [ItemCanBeNull] - private Task getPerfectScore(ScoreInfo score, CancellationToken cancellationToken = default) + private Task getPerfectPerformance(ScoreInfo score, CancellationToken cancellationToken = default) { return Task.Run(async () => { + Ruleset ruleset = score.Ruleset.CreateInstance(); IBeatmap beatmap = beatmapManager.GetWorkingBeatmap(score.BeatmapInfo).GetPlayableBeatmap(score.Ruleset, score.Mods); ScoreInfo perfectPlay = score.DeepClone(); perfectPlay.Accuracy = 1; @@ -79,7 +70,7 @@ namespace osu.Game.Rulesets.Difficulty perfectPlay.Statistics = statistics; // calculate total score - ScoreProcessor scoreProcessor = score.Ruleset.CreateInstance().CreateScoreProcessor(); + ScoreProcessor scoreProcessor = ruleset.CreateScoreProcessor(); scoreProcessor.HighestCombo.Value = perfectPlay.MaxCombo; scoreProcessor.Mods.Value = perfectPlay.Mods; perfectPlay.TotalScore = (long)scoreProcessor.GetImmediateScore(ScoringMode.Standardised, perfectPlay.MaxCombo, statistics); @@ -93,7 +84,9 @@ namespace osu.Game.Rulesets.Difficulty perfectPlay.Rank = mod.AdjustRank(perfectPlay.Rank, 1); } - return perfectPlay; + // calculate performance for this perfect score + // ScorePerformanceCache is not used to avoid caching multiple copies of essentially identical perfect performance attributes + return ruleset.CreatePerformanceCalculator(difficulty.Value.Attributes, perfectPlay)?.Calculate(); }, cancellationToken); } } From 1dabf6c8a52cf176a2acae19e4b571b599b93347 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 Jan 2022 16:39:42 +0900 Subject: [PATCH 515/996] Fix `BeatmapCarousel` signalling it is finished loading before catching up on realm changes --- osu.Game/Screens/Select/BeatmapCarousel.cs | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 2dc9bfba0f..437567d4b2 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -126,14 +126,8 @@ namespace osu.Game.Screens.Select applyActiveCriteria(false); - // Run on late scheduler want to ensure this runs after all pending UpdateBeatmapSet / RemoveBeatmapSet operations are run. - SchedulerAfterChildren.Add(() => - { - BeatmapSetsChanged?.Invoke(); - BeatmapSetsLoaded = true; - - itemsCache.Invalidate(); - }); + if (loadedTestBeatmaps) + signalBeatmapsLoaded(); } private readonly List visibleItems = new List(); @@ -249,6 +243,7 @@ namespace osu.Game.Screens.Select RemoveBeatmapSet(realmFactory.Context.Find(s)); } + signalBeatmapsLoaded(); return; } @@ -547,6 +542,16 @@ namespace osu.Game.Screens.Select } } + private void signalBeatmapsLoaded() + { + Debug.Assert(BeatmapSetsLoaded == false); + + BeatmapSetsChanged?.Invoke(); + BeatmapSetsLoaded = true; + + itemsCache.Invalidate(); + } + private float? scrollTarget; /// From 3faf980fed86b494f4c6a8337fa8c6480a58bc13 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 Jan 2022 22:51:45 +0900 Subject: [PATCH 516/996] Avoid constructor overhead for realm `BeatmapInfo` parameterless constructor --- osu.Game/Beatmaps/BeatmapInfo.cs | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index cddd7e9b30..1ea1c8ab4c 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -26,37 +26,35 @@ namespace osu.Game.Beatmaps public class BeatmapInfo : RealmObject, IHasGuidPrimaryKey, IBeatmapInfo, IEquatable { [PrimaryKey] - public Guid ID { get; set; } = Guid.NewGuid(); + public Guid ID { get; set; } public string DifficultyName { get; set; } = string.Empty; - public RulesetInfo Ruleset { get; set; } + public RulesetInfo Ruleset { get; set; } = null!; - public BeatmapDifficulty Difficulty { get; set; } + public BeatmapDifficulty Difficulty { get; set; } = null!; - public BeatmapMetadata Metadata { get; set; } + public BeatmapMetadata Metadata { get; set; } = null!; [Backlink(nameof(ScoreInfo.BeatmapInfo))] public IQueryable Scores { get; } = null!; - public BeatmapInfo(RulesetInfo ruleset, BeatmapDifficulty difficulty, BeatmapMetadata metadata) + public BeatmapInfo(RulesetInfo? ruleset = null, BeatmapDifficulty? difficulty = null, BeatmapMetadata? metadata = null) { - Ruleset = ruleset; - Difficulty = difficulty; - Metadata = metadata; - } - - [UsedImplicitly] - public BeatmapInfo() // TODO: consider removing this and migrating all usages to ctor with parameters. - { - Ruleset = new RulesetInfo + ID = Guid.NewGuid(); + Ruleset = ruleset ?? new RulesetInfo { OnlineID = 0, ShortName = @"osu", Name = @"null placeholder ruleset" }; - Difficulty = new BeatmapDifficulty(); - Metadata = new BeatmapMetadata(); + Difficulty = difficulty ?? new BeatmapDifficulty(); + Metadata = metadata ?? new BeatmapMetadata(); + } + + [UsedImplicitly] + private BeatmapInfo() + { } public BeatmapSetInfo? BeatmapSet { get; set; } From 3c852e6d025d9fc3503c28f266ee66c9d99e5d77 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 Jan 2022 16:43:51 +0900 Subject: [PATCH 517/996] Avoid constructor overhead for realm `ScoreInfo` parameterless constructor --- osu.Game/Scoring/ScoreInfo.cs | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index cfc37e956d..a28e16450f 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -29,11 +29,11 @@ namespace osu.Game.Scoring public class ScoreInfo : RealmObject, IHasGuidPrimaryKey, IHasRealmFiles, ISoftDelete, IEquatable, IScoreInfo { [PrimaryKey] - public Guid ID { get; set; } = Guid.NewGuid(); + public Guid ID { get; set; } - public BeatmapInfo BeatmapInfo { get; set; } + public BeatmapInfo BeatmapInfo { get; set; } = null!; - public RulesetInfo Ruleset { get; set; } + public RulesetInfo Ruleset { get; set; } = null!; public IList Files { get; } = null!; @@ -57,7 +57,7 @@ namespace osu.Game.Scoring public long OnlineID { get; set; } = -1; [MapTo("User")] - public RealmUser RealmUser { get; set; } + public RealmUser RealmUser { get; set; } = null!; [MapTo("Mods")] public string ModsJson { get; set; } = string.Empty; @@ -65,19 +65,17 @@ namespace osu.Game.Scoring [MapTo("Statistics")] public string StatisticsJson { get; set; } = string.Empty; - public ScoreInfo(BeatmapInfo beatmap, RulesetInfo ruleset, RealmUser realmUser) + public ScoreInfo(BeatmapInfo? beatmap = null, RulesetInfo? ruleset = null, RealmUser? realmUser = null) { - Ruleset = ruleset; - BeatmapInfo = beatmap; - RealmUser = realmUser; + Ruleset = ruleset ?? new RulesetInfo(); + BeatmapInfo = beatmap ?? new BeatmapInfo(); + RealmUser = realmUser ?? new RealmUser(); + ID = Guid.NewGuid(); } - [UsedImplicitly] - public ScoreInfo() // TODO: consider removing this and migrating all usages to ctor with parameters. + [UsedImplicitly] // Realm + private ScoreInfo() { - Ruleset = new RulesetInfo(); - RealmUser = new RealmUser(); - BeatmapInfo = new BeatmapInfo(); } // TODO: this is a bit temporary to account for the fact that this class is used to ferry API user data to certain UI components. From ccddf9b47d94d9f7a3b00aa0e05c577f886e13ae Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 Jan 2022 16:49:39 +0900 Subject: [PATCH 518/996] Avoid constructor overhead for realm `BeatmapSetInfo` parameterless constructor --- osu.Game/Beatmaps/BeatmapSetInfo.cs | 16 +++++++++++++++- osu.Game/Database/RealmObjectExtensions.cs | 3 +++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index a934d1a2e3..3cda111e97 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; using osu.Framework.Testing; using osu.Game.Database; using osu.Game.Extensions; @@ -19,7 +20,7 @@ namespace osu.Game.Beatmaps public class BeatmapSetInfo : RealmObject, IHasGuidPrimaryKey, IHasRealmFiles, ISoftDelete, IEquatable, IBeatmapSetInfo { [PrimaryKey] - public Guid ID { get; set; } = Guid.NewGuid(); + public Guid ID { get; set; } [Indexed] public int OnlineID { get; set; } = -1; @@ -57,6 +58,19 @@ namespace osu.Game.Beatmaps public double MaxBPM => Beatmaps.Count == 0 ? 0 : Beatmaps.Max(b => b.BPM); + public BeatmapSetInfo(IEnumerable? beatmaps = null) + : this() + { + ID = Guid.NewGuid(); + if (beatmaps != null) + Beatmaps.AddRange(beatmaps); + } + + [UsedImplicitly] // Realm + private BeatmapSetInfo() + { + } + /// /// Returns the storage path for the file in this beatmapset with the given filename, if any exists, otherwise null. /// The path returned is relative to the user file storage. diff --git a/osu.Game/Database/RealmObjectExtensions.cs b/osu.Game/Database/RealmObjectExtensions.cs index 746a43fd37..c25aeab336 100644 --- a/osu.Game/Database/RealmObjectExtensions.cs +++ b/osu.Game/Database/RealmObjectExtensions.cs @@ -48,6 +48,7 @@ namespace osu.Game.Database copyChangesToRealm(s.Metadata, d.Metadata); }); c.CreateMap() + .ConstructUsing(_ => new BeatmapSetInfo(null)) .ForMember(s => s.Beatmaps, cc => cc.Ignore()) .AfterMap((s, d) => { @@ -77,6 +78,7 @@ namespace osu.Game.Database applyCommonConfiguration(c); c.CreateMap() + .ConstructUsing(_ => new BeatmapSetInfo(null)) .MaxDepth(2) .AfterMap((s, d) => { @@ -109,6 +111,7 @@ namespace osu.Game.Database applyCommonConfiguration(c); c.CreateMap() + .ConstructUsing(_ => new BeatmapSetInfo(null)) .MaxDepth(2) .ForMember(b => b.Files, cc => cc.Ignore()) .AfterMap((s, d) => From deb108816cd17e92f5700391e65bd03e34aa7e70 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 Jan 2022 17:09:31 +0900 Subject: [PATCH 519/996] Fix some regressions in json output (we need to make all these explicit instead) --- osu.Game/Beatmaps/BeatmapInfo.cs | 1 + osu.Game/Beatmaps/BeatmapSetInfo.cs | 2 ++ 2 files changed, 3 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 1ea1c8ab4c..96254295a6 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -36,6 +36,7 @@ namespace osu.Game.Beatmaps public BeatmapMetadata Metadata { get; set; } = null!; + [JsonIgnore] [Backlink(nameof(ScoreInfo.BeatmapInfo))] public IQueryable Scores { get; } = null!; diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index 3cda111e97..9a4207d5cf 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using JetBrains.Annotations; +using Newtonsoft.Json; using osu.Framework.Testing; using osu.Game.Database; using osu.Game.Extensions; @@ -27,6 +28,7 @@ namespace osu.Game.Beatmaps public DateTimeOffset DateAdded { get; set; } + [JsonIgnore] public IBeatmapMetadataInfo Metadata => Beatmaps.FirstOrDefault()?.Metadata ?? new BeatmapMetadata(); public IList Beatmaps { get; } = null!; From 6c10531df2b5ddd70c1c9ecf095b5d4a8617f7ee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 Jan 2022 17:14:51 +0900 Subject: [PATCH 520/996] Avoid constructor overhead for realm `BeatmapMetadata` parameterless constructor --- osu.Game/Beatmaps/BeatmapMetadata.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs index a3385e3abe..cb38373bd3 100644 --- a/osu.Game/Beatmaps/BeatmapMetadata.cs +++ b/osu.Game/Beatmaps/BeatmapMetadata.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using JetBrains.Annotations; using Newtonsoft.Json; using osu.Framework.Testing; using osu.Game.Models; @@ -27,7 +28,7 @@ namespace osu.Game.Beatmaps [JsonProperty("artist_unicode")] public string ArtistUnicode { get; set; } = string.Empty; - public RealmUser Author { get; set; } = new RealmUser(); // TODO: not sure we want to initialise this only to have it overwritten by retrieval. + public RealmUser Author { get; set; } = null!; public string Source { get; set; } = string.Empty; @@ -43,6 +44,16 @@ namespace osu.Game.Beatmaps public string AudioFile { get; set; } = string.Empty; public string BackgroundFile { get; set; } = string.Empty; + public BeatmapMetadata(RealmUser? user = null) + { + Author = new RealmUser(); + } + + [UsedImplicitly] // Realm + private BeatmapMetadata() + { + } + IUser IBeatmapMetadataInfo.Author => Author; public override string ToString() => this.GetDisplayTitle(); From 70cc03fe4304b1c982178bf34601718503dd3547 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 Jan 2022 17:18:53 +0900 Subject: [PATCH 521/996] Avoid constructor overhead for realm `RealmKeyBinding` parameterless constructor --- .../Database/TestRealmKeyBindingStore.cs | 20 +++---------------- osu.Game/Input/Bindings/RealmKeyBinding.cs | 20 +++++++++++++++++-- osu.Game/Input/RealmKeyBindingStore.cs | 8 +------- 3 files changed, 22 insertions(+), 26 deletions(-) diff --git a/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs index f05d9ab3dc..e3c1d42667 100644 --- a/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs +++ b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs @@ -63,23 +63,9 @@ namespace osu.Game.Tests.Database using (var realm = realmContextFactory.CreateContext()) using (var transaction = realm.BeginWrite()) { - realm.Add(new RealmKeyBinding - { - Action = GlobalAction.Back, - KeyCombination = new KeyCombination(InputKey.A) - }); - - realm.Add(new RealmKeyBinding - { - Action = GlobalAction.Back, - KeyCombination = new KeyCombination(InputKey.S) - }); - - realm.Add(new RealmKeyBinding - { - Action = GlobalAction.Back, - KeyCombination = new KeyCombination(InputKey.D) - }); + realm.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.A))); + realm.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.S))); + realm.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.D))); transaction.Commit(); } diff --git a/osu.Game/Input/Bindings/RealmKeyBinding.cs b/osu.Game/Input/Bindings/RealmKeyBinding.cs index 32813ada16..c941319ddb 100644 --- a/osu.Game/Input/Bindings/RealmKeyBinding.cs +++ b/osu.Game/Input/Bindings/RealmKeyBinding.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using JetBrains.Annotations; using osu.Framework.Input.Bindings; using osu.Game.Database; using Realms; @@ -14,7 +15,7 @@ namespace osu.Game.Input.Bindings public class RealmKeyBinding : RealmObject, IHasGuidPrimaryKey, IKeyBinding { [PrimaryKey] - public Guid ID { get; set; } = Guid.NewGuid(); + public Guid ID { get; set; } public string? RulesetName { get; set; } @@ -38,6 +39,21 @@ namespace osu.Game.Input.Bindings public int ActionInt { get; set; } [MapTo(nameof(KeyCombination))] - public string KeyCombinationString { get; set; } = string.Empty; + public string KeyCombinationString { get; set; } = null!; + + public RealmKeyBinding(object action, KeyCombination keyCombination, string? rulesetName = null, int? variant = null) + { + Action = action; + KeyCombination = keyCombination; + + RulesetName = rulesetName; + Variant = variant; + ID = Guid.NewGuid(); + } + + [UsedImplicitly] // Realm + private RealmKeyBinding() + { + } } } diff --git a/osu.Game/Input/RealmKeyBindingStore.cs b/osu.Game/Input/RealmKeyBindingStore.cs index cb51797685..99f5752cfb 100644 --- a/osu.Game/Input/RealmKeyBindingStore.cs +++ b/osu.Game/Input/RealmKeyBindingStore.cs @@ -92,13 +92,7 @@ namespace osu.Game.Input if (defaultsCount > existingCount) { // insert any defaults which are missing. - realm.Add(defaultsForAction.Skip(existingCount).Select(k => new RealmKeyBinding - { - KeyCombinationString = k.KeyCombination.ToString(), - ActionInt = (int)k.Action, - RulesetName = rulesetName, - Variant = variant - })); + realm.Add(defaultsForAction.Skip(existingCount).Select(k => new RealmKeyBinding(k.Action, k.KeyCombination, rulesetName, variant))); } else if (defaultsCount < existingCount) { From 0bd7486a832e0ec701e54d8ed5b271e1190d4a1c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 Jan 2022 17:29:03 +0900 Subject: [PATCH 522/996] Avoid constructor overhead for realm `SkinInfo` parameterless constructor --- osu.Game/Skinning/SkinInfo.cs | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/osu.Game/Skinning/SkinInfo.cs b/osu.Game/Skinning/SkinInfo.cs index fee8c3edb2..a89725e466 100644 --- a/osu.Game/Skinning/SkinInfo.cs +++ b/osu.Game/Skinning/SkinInfo.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using JetBrains.Annotations; using Newtonsoft.Json; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Testing; @@ -26,16 +27,16 @@ namespace osu.Game.Skinning [PrimaryKey] [JsonProperty] - public Guid ID { get; set; } = Guid.NewGuid(); + public Guid ID { get; set; } [JsonProperty] - public string Name { get; set; } = string.Empty; + public string Name { get; set; } = null!; [JsonProperty] - public string Creator { get; set; } = string.Empty; + public string Creator { get; set; } = null!; [JsonProperty] - public string InstantiationInfo { get; set; } = string.Empty; + public string InstantiationInfo { get; set; } = null!; public string Hash { get; set; } = string.Empty; @@ -55,6 +56,19 @@ namespace osu.Game.Skinning public bool DeletePending { get; set; } + public SkinInfo(string? name = null, string? creator = null, string? instantiationInfo = null) + { + Name = name ?? string.Empty; + Creator = creator ?? string.Empty; + InstantiationInfo = instantiationInfo ?? string.Empty; + ID = Guid.NewGuid(); + } + + [UsedImplicitly] // Realm + private SkinInfo() + { + } + public bool Equals(SkinInfo? other) { if (ReferenceEquals(this, other)) return true; From 4235fb317d617909ee8b3b80a4462a1cbe5fa939 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Jan 2022 15:00:05 +0900 Subject: [PATCH 523/996] Remove unnecessary detach operation --- osu.Game/Collections/CollectionManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs index d230e649f7..c4f991094c 100644 --- a/osu.Game/Collections/CollectionManager.cs +++ b/osu.Game/Collections/CollectionManager.cs @@ -209,7 +209,7 @@ namespace osu.Game.Collections string checksum = sr.ReadString(); - var beatmap = beatmaps.QueryBeatmap(b => b.MD5Hash == checksum)?.Detach(); + var beatmap = beatmaps.QueryBeatmap(b => b.MD5Hash == checksum); if (beatmap != null) collection.Beatmaps.Add(beatmap); } From 3ba712703b7a464c85ffe08927b370c8b8f6514a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 Jan 2022 17:50:17 +0900 Subject: [PATCH 524/996] Add a note about hidden beatmap check --- osu.Game/Screens/Select/BeatmapCarousel.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 437567d4b2..d23febf429 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -713,6 +713,11 @@ namespace osu.Game.Screens.Select { beatmapSet = beatmapSet.Detach(); + // This can be moved to the realm query if required using: + // .Filter("DeletePending == false && Protected == false && ALL Beatmaps.Hidden == false") + // + // As long as we are detaching though, it makes more sense to do it here as adding to the realm query has an overhead + // as seen at https://github.com/realm/realm-dotnet/discussions/2773#discussioncomment-2004275. if (beatmapSet.Beatmaps.All(b => b.Hidden)) return null; From b1cf3befa6700c49403d5e628f14da0daa37d7df Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 Jan 2022 18:36:20 +0900 Subject: [PATCH 525/996] Fix incorrect query in comment --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index d23febf429..75ad0511e6 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -714,7 +714,7 @@ namespace osu.Game.Screens.Select beatmapSet = beatmapSet.Detach(); // This can be moved to the realm query if required using: - // .Filter("DeletePending == false && Protected == false && ALL Beatmaps.Hidden == false") + // .Filter("DeletePending == false && Protected == false && ANY Beatmaps.Hidden == false") // // As long as we are detaching though, it makes more sense to do it here as adding to the realm query has an overhead // as seen at https://github.com/realm/realm-dotnet/discussions/2773#discussioncomment-2004275. From 5df46d0a54785719585b47646dfc305674615e9e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 Jan 2022 20:23:17 +0900 Subject: [PATCH 526/996] Remove all calls to `Realm.Refresh` to fix blocking overhead from subscriptions Turns out this is not required if realm is aware of a `SynchronizationContext`. See https://github.com/realm/realm-dotnet/discussions/2775#discussioncomment-2005412 for further reading. --- osu.Game/Database/EFToRealmMigrator.cs | 3 --- osu.Game/Database/RealmContextFactory.cs | 14 +------------- osu.Game/OsuGameBase.cs | 7 ------- 3 files changed, 1 insertion(+), 23 deletions(-) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index cff83938bf..0f726f8ee5 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -46,9 +46,6 @@ namespace osu.Game.Database migrateScores(ef); } - Logger.Log("Refreshing realm...", LoggingTarget.Database); - realmContextFactory.Refresh(); - // Delete the database permanently. // Will cause future startups to not attempt migration. Logger.Log("Migration successful, deleting EF database", LoggingTarget.Database); diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 99b357710e..31dbb0c6c4 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -61,10 +61,10 @@ namespace osu.Game.Database private readonly ThreadLocal currentThreadCanCreateContexts = new ThreadLocal(); - private static readonly GlobalStatistic refreshes = GlobalStatistics.Get(@"Realm", @"Dirty Refreshes"); private static readonly GlobalStatistic contexts_created = GlobalStatistics.Get(@"Realm", @"Contexts (Created)"); private readonly object contextLock = new object(); + private Realm? context; public Realm Context @@ -169,18 +169,6 @@ namespace osu.Game.Database /// public bool Compact() => Realm.Compact(getConfiguration()); - /// - /// Perform a blocking refresh on the main realm context. - /// - public void Refresh() - { - lock (contextLock) - { - if (context?.Refresh() == true) - refreshes.Value++; - } - } - public Realm CreateContext() { if (isDisposed) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index fb09dac1b1..5af992f800 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -351,13 +351,6 @@ namespace osu.Game FrameStatistics.ValueChanged += e => fpsDisplayVisible.Value = e.NewValue != FrameStatisticsMode.None; } - protected override void Update() - { - base.Update(); - - realmFactory.Refresh(); - } - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); From a8ce2c5edf15f5af96cb8e14183e89757c650ae9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 Jan 2022 21:14:10 +0900 Subject: [PATCH 527/996] Detach before sending `BeatmapSetInfo` to any handling method --- osu.Game/Screens/Select/BeatmapCarousel.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 75ad0511e6..0ee59f7f04 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -114,7 +114,7 @@ namespace osu.Game.Screens.Select { CarouselRoot newRoot = new CarouselRoot(this); - newRoot.AddChildren(beatmapSets.Select(createCarouselSet).Where(g => g != null)); + newRoot.AddChildren(beatmapSets.Select(s => createCarouselSet(s.Detach())).Where(g => g != null)); root = newRoot; if (selectedBeatmapSet != null && !beatmapSets.Contains(selectedBeatmapSet.BeatmapSet)) @@ -209,7 +209,7 @@ namespace osu.Game.Screens.Select return; foreach (int i in changes.InsertedIndices) - RemoveBeatmapSet(sender[i]); + RemoveBeatmapSet(sender[i].Detach()); } private void beatmapSetsChanged(IRealmCollection sender, ChangeSet changes, Exception error) @@ -248,10 +248,10 @@ namespace osu.Game.Screens.Select } foreach (int i in changes.NewModifiedIndices) - UpdateBeatmapSet(sender[i]); + UpdateBeatmapSet(sender[i].Detach()); foreach (int i in changes.InsertedIndices) - UpdateBeatmapSet(sender[i]); + UpdateBeatmapSet(sender[i].Detach()); } private void beatmapsChanged(IRealmCollection sender, ChangeSet changes, Exception error) @@ -261,7 +261,7 @@ namespace osu.Game.Screens.Select return; foreach (int i in changes.InsertedIndices) - UpdateBeatmapSet(sender[i].BeatmapSet); + UpdateBeatmapSet(sender[i].BeatmapSet?.Detach()); } private IRealmCollection getBeatmapSets(Realm realm) => realm.All().Where(s => !s.DeletePending && !s.Protected).AsRealmCollection(); @@ -711,8 +711,6 @@ namespace osu.Game.Screens.Select private CarouselBeatmapSet createCarouselSet(BeatmapSetInfo beatmapSet) { - beatmapSet = beatmapSet.Detach(); - // This can be moved to the realm query if required using: // .Filter("DeletePending == false && Protected == false && ANY Beatmaps.Hidden == false") // From 9a864267d2054cb48fe06db37ccbdd9e4b8f26fa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 Jan 2022 21:57:16 +0900 Subject: [PATCH 528/996] Fix `CarouselGroupEagerSelect` not invoking subclassed `AddChild` from `AddChildren` calls --- .../Select/Carousel/CarouselGroupEagerSelect.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs b/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs index 9e8aad4b6f..aac0e4ed82 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs @@ -55,10 +55,16 @@ namespace osu.Game.Screens.Select.Carousel updateSelectedIndex(); } + private bool addingChildren; + public void AddChildren(IEnumerable items) { + addingChildren = true; + foreach (var i in items) - base.AddChild(i); + AddChild(i); + + addingChildren = false; attemptSelection(); } @@ -66,7 +72,8 @@ namespace osu.Game.Screens.Select.Carousel public override void AddChild(CarouselItem i) { base.AddChild(i); - attemptSelection(); + if (!addingChildren) + attemptSelection(); } protected override void ChildItemStateChanged(CarouselItem item, CarouselItemState value) From 0b93f3c88f2f1d2a6ae7b3e2300d5b77f8606cfb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 Jan 2022 21:58:16 +0900 Subject: [PATCH 529/996] Add `` dictionary to speed up update operations in carousel --- osu.Game/Screens/Select/BeatmapCarousel.cs | 99 ++++++++++++++-------- 1 file changed, 64 insertions(+), 35 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 0ee59f7f04..458a987130 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -95,7 +95,7 @@ namespace osu.Game.Screens.Select protected readonly CarouselScrollContainer Scroll; - private IEnumerable beatmapSets => root.Children.OfType(); + private IEnumerable beatmapSets => root.BeatmapSetsByID.Values; // todo: only used for testing, maybe remove. private bool loadedTestBeatmaps; @@ -117,6 +117,7 @@ namespace osu.Game.Screens.Select newRoot.AddChildren(beatmapSets.Select(s => createCarouselSet(s.Detach())).Where(g => g != null)); root = newRoot; + if (selectedBeatmapSet != null && !beatmapSets.Contains(selectedBeatmapSet.BeatmapSet)) selectedBeatmapSet = null; @@ -209,7 +210,7 @@ namespace osu.Game.Screens.Select return; foreach (int i in changes.InsertedIndices) - RemoveBeatmapSet(sender[i].Detach()); + removeBeatmapSet(sender[i].ID); } private void beatmapSetsChanged(IRealmCollection sender, ChangeSet changes, Exception error) @@ -223,24 +224,20 @@ namespace osu.Game.Screens.Select // During initial population, we must manually account for the fact that our original query was done on an async thread. // Since then, there may have been imports or deletions. // Here we manually catch up on any changes. - var populatedSets = new HashSet(); - foreach (var s in beatmapSets) - populatedSets.Add(s.BeatmapSet.ID); - var realmSets = new HashSet(); foreach (var s in sender) realmSets.Add(s.ID); - foreach (var s in realmSets) + foreach (var id in realmSets) { - if (!populatedSets.Contains(s)) - UpdateBeatmapSet(realmFactory.Context.Find(s)); + if (!root.BeatmapSetsByID.ContainsKey(id)) + UpdateBeatmapSet(realmFactory.Context.Find(id).Detach()); } - foreach (var s in populatedSets) + foreach (var id in root.BeatmapSetsByID.Keys) { - if (!realmSets.Contains(s)) - RemoveBeatmapSet(realmFactory.Context.Find(s)); + if (!realmSets.Contains(id)) + removeBeatmapSet(id); } signalBeatmapsLoaded(); @@ -261,16 +258,30 @@ namespace osu.Game.Screens.Select return; foreach (int i in changes.InsertedIndices) - UpdateBeatmapSet(sender[i].BeatmapSet?.Detach()); + { + var beatmapInfo = sender[i]; + var beatmapSet = beatmapInfo.BeatmapSet; + + Debug.Assert(beatmapSet != null); + + // Only require to action here if the beatmap is missing. + // This avoids processing these events unnecessarily when new beatmaps are imported, for example. + if (root.BeatmapSetsByID.TryGetValue(beatmapSet.ID, out var existingSet) + && existingSet.BeatmapSet.Beatmaps.All(b => b.ID != beatmapInfo.ID)) + { + UpdateBeatmapSet(beatmapSet.Detach()); + } + } } private IRealmCollection getBeatmapSets(Realm realm) => realm.All().Where(s => !s.DeletePending && !s.Protected).AsRealmCollection(); - public void RemoveBeatmapSet(BeatmapSetInfo beatmapSet) => Schedule(() => - { - var existingSet = beatmapSets.FirstOrDefault(b => b.BeatmapSet.Equals(beatmapSet)); + public void RemoveBeatmapSet(BeatmapSetInfo beatmapSet) => + removeBeatmapSet(beatmapSet.ID); - if (existingSet == null) + private void removeBeatmapSet(Guid beatmapSetID) => Schedule(() => + { + if (!root.BeatmapSetsByID.TryGetValue(beatmapSetID, out var existingSet)) return; root.RemoveChild(existingSet); @@ -281,33 +292,27 @@ namespace osu.Game.Screens.Select { Guid? previouslySelectedID = null; - CarouselBeatmapSet existingSet = beatmapSets.FirstOrDefault(b => b.BeatmapSet.Equals(beatmapSet)); - // If the selected beatmap is about to be removed, store its ID so it can be re-selected if required - if (existingSet?.State?.Value == CarouselItemState.Selected) + if (selectedBeatmapSet?.BeatmapSet.ID == beatmapSet.ID) previouslySelectedID = selectedBeatmap?.BeatmapInfo.ID; var newSet = createCarouselSet(beatmapSet); - if (existingSet != null) - root.RemoveChild(existingSet); + root.RemoveChild(beatmapSet.ID); - if (newSet == null) + if (newSet != null) { - itemsCache.Invalidate(); - return; + root.AddChild(newSet); + + // only reset scroll position if already near the scroll target. + // without this, during a large beatmap import it is impossible to navigate the carousel. + applyActiveCriteria(false, alwaysResetScrollPosition: false); + + // check if we can/need to maintain our current selection. + if (previouslySelectedID != null) + select((CarouselItem)newSet.Beatmaps.FirstOrDefault(b => b.BeatmapInfo.ID == previouslySelectedID) ?? newSet); } - root.AddChild(newSet); - - // only reset scroll position if already near the scroll target. - // without this, during a large beatmap import it is impossible to navigate the carousel. - applyActiveCriteria(false, alwaysResetScrollPosition: false); - - // check if we can/need to maintain our current selection. - if (previouslySelectedID != null) - select((CarouselItem)newSet.Beatmaps.FirstOrDefault(b => b.BeatmapInfo.ID == previouslySelectedID) ?? newSet); - itemsCache.Invalidate(); Schedule(() => BeatmapSetsChanged?.Invoke()); }); @@ -911,6 +916,8 @@ namespace osu.Game.Screens.Select { private readonly BeatmapCarousel carousel; + public readonly Dictionary BeatmapSetsByID = new Dictionary(); + public CarouselRoot(BeatmapCarousel carousel) { // root should always remain selected. if not, PerformSelection will not be called. @@ -920,6 +927,28 @@ namespace osu.Game.Screens.Select this.carousel = carousel; } + public override void AddChild(CarouselItem i) + { + CarouselBeatmapSet set = (CarouselBeatmapSet)i; + BeatmapSetsByID[set.BeatmapSet.ID] = set; + + base.AddChild(i); + } + + public void RemoveChild(Guid beatmapSetID) + { + if (BeatmapSetsByID.TryGetValue(beatmapSetID, out var carouselBeatmapSet)) + RemoveChild(carouselBeatmapSet); + } + + public override void RemoveChild(CarouselItem i) + { + CarouselBeatmapSet set = (CarouselBeatmapSet)i; + BeatmapSetsByID.Remove(set.BeatmapSet.ID); + + base.RemoveChild(i); + } + protected override void PerformSelection() { if (LastSelected == null || LastSelected.Filtered.Value) From 80f3a67876adcad87ba910e41e7bc82fd46cf820 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 Jan 2022 22:21:00 +0900 Subject: [PATCH 530/996] Use `for` instead of `foreach` to avoid enumerator overhead --- osu.Game/Screens/Select/BeatmapCarousel.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 458a987130..a5704b3b2e 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -225,8 +225,9 @@ namespace osu.Game.Screens.Select // Since then, there may have been imports or deletions. // Here we manually catch up on any changes. var realmSets = new HashSet(); - foreach (var s in sender) - realmSets.Add(s.ID); + + for (int i = 0; i < sender.Count; i++) + realmSets.Add(sender[i].ID); foreach (var id in realmSets) { From ba31ddee017a950be2c8b50c4c9750ebeb575af6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 00:33:46 +0900 Subject: [PATCH 531/996] Revert `beatmapSets` reference to fix tests New version changed order. --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index a5704b3b2e..961dac9856 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -95,7 +95,7 @@ namespace osu.Game.Screens.Select protected readonly CarouselScrollContainer Scroll; - private IEnumerable beatmapSets => root.BeatmapSetsByID.Values; + private IEnumerable beatmapSets => root.Children.OfType(); // todo: only used for testing, maybe remove. private bool loadedTestBeatmaps; From 079b2dfc42ac22168dbc4f436653a648b51f9ad8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 00:01:34 +0900 Subject: [PATCH 532/996] Create backup of databases before opening contexts Attempt to avoid file IO issues. Closes #16531. --- osu.Game/Database/EFToRealmMigrator.cs | 30 ++++++++------------------ 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index 0f726f8ee5..727815cc4d 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -26,8 +26,6 @@ namespace osu.Game.Database private readonly OsuConfigManager config; private readonly Storage storage; - private bool hasTakenBackup; - public EFToRealmMigrator(DatabaseContextFactory efContextFactory, RealmContextFactory realmContextFactory, OsuConfigManager config, Storage storage) { this.efContextFactory = efContextFactory; @@ -38,6 +36,8 @@ namespace osu.Game.Database public void Run() { + createBackup(); + using (var ef = efContextFactory.Get()) { migrateSettings(ef); @@ -77,8 +77,6 @@ namespace osu.Game.Database { Logger.Log($"Found {count} beatmaps in EF", LoggingTarget.Database); - ensureBackup(); - // only migrate data if the realm database is empty. // note that this cannot be written as: `realm.All().All(s => s.Protected)`, because realm does not support `.All()`. if (realm.All().Any(s => !s.Protected)) @@ -210,8 +208,6 @@ namespace osu.Game.Database { Logger.Log($"Found {count} scores in EF", LoggingTarget.Database); - ensureBackup(); - // only migrate data if the realm database is empty. if (realm.All().Any()) { @@ -291,8 +287,6 @@ namespace osu.Game.Database if (!existingSkins.Any()) return; - ensureBackup(); - var userSkinChoice = config.GetBindable(OsuSetting.Skin); int.TryParse(userSkinChoice.Value, out int userSkinInt); @@ -363,7 +357,6 @@ namespace osu.Game.Database return; Logger.Log("Beginning settings migration to realm", LoggingTarget.Database); - ensureBackup(); using (var realm = realmContextFactory.CreateContext()) using (var transaction = realm.BeginWrite()) @@ -400,21 +393,16 @@ namespace osu.Game.Database private string? getRulesetShortNameFromLegacyID(long rulesetId) => efContextFactory.Get().RulesetInfo.FirstOrDefault(r => r.ID == rulesetId)?.ShortName; - private void ensureBackup() + private void createBackup() { - if (!hasTakenBackup) - { - string migration = $"before_final_migration_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}"; + string migration = $"before_final_migration_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}"; - efContextFactory.CreateBackup($"client.{migration}.db"); - realmContextFactory.CreateBackup($"client.{migration}.realm"); + efContextFactory.CreateBackup($"client.{migration}.db"); + realmContextFactory.CreateBackup($"client.{migration}.realm"); - using (var source = storage.GetStream("collection.db")) - using (var destination = storage.GetStream($"collection.{migration}.db", FileAccess.Write, FileMode.CreateNew)) - source.CopyTo(destination); - - hasTakenBackup = true; - } + using (var source = storage.GetStream("collection.db")) + using (var destination = storage.GetStream($"collection.{migration}.db", FileAccess.Write, FileMode.CreateNew)) + source.CopyTo(destination); } } } From 7aad2780b1fae24d9fd6213f0e44c519fc87cc89 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 00:46:47 +0900 Subject: [PATCH 533/996] Add retry logic for realm backup creation --- osu.Game/Database/RealmContextFactory.cs | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 31dbb0c6c4..ffadf8258d 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -367,9 +367,24 @@ namespace osu.Game.Database using (BlockAllOperations()) { Logger.Log($"Creating full realm database backup at {backupFilename}", LoggingTarget.Database); - using (var source = storage.GetStream(Filename)) - using (var destination = storage.GetStream(backupFilename, FileAccess.Write, FileMode.CreateNew)) - source.CopyTo(destination); + + int attempts = 10; + + while (attempts-- > 0) + { + try + { + using (var source = storage.GetStream(Filename)) + 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 0c9eb3ad61a412f6ebed4d0dc08f593743cee8d1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 01:33:45 +0900 Subject: [PATCH 534/996] Add realm factory helper methods to run work on the correct context Avoids constructing a new `Realm` instance when called from the update thread without worrying about disposal. --- osu.Game/Database/RealmContextFactory.cs | 35 ++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 31dbb0c6c4..50e456a0c8 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -169,6 +169,41 @@ namespace osu.Game.Database /// public bool Compact() => Realm.Compact(getConfiguration()); + /// + /// Run work on realm with a return value. + /// + /// + /// Handles correct context management automatically. + /// + /// The work to run. + /// The return type. + public T Run(Func action) + { + if (ThreadSafety.IsUpdateThread) + return action(Context); + + using (var realm = CreateContext()) + return action(realm); + } + + /// + /// Run work on realm. + /// + /// + /// Handles correct context management automatically. + /// + /// The work to run. + public void Run(Action action) + { + if (ThreadSafety.IsUpdateThread) + action(Context); + else + { + using (var realm = CreateContext()) + action(realm); + } + } + public Realm CreateContext() { if (isDisposed) From a5d2047f055bbbda917d8acec11f09a363b202ce Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 01:34:20 +0900 Subject: [PATCH 535/996] Fix various cases of creating realm contexts from update thread when not necessary --- osu.Game/Beatmaps/BeatmapManager.cs | 39 ++++++++++--------- osu.Game/Beatmaps/BeatmapModelManager.cs | 7 ++-- .../Settings/Sections/Input/KeyBindingRow.cs | 4 +- osu.Game/Scoring/ScoreModelManager.cs | 3 +- osu.Game/Skinning/SkinManager.cs | 9 ++--- osu.Game/Stores/BeatmapImporter.cs | 3 +- osu.Game/Stores/RealmArchiveModelManager.cs | 8 ++-- 7 files changed, 36 insertions(+), 37 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index ee649ad960..cc765657cc 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -119,15 +119,17 @@ namespace osu.Game.Beatmaps /// The beatmap difficulty to hide. public void Hide(BeatmapInfo beatmapInfo) { - using (var realm = contextFactory.CreateContext()) - using (var transaction = realm.BeginWrite()) + contextFactory.Run(realm => { - if (!beatmapInfo.IsManaged) - beatmapInfo = realm.Find(beatmapInfo.ID); + using (var transaction = realm.BeginWrite()) + { + if (!beatmapInfo.IsManaged) + beatmapInfo = realm.Find(beatmapInfo.ID); - beatmapInfo.Hidden = true; - transaction.Commit(); - } + beatmapInfo.Hidden = true; + transaction.Commit(); + } + }); } /// @@ -136,15 +138,17 @@ namespace osu.Game.Beatmaps /// The beatmap difficulty to restore. public void Restore(BeatmapInfo beatmapInfo) { - using (var realm = contextFactory.CreateContext()) - using (var transaction = realm.BeginWrite()) + contextFactory.Run(realm => { - if (!beatmapInfo.IsManaged) - beatmapInfo = realm.Find(beatmapInfo.ID); + using (var transaction = realm.BeginWrite()) + { + if (!beatmapInfo.IsManaged) + beatmapInfo = realm.Find(beatmapInfo.ID); - beatmapInfo.Hidden = false; - transaction.Commit(); - } + beatmapInfo.Hidden = false; + transaction.Commit(); + } + }); } public void RestoreAll() @@ -176,8 +180,7 @@ namespace osu.Game.Beatmaps /// The first result for the provided query, or null if no results were found. public ILive? QueryBeatmapSet(Expression> query) { - using (var context = contextFactory.CreateContext()) - return context.All().FirstOrDefault(query)?.ToLive(contextFactory); + return contextFactory.Run(realm => realm.All().FirstOrDefault(query)?.ToLive(contextFactory)); } #region Delegation to BeatmapModelManager (methods which previously existed locally). @@ -305,13 +308,13 @@ namespace osu.Game.Beatmaps // If we seem to be missing files, now is a good time to re-fetch. if (importedBeatmap?.BeatmapSet?.Files.Count == 0) { - using (var realm = contextFactory.CreateContext()) + contextFactory.Run(realm => { var refetch = realm.Find(importedBeatmap.ID)?.Detach(); if (refetch != null) importedBeatmap = refetch; - } + }); } return workingBeatmapCache.GetWorkingBeatmap(importedBeatmap); diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs index 3822c6e121..a4ba13a88d 100644 --- a/osu.Game/Beatmaps/BeatmapModelManager.cs +++ b/osu.Game/Beatmaps/BeatmapModelManager.cs @@ -98,17 +98,16 @@ namespace osu.Game.Beatmaps /// The first result for the provided query, or null if no results were found. public BeatmapInfo? QueryBeatmap(Expression> query) { - using (var context = ContextFactory.CreateContext()) - return context.All().FirstOrDefault(query)?.Detach(); + return ContextFactory.Run(realm => realm.All().FirstOrDefault(query)?.Detach()); } public void Update(BeatmapSetInfo item) { - using (var realm = ContextFactory.CreateContext()) + ContextFactory.Run(realm => { var existing = realm.Find(item.ID); realm.Write(r => item.CopyChangesToRealm(existing)); - } + }); } } } diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs index e0a1a82326..60aff91301 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs @@ -386,11 +386,11 @@ namespace osu.Game.Overlays.Settings.Sections.Input private void updateStoreFromButton(KeyButton button) { - using (var realm = realmFactory.CreateContext()) + realmFactory.Run(realm => { var binding = realm.Find(((IHasGuidPrimaryKey)button.KeyBinding).ID); realm.Write(() => binding.KeyCombinationString = button.KeyBinding.KeyCombinationString); - } + }); } private void updateIsDefaultValue() diff --git a/osu.Game/Scoring/ScoreModelManager.cs b/osu.Game/Scoring/ScoreModelManager.cs index 5ba152fad3..5e560effa1 100644 --- a/osu.Game/Scoring/ScoreModelManager.cs +++ b/osu.Game/Scoring/ScoreModelManager.cs @@ -74,8 +74,7 @@ namespace osu.Game.Scoring public override bool IsAvailableLocally(ScoreInfo model) { - using (var context = ContextFactory.CreateContext()) - return context.All().Any(b => b.OnlineID == model.OnlineID); + return ContextFactory.Run(realm => realm.All().Any(s => s.OnlineID == model.OnlineID)); } } } diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index cde21b78c1..3f6e5754fb 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -113,10 +113,10 @@ namespace osu.Game.Skinning public void SelectRandomSkin() { - using (var context = contextFactory.CreateContext()) + contextFactory.Run(realm => { // choose from only user skins, removing the current selection to ensure a new one is chosen. - var randomChoices = context.All().Where(s => !s.DeletePending && s.ID != CurrentSkinInfo.Value.ID).ToArray(); + var randomChoices = realm.All().Where(s => !s.DeletePending && s.ID != CurrentSkinInfo.Value.ID).ToArray(); if (randomChoices.Length == 0) { @@ -127,7 +127,7 @@ namespace osu.Game.Skinning var chosen = randomChoices.ElementAt(RNG.Next(0, randomChoices.Length)); CurrentSkinInfo.Value = chosen.ToLive(contextFactory); - } + }); } /// @@ -182,8 +182,7 @@ namespace osu.Game.Skinning /// The first result for the provided query, or null if no results were found. public ILive Query(Expression> query) { - using (var context = contextFactory.CreateContext()) - return context.All().FirstOrDefault(query)?.ToLive(contextFactory); + return contextFactory.Run(realm => realm.All().FirstOrDefault(query)?.ToLive(contextFactory)); } public event Action SourceChanged; diff --git a/osu.Game/Stores/BeatmapImporter.cs b/osu.Game/Stores/BeatmapImporter.cs index d285a6b61c..61178014ef 100644 --- a/osu.Game/Stores/BeatmapImporter.cs +++ b/osu.Game/Stores/BeatmapImporter.cs @@ -165,8 +165,7 @@ namespace osu.Game.Stores public override bool IsAvailableLocally(BeatmapSetInfo model) { - using (var context = ContextFactory.CreateContext()) - return context.All().Any(b => b.OnlineID == model.OnlineID); + return ContextFactory.Run(realm => realm.All().Any(b => b.OnlineID == model.OnlineID)); } public override string HumanisedModelName => "beatmap"; diff --git a/osu.Game/Stores/RealmArchiveModelManager.cs b/osu.Game/Stores/RealmArchiveModelManager.cs index b456dae343..115fbf721d 100644 --- a/osu.Game/Stores/RealmArchiveModelManager.cs +++ b/osu.Game/Stores/RealmArchiveModelManager.cs @@ -165,7 +165,7 @@ namespace osu.Game.Stores public bool Delete(TModel item) { - using (var realm = ContextFactory.CreateContext()) + return ContextFactory.Run(realm => { if (!item.IsManaged) item = realm.Find(item.ID); @@ -175,12 +175,12 @@ namespace osu.Game.Stores realm.Write(r => item.DeletePending = true); return true; - } + }); } public void Undelete(TModel item) { - using (var realm = ContextFactory.CreateContext()) + ContextFactory.Run(realm => { if (!item.IsManaged) item = realm.Find(item.ID); @@ -189,7 +189,7 @@ namespace osu.Game.Stores return; realm.Write(r => item.DeletePending = false); - } + }); } public abstract bool IsAvailableLocally(TModel model); From e0fe8af365535a20c077cc397a69b2bf801c9dba Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Fri, 21 Jan 2022 08:54:08 +0800 Subject: [PATCH 536/996] Schedule setPerformanceValue --- .../Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs index bda147cf22..158fd82b29 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs @@ -46,7 +46,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics { new PerformanceBreakdownCalculator(beatmapManager, difficultyCache, performanceCache) .CalculateAsync(score, cancellationTokenSource.Token) - .ContinueWith(t => setPerformanceValue(t.GetResultSafely())); + .ContinueWith(t => Schedule(() => setPerformanceValue(t.GetResultSafely()))); } private void setPerformanceValue(PerformanceBreakdown breakdown) From 45bf35c42532d4051166df6a8df955d41a8b7e49 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 12:26:24 +0900 Subject: [PATCH 537/996] Avoid performing keyword filtering at song select unless keywords are specified --- osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index d54a3bb54e..6b198ab505 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -55,7 +55,7 @@ namespace osu.Game.Screens.Select.Carousel match &= !criteria.UserStarDifficulty.HasFilter || criteria.UserStarDifficulty.IsInRange(BeatmapInfo.StarRating); - if (match) + if (match && criteria.SearchTerms.Length > 0) { string[] terms = BeatmapInfo.GetSearchableTerms(); From 5b24800b0e9c88af8747f9e33a3c14b97ca1f4d8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 12:37:17 +0900 Subject: [PATCH 538/996] Avoid applying filter in `UpdateBeatmapSet` flow --- osu.Game/Screens/Select/BeatmapCarousel.cs | 12 +++++---- .../Screens/Select/Carousel/CarouselGroup.cs | 27 +++++++++++++++++-- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 75ad0511e6..6d3d7aa185 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -300,16 +300,18 @@ namespace osu.Game.Screens.Select root.AddChild(newSet); - // only reset scroll position if already near the scroll target. - // without this, during a large beatmap import it is impossible to navigate the carousel. - applyActiveCriteria(false, alwaysResetScrollPosition: false); - // check if we can/need to maintain our current selection. if (previouslySelectedID != null) select((CarouselItem)newSet.Beatmaps.FirstOrDefault(b => b.BeatmapInfo.ID == previouslySelectedID) ?? newSet); itemsCache.Invalidate(); - Schedule(() => BeatmapSetsChanged?.Invoke()); + Schedule(() => + { + if (!Scroll.UserScrolling) + ScrollToSelected(true); + + BeatmapSetsChanged?.Invoke(); + }); }); /// diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs index b85e868b89..7e4c5fad72 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; namespace osu.Game.Screens.Select.Carousel { @@ -36,7 +37,21 @@ namespace osu.Game.Screens.Select.Carousel { i.State.ValueChanged += state => ChildItemStateChanged(i, state.NewValue); i.ChildID = ++currentChildID; - InternalChildren.Add(i); + + if (lastCriteria != null) + { + i.Filter(lastCriteria); + + int index = InternalChildren.BinarySearch(i, criteriaComparer); + if (index < 0) index = ~index; // BinarySearch hacks multiple return values with 2's complement. + + InternalChildren.Insert(index, i); + } + else + { + // criteria may be null for initial population. the filtering will be applied post-add. + InternalChildren.Add(i); + } } public CarouselGroup(List items = null) @@ -62,14 +77,22 @@ namespace osu.Game.Screens.Select.Carousel }; } + private Comparer criteriaComparer; + + [CanBeNull] + private FilterCriteria lastCriteria; + public override void Filter(FilterCriteria criteria) { base.Filter(criteria); InternalChildren.ForEach(c => c.Filter(criteria)); + // IEnumerable.OrderBy() is used instead of List.Sort() to ensure sorting stability - var criteriaComparer = Comparer.Create((x, y) => x.CompareTo(criteria, y)); + criteriaComparer = Comparer.Create((x, y) => x.CompareTo(criteria, y)); InternalChildren = InternalChildren.OrderBy(c => c, criteriaComparer).ToList(); + + lastCriteria = criteria; } protected virtual void ChildItemStateChanged(CarouselItem item, CarouselItemState value) From bed7b69464ed1b6971ae84ee81885d6ce90c5fed Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 13:09:03 +0900 Subject: [PATCH 539/996] Apply NRT to `CarouselGroup` --- .../Screens/Select/Carousel/CarouselGroup.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs index 7e4c5fad72..6ebe314072 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs @@ -3,7 +3,8 @@ using System.Collections.Generic; using System.Linq; -using JetBrains.Annotations; + +#nullable enable namespace osu.Game.Screens.Select.Carousel { @@ -12,7 +13,7 @@ namespace osu.Game.Screens.Select.Carousel /// public class CarouselGroup : CarouselItem { - public override DrawableCarouselItem CreateDrawableRepresentation() => null; + public override DrawableCarouselItem? CreateDrawableRepresentation() => null; public IReadOnlyList Children => InternalChildren; @@ -24,6 +25,10 @@ namespace osu.Game.Screens.Select.Carousel /// private ulong currentChildID; + private Comparer? criteriaComparer; + + private FilterCriteria? lastCriteria; + public virtual void RemoveChild(CarouselItem i) { InternalChildren.Remove(i); @@ -54,7 +59,7 @@ namespace osu.Game.Screens.Select.Carousel } } - public CarouselGroup(List items = null) + public CarouselGroup(List? items = null) { if (items != null) InternalChildren = items; @@ -77,11 +82,6 @@ namespace osu.Game.Screens.Select.Carousel }; } - private Comparer criteriaComparer; - - [CanBeNull] - private FilterCriteria lastCriteria; - public override void Filter(FilterCriteria criteria) { base.Filter(criteria); From 5be41a189bb00e22926511b35424db85c992ead6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 14:56:28 +0900 Subject: [PATCH 540/996] Expose EF context factory for use in external migration logic --- osu.Game/OsuGameBase.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 5af992f800..3d40126b4f 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -161,6 +161,11 @@ namespace osu.Game private readonly BindableNumber globalTrackVolumeAdjust = new BindableNumber(global_track_volume_adjust); + /// + /// A legacy EF context factory if migration has not been performed to realm yet. + /// + protected DatabaseContextFactory EFContextFactory { get; private set; } + public OsuGameBase() { UseDevelopmentServer = DebugUtils.IsDebugBuild; @@ -184,19 +189,14 @@ namespace osu.Game Resources.AddStore(new DllResourceStore(OsuResources.ResourceAssembly)); - DatabaseContextFactory efContextFactory = Storage.Exists(DatabaseContextFactory.DATABASE_NAME) - ? new DatabaseContextFactory(Storage) - : null; + if (Storage.Exists(DatabaseContextFactory.DATABASE_NAME)) + dependencies.Cache(EFContextFactory = new DatabaseContextFactory(Storage)); - dependencies.Cache(realmFactory = new RealmContextFactory(Storage, "client", efContextFactory)); + dependencies.Cache(realmFactory = new RealmContextFactory(Storage, "client", EFContextFactory)); dependencies.Cache(RulesetStore = new RulesetStore(realmFactory, Storage)); dependencies.CacheAs(RulesetStore); - // A non-null context factory means there's still content to migrate. - if (efContextFactory != null) - new EFToRealmMigrator(efContextFactory, realmFactory, LocalConfig, Storage).Run(); - dependencies.CacheAs(Storage); var largeStore = new LargeTextureStore(Host.CreateTextureLoaderStore(new NamespacedResourceStore(Resources, @"Textures"))); From 5622d2ba4f704fe707ea5a9e647ee9859277ed34 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 14:56:49 +0900 Subject: [PATCH 541/996] Show realm migration progress at `Loader` --- osu.Game/Database/EFToRealmMigrator.cs | 150 +++++++++++++++++++------ osu.Game/Screens/Loader.cs | 12 +- 2 files changed, 125 insertions(+), 37 deletions(-) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index 727815cc4d..85d65fea82 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -4,52 +4,130 @@ using System; using System.IO; using System.Linq; +using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Beatmaps; using osu.Game.Configuration; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; using osu.Game.Models; using osu.Game.Rulesets; using osu.Game.Scoring; using osu.Game.Skinning; +using osuTK; using Realms; #nullable enable namespace osu.Game.Database { - internal class EFToRealmMigrator + internal class EFToRealmMigrator : CompositeDrawable { - private readonly DatabaseContextFactory efContextFactory; - private readonly RealmContextFactory realmContextFactory; - private readonly OsuConfigManager config; - private readonly Storage storage; + public bool FinishedMigrating { get; private set; } - public EFToRealmMigrator(DatabaseContextFactory efContextFactory, RealmContextFactory realmContextFactory, OsuConfigManager config, Storage storage) + [Resolved] + private DatabaseContextFactory efContextFactory { get; set; } = null!; + + [Resolved] + private RealmContextFactory realmContextFactory { get; set; } = null!; + + [Resolved] + private OsuConfigManager config { get; set; } = null!; + + [Resolved] + private Storage storage { get; set; } = null!; + + private readonly OsuSpriteText currentOperationText; + + public EFToRealmMigrator() { - this.efContextFactory = efContextFactory; - this.realmContextFactory = realmContextFactory; - this.config = config; - this.storage = storage; + RelativeSizeAxes = Axes.Both; + + InternalChildren = new Drawable[] + { + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Spacing = new Vector2(10), + Children = new Drawable[] + { + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "Database migration in progress", + Font = OsuFont.Default.With(size: 40) + }, + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "This could take a few minutes depending on the speed of your disk(s).", + Font = OsuFont.Default.With(size: 30) + }, + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "Please keep the window open until this completes!", + Font = OsuFont.Default.With(size: 30) + }, + new LoadingSpinner(true) + { + State = { Value = Visibility.Visible } + }, + currentOperationText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.Default.With(size: 30) + }, + } + }, + }; } - public void Run() + protected override void LoadComplete() { + base.LoadComplete(); + + // needs to be run on the update thread because of realm BlockAllOperations. + // maybe we can work around this? not sure.. createBackup(); - using (var ef = efContextFactory.Get()) + Task.Factory.StartNew(() => { - migrateSettings(ef); - migrateSkins(ef); - migrateBeatmaps(ef); - migrateScores(ef); - } + using (var ef = efContextFactory.Get()) + { + migrateSettings(ef); + migrateSkins(ef); + migrateBeatmaps(ef); + migrateScores(ef); + } - // Delete the database permanently. - // Will cause future startups to not attempt migration. - Logger.Log("Migration successful, deleting EF database", LoggingTarget.Database); - efContextFactory.ResetDatabase(); + // Delete the database permanently. + // Will cause future startups to not attempt migration. + log("Migration successful, deleting EF database"); + efContextFactory.ResetDatabase(); + }, TaskCreationOptions.LongRunning).ContinueWith(t => + { + FinishedMigrating = true; + }); + } + + private void log(string message) + { + Logger.Log(message, LoggingTarget.Database); + Scheduler.AddOnce(m => currentOperationText.Text = m, message); } private void migrateBeatmaps(OsuDbContext ef) @@ -62,12 +140,12 @@ namespace osu.Game.Database .Include(s => s.Files).ThenInclude(f => f.FileInfo) .Include(s => s.Metadata); - Logger.Log("Beginning beatmaps migration to realm", LoggingTarget.Database); + log("Beginning beatmaps migration to realm"); // previous entries in EF are removed post migration. if (!existingBeatmapSets.Any()) { - Logger.Log("No beatmaps found to migrate", LoggingTarget.Database); + log("No beatmaps found to migrate"); return; } @@ -75,13 +153,13 @@ namespace osu.Game.Database using (var realm = realmContextFactory.CreateContext()) { - Logger.Log($"Found {count} beatmaps in EF", LoggingTarget.Database); + log($"Found {count} beatmaps in EF"); // only migrate data if the realm database is empty. // note that this cannot be written as: `realm.All().All(s => s.Protected)`, because realm does not support `.All()`. if (realm.All().Any(s => !s.Protected)) { - Logger.Log("Skipping migration as realm already has beatmaps loaded", LoggingTarget.Database); + log("Skipping migration as realm already has beatmaps loaded"); } else { @@ -96,7 +174,7 @@ namespace osu.Game.Database { transaction.Commit(); transaction = realm.BeginWrite(); - Logger.Log($"Migrated {written}/{count} beatmaps...", LoggingTarget.Database); + log($"Migrated {written}/{count} beatmaps..."); } var realmBeatmapSet = new BeatmapSetInfo @@ -156,7 +234,7 @@ namespace osu.Game.Database transaction.Commit(); } - Logger.Log($"Successfully migrated {count} beatmaps to realm", LoggingTarget.Database); + log($"Successfully migrated {count} beatmaps to realm"); } } } @@ -193,12 +271,12 @@ namespace osu.Game.Database .Include(s => s.Files) .ThenInclude(f => f.FileInfo); - Logger.Log("Beginning scores migration to realm", LoggingTarget.Database); + log("Beginning scores migration to realm"); // previous entries in EF are removed post migration. if (!existingScores.Any()) { - Logger.Log("No scores found to migrate", LoggingTarget.Database); + log("No scores found to migrate"); return; } @@ -206,12 +284,12 @@ namespace osu.Game.Database using (var realm = realmContextFactory.CreateContext()) { - Logger.Log($"Found {count} scores in EF", LoggingTarget.Database); + log($"Found {count} scores in EF"); // only migrate data if the realm database is empty. if (realm.All().Any()) { - Logger.Log("Skipping migration as realm already has scores loaded", LoggingTarget.Database); + log("Skipping migration as realm already has scores loaded"); } else { @@ -226,7 +304,7 @@ namespace osu.Game.Database { transaction.Commit(); transaction = realm.BeginWrite(); - Logger.Log($"Migrated {written}/{count} scores...", LoggingTarget.Database); + log($"Migrated {written}/{count} scores..."); } var beatmap = realm.All().First(b => b.Hash == score.BeatmapInfo.Hash); @@ -270,7 +348,7 @@ namespace osu.Game.Database transaction.Commit(); } - Logger.Log($"Successfully migrated {count} scores to realm", LoggingTarget.Database); + log($"Successfully migrated {count} scores to realm"); } } } @@ -308,7 +386,7 @@ namespace osu.Game.Database // note that this cannot be written as: `realm.All().All(s => s.Protected)`, because realm does not support `.All()`. if (!realm.All().Any(s => !s.Protected)) { - Logger.Log($"Migrating {existingSkins.Count} skins", LoggingTarget.Database); + log($"Migrating {existingSkins.Count} skins"); foreach (var skin in existingSkins) { @@ -356,7 +434,7 @@ namespace osu.Game.Database if (!existingSettings.Any()) return; - Logger.Log("Beginning settings migration to realm", LoggingTarget.Database); + log("Beginning settings migration to realm"); using (var realm = realmContextFactory.CreateContext()) using (var transaction = realm.BeginWrite()) @@ -364,7 +442,7 @@ namespace osu.Game.Database // only migrate data if the realm database is empty. if (!realm.All().Any()) { - Logger.Log($"Migrating {existingSettings.Count} settings", LoggingTarget.Database); + log($"Migrating {existingSettings.Count} settings"); foreach (var dkb in existingSettings) { diff --git a/osu.Game/Screens/Loader.cs b/osu.Game/Screens/Loader.cs index 41097a4c74..8c4a13f2bd 100644 --- a/osu.Game/Screens/Loader.cs +++ b/osu.Game/Screens/Loader.cs @@ -12,6 +12,7 @@ using osu.Game.Screens.Menu; using osu.Framework.Screens; using osu.Framework.Threading; using osu.Game.Configuration; +using osu.Game.Database; using osu.Game.Graphics.UserInterface; using IntroSequence = osu.Game.Configuration.IntroSequence; @@ -63,6 +64,11 @@ namespace osu.Game.Screens protected virtual ShaderPrecompiler CreateShaderPrecompiler() => new ShaderPrecompiler(); + [Resolved(canBeNull: true)] + private DatabaseContextFactory efContextFactory { get; set; } + + private EFToRealmMigrator realmMigrator; + public override void OnEntering(IScreen last) { base.OnEntering(last); @@ -70,6 +76,10 @@ namespace osu.Game.Screens LoadComponentAsync(precompiler = CreateShaderPrecompiler(), AddInternal); LoadComponentAsync(loadableScreen = CreateLoadableScreen()); + // A non-null context factory means there's still content to migrate. + if (efContextFactory != null) + LoadComponentAsync(realmMigrator = new EFToRealmMigrator(), AddInternal); + LoadComponentAsync(spinner = new LoadingSpinner(true, true) { Anchor = Anchor.BottomRight, @@ -86,7 +96,7 @@ namespace osu.Game.Screens private void checkIfLoaded() { - if (loadableScreen.LoadState != LoadState.Ready || !precompiler.FinishedCompiling) + if (loadableScreen.LoadState != LoadState.Ready || !precompiler.FinishedCompiling || realmMigrator?.FinishedMigrating == false) { Schedule(checkIfLoaded); return; From 3bcdce128c5927a90f718b0ef54d76678272648a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 15:29:21 +0900 Subject: [PATCH 542/996] Use dictionary add for safety --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 3cd9253eff..e8171d1512 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -934,7 +934,7 @@ namespace osu.Game.Screens.Select public override void AddChild(CarouselItem i) { CarouselBeatmapSet set = (CarouselBeatmapSet)i; - BeatmapSetsByID[set.BeatmapSet.ID] = set; + BeatmapSetsByID.Add(set.BeatmapSet.ID, set); base.AddChild(i); } From dde10d1ba214691e8f41616a0f3fe5b1949867cc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 16:38:07 +0900 Subject: [PATCH 543/996] Remove unused `IRealmFactory` interface --- osu.Game/Database/IRealmFactory.cs | 20 -------------------- osu.Game/Database/RealmContextFactory.cs | 2 +- 2 files changed, 1 insertion(+), 21 deletions(-) delete mode 100644 osu.Game/Database/IRealmFactory.cs diff --git a/osu.Game/Database/IRealmFactory.cs b/osu.Game/Database/IRealmFactory.cs deleted file mode 100644 index a957424584..0000000000 --- a/osu.Game/Database/IRealmFactory.cs +++ /dev/null @@ -1,20 +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 Realms; - -namespace osu.Game.Database -{ - public interface IRealmFactory - { - /// - /// The main realm context, bound to the update thread. - /// - Realm Context { get; } - - /// - /// Create a new realm context for use on the current thread. - /// - Realm CreateContext(); - } -} diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 50e456a0c8..c1b159eac7 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -30,7 +30,7 @@ namespace osu.Game.Database /// /// A factory which provides both the main (update thread bound) realm context and creates contexts for async usage. /// - public class RealmContextFactory : IDisposable, IRealmFactory + public class RealmContextFactory : IDisposable { private readonly Storage storage; From a59105635e36c31baf63217ea74d472ec09dc239 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 16:40:20 +0900 Subject: [PATCH 544/996] Make `CreateContext` private --- osu.Game/Database/RealmContextFactory.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index c1b159eac7..5c9d2d7c5a 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -72,13 +72,13 @@ namespace osu.Game.Database get { if (!ThreadSafety.IsUpdateThread) - throw new InvalidOperationException(@$"Use {nameof(CreateContext)} when performing realm operations from a non-update thread"); + throw new InvalidOperationException(@$"Use {nameof(createContext)} when performing realm operations from a non-update thread"); lock (contextLock) { if (context == null) { - context = CreateContext(); + context = createContext(); Logger.Log(@$"Opened realm ""{context.Config.DatabasePath}"" at version {context.Config.SchemaVersion}"); } @@ -124,7 +124,7 @@ namespace osu.Game.Database private void cleanupPendingDeletions() { - using (var realm = CreateContext()) + using (var realm = createContext()) using (var transaction = realm.BeginWrite()) { var pendingDeleteScores = realm.All().Where(s => s.DeletePending); @@ -182,7 +182,7 @@ namespace osu.Game.Database if (ThreadSafety.IsUpdateThread) return action(Context); - using (var realm = CreateContext()) + using (var realm = createContext()) return action(realm); } @@ -199,12 +199,12 @@ namespace osu.Game.Database action(Context); else { - using (var realm = CreateContext()) + using (var realm = createContext()) action(realm); } } - public Realm CreateContext() + private Realm createContext() { if (isDisposed) throw new ObjectDisposedException(nameof(RealmContextFactory)); From da0a803e6c2df35194fd5a21e98f5940955d9f5f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 17:08:02 +0900 Subject: [PATCH 545/996] Add `RealmContextFactory.Write` helper method --- osu.Game/Database/RealmContextFactory.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 5c9d2d7c5a..ea33fec2a0 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -204,6 +204,24 @@ namespace osu.Game.Database } } + /// + /// Write changes to realm. + /// + /// + /// Handles correct context management and transaction committing automatically. + /// + /// The work to run. + public void Write(Action action) + { + if (ThreadSafety.IsUpdateThread) + Context.Write(action); + else + { + using (var realm = createContext()) + realm.Write(action); + } + } + private Realm createContext() { if (isDisposed) From 114c9e8c1f985b2df61cd8e65e9a3bef9f94bb57 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 17:08:20 +0900 Subject: [PATCH 546/996] Update all usages of `CreateContext` to use either `Run` or `Write` --- .../Beatmaps/IO/BeatmapImportHelper.cs | 3 +- osu.Game.Tests/Database/GeneralUsageTests.cs | 20 ++-- osu.Game.Tests/Database/RealmLiveTests.cs | 57 +++++----- .../Database/TestRealmKeyBindingStore.cs | 27 ++--- ...eneOnlinePlayBeatmapAvailabilityTracker.cs | 4 +- .../Visual/Ranking/TestSceneResultsScreen.cs | 4 +- .../TestSceneDeleteLocalScore.cs | 4 +- osu.Game/Beatmaps/BeatmapManager.cs | 26 ++--- osu.Game/Beatmaps/BeatmapModelManager.cs | 4 +- osu.Game/Database/EFToRealmMigrator.cs | 104 +++++++++-------- osu.Game/Database/RealmLive.cs | 7 +- osu.Game/Input/RealmKeyBindingStore.cs | 36 +++--- .../Sections/Input/KeyBindingsSubsection.cs | 5 +- .../Configuration/RulesetConfigManager.cs | 18 +-- osu.Game/Rulesets/RulesetStore.cs | 107 +++++++++--------- osu.Game/Scoring/ScoreManager.cs | 11 +- osu.Game/Screens/Select/BeatmapCarousel.cs | 3 +- .../Select/Leaderboards/BeatmapLeaderboard.cs | 6 +- osu.Game/Skinning/SkinManager.cs | 19 ++-- osu.Game/Skinning/SkinModelManager.cs | 4 +- osu.Game/Stores/RealmArchiveModelImporter.cs | 4 +- osu.Game/Stores/RealmFileStore.cs | 7 +- 22 files changed, 230 insertions(+), 250 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/IO/BeatmapImportHelper.cs b/osu.Game.Tests/Beatmaps/IO/BeatmapImportHelper.cs index 44f6943871..7aa2dc7093 100644 --- a/osu.Game.Tests/Beatmaps/IO/BeatmapImportHelper.cs +++ b/osu.Game.Tests/Beatmaps/IO/BeatmapImportHelper.cs @@ -55,8 +55,7 @@ namespace osu.Game.Tests.Beatmaps.IO { var realmContextFactory = osu.Dependencies.Get(); - using (var realm = realmContextFactory.CreateContext()) - BeatmapImporterTests.EnsureLoaded(realm, timeout); + realmContextFactory.Run(realm => BeatmapImporterTests.EnsureLoaded(realm, timeout)); // TODO: add back some extra checks outside of the realm ones? // var set = queryBeatmapSets().First(); diff --git a/osu.Game.Tests/Database/GeneralUsageTests.cs b/osu.Game.Tests/Database/GeneralUsageTests.cs index 0961ad71e4..9ebe94b383 100644 --- a/osu.Game.Tests/Database/GeneralUsageTests.cs +++ b/osu.Game.Tests/Database/GeneralUsageTests.cs @@ -21,7 +21,7 @@ namespace osu.Game.Tests.Database [Test] public void TestConstructRealm() { - RunTestWithRealm((realmFactory, _) => { realmFactory.CreateContext().Refresh(); }); + RunTestWithRealm((realmFactory, _) => { realmFactory.Run(realm => realm.Refresh()); }); } [Test] @@ -46,23 +46,21 @@ namespace osu.Game.Tests.Database { bool callbackRan = false; - using (var context = realmFactory.CreateContext()) + realmFactory.Run(realm => { - var subscription = context.All().QueryAsyncWithNotifications((sender, changes, error) => + var subscription = realm.All().QueryAsyncWithNotifications((sender, changes, error) => { - using (realmFactory.CreateContext()) + realmFactory.Run(_ => { callbackRan = true; - } + }); }); // Force the callback above to run. - using (realmFactory.CreateContext()) - { - } + realmFactory.Run(r => r.Refresh()); subscription?.Dispose(); - } + }); Assert.IsTrue(callbackRan); }); @@ -78,12 +76,12 @@ namespace osu.Game.Tests.Database Task.Factory.StartNew(() => { - using (realmFactory.CreateContext()) + realmFactory.Run(_ => { hasThreadedUsage.Set(); stopThreadedUsage.Wait(); - } + }); }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler); hasThreadedUsage.Wait(); diff --git a/osu.Game.Tests/Database/RealmLiveTests.cs b/osu.Game.Tests/Database/RealmLiveTests.cs index 187fcd3ca7..2f16df4624 100644 --- a/osu.Game.Tests/Database/RealmLiveTests.cs +++ b/osu.Game.Tests/Database/RealmLiveTests.cs @@ -23,9 +23,9 @@ namespace osu.Game.Tests.Database { RunTestWithRealm((realmFactory, _) => { - ILive beatmap = realmFactory.CreateContext().Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()))).ToLive(realmFactory); + ILive beatmap = realmFactory.Run(realm => realm.Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()))).ToLive(realmFactory)); - ILive beatmap2 = realmFactory.CreateContext().All().First().ToLive(realmFactory); + ILive beatmap2 = realmFactory.Run(realm => realm.All().First().ToLive(realmFactory)); Assert.AreEqual(beatmap, beatmap2); }); @@ -38,14 +38,14 @@ namespace osu.Game.Tests.Database { var beatmap = new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()); - ILive liveBeatmap; + ILive? liveBeatmap = null; - using (var context = realmFactory.CreateContext()) + realmFactory.Run(realm => { - context.Write(r => r.Add(beatmap)); + realm.Write(r => r.Add(beatmap)); liveBeatmap = beatmap.ToLive(realmFactory); - } + }); using (var migratedStorage = new TemporaryNativeStorage("realm-test-migration-target")) { @@ -53,7 +53,7 @@ namespace osu.Game.Tests.Database storage.Migrate(migratedStorage); - Assert.IsFalse(liveBeatmap.PerformRead(l => l.Hidden)); + Assert.IsFalse(liveBeatmap?.PerformRead(l => l.Hidden)); } }); } @@ -67,8 +67,7 @@ namespace osu.Game.Tests.Database var liveBeatmap = beatmap.ToLive(realmFactory); - using (var context = realmFactory.CreateContext()) - context.Write(r => r.Add(beatmap)); + realmFactory.Run(realm => realm.Write(r => r.Add(beatmap))); Assert.IsFalse(liveBeatmap.PerformRead(l => l.Hidden)); }); @@ -99,12 +98,12 @@ namespace osu.Game.Tests.Database ILive? liveBeatmap = null; Task.Factory.StartNew(() => { - using (var threadContext = realmFactory.CreateContext()) + realmFactory.Run(threadContext => { var beatmap = threadContext.Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()))); liveBeatmap = beatmap.ToLive(realmFactory); - } + }); }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely(); Debug.Assert(liveBeatmap != null); @@ -128,12 +127,12 @@ namespace osu.Game.Tests.Database ILive? liveBeatmap = null; Task.Factory.StartNew(() => { - using (var threadContext = realmFactory.CreateContext()) + realmFactory.Run(threadContext => { var beatmap = threadContext.Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()))); liveBeatmap = beatmap.ToLive(realmFactory); - } + }); }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely(); Debug.Assert(liveBeatmap != null); @@ -170,12 +169,12 @@ namespace osu.Game.Tests.Database Task.Factory.StartNew(() => { - using (var threadContext = realmFactory.CreateContext()) + realmFactory.Run(threadContext => { var beatmap = threadContext.Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()))); liveBeatmap = beatmap.ToLive(realmFactory); - } + }); }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely(); Debug.Assert(liveBeatmap != null); @@ -189,13 +188,13 @@ namespace osu.Game.Tests.Database }); // Can't be used, even from within a valid context. - using (realmFactory.CreateContext()) + realmFactory.Run(threadContext => { Assert.Throws(() => { var __ = liveBeatmap.Value; }); - } + }); }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely(); }); } @@ -208,12 +207,12 @@ namespace osu.Game.Tests.Database ILive? liveBeatmap = null; Task.Factory.StartNew(() => { - using (var threadContext = realmFactory.CreateContext()) + realmFactory.Run(threadContext => { var beatmap = threadContext.Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()))); liveBeatmap = beatmap.ToLive(realmFactory); - } + }); }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely(); Debug.Assert(liveBeatmap != null); @@ -235,50 +234,50 @@ namespace osu.Game.Tests.Database { int changesTriggered = 0; - using (var updateThreadContext = realmFactory.CreateContext()) + realmFactory.Run(outerRealm => { - updateThreadContext.All().QueryAsyncWithNotifications(gotChange); + outerRealm.All().QueryAsyncWithNotifications(gotChange); ILive? liveBeatmap = null; Task.Factory.StartNew(() => { - using (var threadContext = realmFactory.CreateContext()) + realmFactory.Run(innerRealm => { var ruleset = CreateRuleset(); - var beatmap = threadContext.Write(r => r.Add(new BeatmapInfo(ruleset, new BeatmapDifficulty(), new BeatmapMetadata()))); + var beatmap = innerRealm.Write(r => r.Add(new BeatmapInfo(ruleset, new BeatmapDifficulty(), new BeatmapMetadata()))); // add a second beatmap to ensure that a full refresh occurs below. // not just a refresh from the resolved Live. - threadContext.Write(r => r.Add(new BeatmapInfo(ruleset, new BeatmapDifficulty(), new BeatmapMetadata()))); + innerRealm.Write(r => r.Add(new BeatmapInfo(ruleset, new BeatmapDifficulty(), new BeatmapMetadata()))); liveBeatmap = beatmap.ToLive(realmFactory); - } + }); }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely(); Debug.Assert(liveBeatmap != null); // not yet seen by main context - Assert.AreEqual(0, updateThreadContext.All().Count()); + Assert.AreEqual(0, outerRealm.All().Count()); Assert.AreEqual(0, changesTriggered); liveBeatmap.PerformRead(resolved => { // retrieval causes an implicit refresh. even changes that aren't related to the retrieval are fired at this point. // ReSharper disable once AccessToDisposedClosure - Assert.AreEqual(2, updateThreadContext.All().Count()); + Assert.AreEqual(2, outerRealm.All().Count()); Assert.AreEqual(1, changesTriggered); // can access properties without a crash. Assert.IsFalse(resolved.Hidden); // ReSharper disable once AccessToDisposedClosure - updateThreadContext.Write(r => + outerRealm.Write(r => { // can use with the main context. r.Remove(resolved); }); }); - } + }); void gotChange(IRealmCollection sender, ChangeSet changes, Exception error) { diff --git a/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs index e3c1d42667..c1041e9fd6 100644 --- a/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs +++ b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs @@ -60,15 +60,12 @@ namespace osu.Game.Tests.Database KeyBindingContainer testContainer = new TestKeyBindingContainer(); // Add some excess bindings for an action which only supports 1. - using (var realm = realmContextFactory.CreateContext()) - using (var transaction = realm.BeginWrite()) + realmContextFactory.Write(realm => { realm.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.A))); realm.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.S))); realm.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.D))); - - transaction.Commit(); - } + }); Assert.That(queryCount(GlobalAction.Back), Is.EqualTo(3)); @@ -79,13 +76,13 @@ namespace osu.Game.Tests.Database private int queryCount(GlobalAction? match = null) { - using (var realm = realmContextFactory.CreateContext()) + return realmContextFactory.Run(realm => { var results = realm.All(); if (match.HasValue) results = results.Where(k => k.ActionInt == (int)match.Value); return results.Count(); - } + }); } [Test] @@ -95,26 +92,26 @@ namespace osu.Game.Tests.Database keyBindingStore.Register(testContainer, Enumerable.Empty()); - using (var primaryRealm = realmContextFactory.CreateContext()) + realmContextFactory.Run(outerRealm => { - var backBinding = primaryRealm.All().Single(k => k.ActionInt == (int)GlobalAction.Back); + var backBinding = outerRealm.All().Single(k => k.ActionInt == (int)GlobalAction.Back); Assert.That(backBinding.KeyCombination.Keys, Is.EquivalentTo(new[] { InputKey.Escape })); var tsr = ThreadSafeReference.Create(backBinding); - using (var threadedContext = realmContextFactory.CreateContext()) + realmContextFactory.Run(innerRealm => { - var binding = threadedContext.ResolveReference(tsr); - threadedContext.Write(() => binding.KeyCombination = new KeyCombination(InputKey.BackSpace)); - } + var binding = innerRealm.ResolveReference(tsr); + innerRealm.Write(() => binding.KeyCombination = new KeyCombination(InputKey.BackSpace)); + }); Assert.That(backBinding.KeyCombination.Keys, Is.EquivalentTo(new[] { InputKey.BackSpace })); // check still correct after re-query. - backBinding = primaryRealm.All().Single(k => k.ActionInt == (int)GlobalAction.Back); + backBinding = outerRealm.All().Single(k => k.ActionInt == (int)GlobalAction.Back); Assert.That(backBinding.KeyCombination.Keys, Is.EquivalentTo(new[] { InputKey.BackSpace })); - } + }); } [TearDown] diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index 8c24b2eef8..1d639c6418 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -60,8 +60,8 @@ namespace osu.Game.Tests.Online testBeatmapInfo = getTestBeatmapInfo(testBeatmapFile); testBeatmapSet = testBeatmapInfo.BeatmapSet; - ContextFactory.Context.Write(r => r.RemoveAll()); - ContextFactory.Context.Write(r => r.RemoveAll()); + ContextFactory.Write(r => r.RemoveAll()); + ContextFactory.Write(r => r.RemoveAll()); selectedItem.Value = new PlaylistItem { diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index 62500babc1..a77480ee54 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs @@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.Ranking { base.LoadComplete(); - using (var realm = realmContextFactory.CreateContext()) + realmContextFactory.Run(realm => { var beatmapInfo = realm.All() .Filter($"{nameof(BeatmapInfo.Ruleset)}.{nameof(RulesetInfo.OnlineID)} = $0", 0) @@ -50,7 +50,7 @@ namespace osu.Game.Tests.Visual.Ranking if (beatmapInfo != null) Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmapInfo); - } + }); } [Test] diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index 1e14e4b3e5..f43354514b 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -122,11 +122,11 @@ namespace osu.Game.Tests.Visual.UserInterface [SetUp] public void Setup() => Schedule(() => { - using (var realm = realmFactory.CreateContext()) + realmFactory.Run(realm => { // Due to soft deletions, we can re-use deleted scores between test runs scoreManager.Undelete(realm.All().Where(s => s.DeletePending).ToList()); - } + }); leaderboard.Scores = null; leaderboard.FinishTransforms(true); // After setting scores, we may be waiting for transforms to expire drawables diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index cc765657cc..a9340e1250 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -153,14 +153,16 @@ namespace osu.Game.Beatmaps public void RestoreAll() { - using (var realm = contextFactory.CreateContext()) - using (var transaction = realm.BeginWrite()) + contextFactory.Run(realm => { - foreach (var beatmap in realm.All().Where(b => b.Hidden)) - beatmap.Hidden = false; + using (var transaction = realm.BeginWrite()) + { + foreach (var beatmap in realm.All().Where(b => b.Hidden)) + beatmap.Hidden = false; - transaction.Commit(); - } + transaction.Commit(); + } + }); } /// @@ -169,8 +171,7 @@ namespace osu.Game.Beatmaps /// A list of available . public List GetAllUsableBeatmapSets() { - using (var context = contextFactory.CreateContext()) - return context.All().Where(b => !b.DeletePending).Detach(); + return contextFactory.Run(realm => realm.All().Where(b => !b.DeletePending).Detach()); } /// @@ -235,21 +236,20 @@ namespace osu.Game.Beatmaps public void Delete(Expression>? filter = null, bool silent = false) { - using (var context = contextFactory.CreateContext()) + contextFactory.Run(realm => { - var items = context.All().Where(s => !s.DeletePending && !s.Protected); + var items = realm.All().Where(s => !s.DeletePending && !s.Protected); if (filter != null) items = items.Where(filter); beatmapModelManager.Delete(items.ToList(), silent); - } + }); } public void UndeleteAll() { - using (var context = contextFactory.CreateContext()) - beatmapModelManager.Undelete(context.All().Where(s => s.DeletePending).ToList()); + contextFactory.Run(realm => beatmapModelManager.Undelete(realm.All().Where(s => s.DeletePending).ToList())); } public void Undelete(List items, bool silent = false) diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs index a4ba13a88d..44d6af5b73 100644 --- a/osu.Game/Beatmaps/BeatmapModelManager.cs +++ b/osu.Game/Beatmaps/BeatmapModelManager.cs @@ -103,10 +103,10 @@ namespace osu.Game.Beatmaps public void Update(BeatmapSetInfo item) { - ContextFactory.Run(realm => + ContextFactory.Write(realm => { var existing = realm.Find(item.ID); - realm.Write(r => item.CopyChangesToRealm(existing)); + item.CopyChangesToRealm(existing); }); } } diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index 0f726f8ee5..58321efcc9 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -73,7 +73,7 @@ namespace osu.Game.Database int count = existingBeatmapSets.Count(); - using (var realm = realmContextFactory.CreateContext()) + realmContextFactory.Run(realm => { Logger.Log($"Found {count} beatmaps in EF", LoggingTarget.Database); @@ -160,7 +160,7 @@ namespace osu.Game.Database Logger.Log($"Successfully migrated {count} beatmaps to realm", LoggingTarget.Database); } - } + }); } private BeatmapMetadata getBestMetadata(EFBeatmapMetadata? beatmapMetadata, EFBeatmapMetadata? beatmapSetMetadata) @@ -206,7 +206,7 @@ namespace osu.Game.Database int count = existingScores.Count(); - using (var realm = realmContextFactory.CreateContext()) + realmContextFactory.Run(realm => { Logger.Log($"Found {count} scores in EF", LoggingTarget.Database); @@ -276,7 +276,7 @@ namespace osu.Game.Database Logger.Log($"Successfully migrated {count} scores to realm", LoggingTarget.Database); } - } + }); } private void migrateSkins(OsuDbContext db) @@ -307,37 +307,39 @@ namespace osu.Game.Database break; } - using (var realm = realmContextFactory.CreateContext()) - using (var transaction = realm.BeginWrite()) + realmContextFactory.Run(realm => { - // only migrate data if the realm database is empty. - // note that this cannot be written as: `realm.All().All(s => s.Protected)`, because realm does not support `.All()`. - if (!realm.All().Any(s => !s.Protected)) + using (var transaction = realm.BeginWrite()) { - Logger.Log($"Migrating {existingSkins.Count} skins", LoggingTarget.Database); - - foreach (var skin in existingSkins) + // only migrate data if the realm database is empty. + // note that this cannot be written as: `realm.All().All(s => s.Protected)`, because realm does not support `.All()`. + if (!realm.All().Any(s => !s.Protected)) { - var realmSkin = new SkinInfo + Logger.Log($"Migrating {existingSkins.Count} skins", LoggingTarget.Database); + + foreach (var skin in existingSkins) { - Name = skin.Name, - Creator = skin.Creator, - Hash = skin.Hash, - Protected = false, - InstantiationInfo = skin.InstantiationInfo, - }; + var realmSkin = new SkinInfo + { + Name = skin.Name, + Creator = skin.Creator, + Hash = skin.Hash, + Protected = false, + InstantiationInfo = skin.InstantiationInfo, + }; - migrateFiles(skin, realm, realmSkin); + migrateFiles(skin, realm, realmSkin); - realm.Add(realmSkin); + realm.Add(realmSkin); - if (skin.ID == userSkinInt) - userSkinChoice.Value = realmSkin.ID.ToString(); + if (skin.ID == userSkinInt) + userSkinChoice.Value = realmSkin.ID.ToString(); + } } - } - transaction.Commit(); - } + transaction.Commit(); + } + }); } private static void migrateFiles(IHasFiles fileSource, Realm realm, IHasRealmFiles realmObject) where T : INamedFileInfo @@ -365,36 +367,38 @@ namespace osu.Game.Database Logger.Log("Beginning settings migration to realm", LoggingTarget.Database); ensureBackup(); - using (var realm = realmContextFactory.CreateContext()) - using (var transaction = realm.BeginWrite()) + realmContextFactory.Run(realm => { - // only migrate data if the realm database is empty. - if (!realm.All().Any()) + using (var transaction = realm.BeginWrite()) { - Logger.Log($"Migrating {existingSettings.Count} settings", LoggingTarget.Database); - - foreach (var dkb in existingSettings) + // only migrate data if the realm database is empty. + if (!realm.All().Any()) { - if (dkb.RulesetID == null) - continue; + Logger.Log($"Migrating {existingSettings.Count} settings", LoggingTarget.Database); - string? shortName = getRulesetShortNameFromLegacyID(dkb.RulesetID.Value); - - if (string.IsNullOrEmpty(shortName)) - continue; - - realm.Add(new RealmRulesetSetting + foreach (var dkb in existingSettings) { - Key = dkb.Key, - Value = dkb.StringValue, - RulesetName = shortName, - Variant = dkb.Variant ?? 0, - }); - } - } + if (dkb.RulesetID == null) + continue; - transaction.Commit(); - } + string? shortName = getRulesetShortNameFromLegacyID(dkb.RulesetID.Value); + + if (string.IsNullOrEmpty(shortName)) + continue; + + realm.Add(new RealmRulesetSetting + { + Key = dkb.Key, + Value = dkb.StringValue, + RulesetName = shortName, + Variant = dkb.Variant ?? 0, + }); + } + } + + transaction.Commit(); + } + }); } private string? getRulesetShortNameFromLegacyID(long rulesetId) => diff --git a/osu.Game/Database/RealmLive.cs b/osu.Game/Database/RealmLive.cs index 6594224666..05367160f3 100644 --- a/osu.Game/Database/RealmLive.cs +++ b/osu.Game/Database/RealmLive.cs @@ -51,8 +51,7 @@ namespace osu.Game.Database return; } - using (var realm = realmFactory.CreateContext()) - perform(realm.Find(ID)); + realmFactory.Run(realm => perform(realm.Find(ID))); } /// @@ -64,7 +63,7 @@ namespace osu.Game.Database if (!IsManaged) return perform(data); - using (var realm = realmFactory.CreateContext()) + return realmFactory.Run(realm => { var returnData = perform(realm.Find(ID)); @@ -72,7 +71,7 @@ namespace osu.Game.Database throw new InvalidOperationException(@$"Managed realm objects should not exit the scope of {nameof(PerformRead)}."); return returnData; - } + }); } /// diff --git a/osu.Game/Input/RealmKeyBindingStore.cs b/osu.Game/Input/RealmKeyBindingStore.cs index 99f5752cfb..60f7eb2198 100644 --- a/osu.Game/Input/RealmKeyBindingStore.cs +++ b/osu.Game/Input/RealmKeyBindingStore.cs @@ -34,7 +34,7 @@ namespace osu.Game.Input { List combinations = new List(); - using (var context = realmFactory.CreateContext()) + realmFactory.Run(context => { foreach (var action in context.All().Where(b => string.IsNullOrEmpty(b.RulesetName) && (GlobalAction)b.ActionInt == globalAction)) { @@ -44,7 +44,7 @@ namespace osu.Game.Input if (str.Length > 0) combinations.Add(str); } - } + }); return combinations; } @@ -56,24 +56,26 @@ namespace osu.Game.Input /// The rulesets to populate defaults from. public void Register(KeyBindingContainer container, IEnumerable rulesets) { - using (var realm = realmFactory.CreateContext()) - using (var transaction = realm.BeginWrite()) + realmFactory.Run(realm => { - // intentionally flattened to a list rather than querying against the IQueryable, as nullable fields being queried against aren't indexed. - // this is much faster as a result. - var existingBindings = realm.All().ToList(); - - insertDefaults(realm, existingBindings, container.DefaultKeyBindings); - - foreach (var ruleset in rulesets) + using (var transaction = realm.BeginWrite()) { - var instance = ruleset.CreateInstance(); - foreach (int variant in instance.AvailableVariants) - insertDefaults(realm, existingBindings, instance.GetDefaultKeyBindings(variant), ruleset.ShortName, variant); - } + // intentionally flattened to a list rather than querying against the IQueryable, as nullable fields being queried against aren't indexed. + // this is much faster as a result. + var existingBindings = realm.All().ToList(); - transaction.Commit(); - } + insertDefaults(realm, existingBindings, container.DefaultKeyBindings); + + foreach (var ruleset in rulesets) + { + var instance = ruleset.CreateInstance(); + foreach (int variant in instance.AvailableVariants) + insertDefaults(realm, existingBindings, instance.GetDefaultKeyBindings(variant), ruleset.ShortName, variant); + } + + transaction.Commit(); + } + }); } private void insertDefaults(Realm realm, List existingBindings, IEnumerable defaults, string? rulesetName = null, int? variant = null) diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs index 94c7c66538..9075dfefd4 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs @@ -34,10 +34,9 @@ namespace osu.Game.Overlays.Settings.Sections.Input { string rulesetName = Ruleset?.ShortName; - List bindings; + List bindings = null; - using (var realm = realmFactory.CreateContext()) - bindings = realm.All().Where(b => b.RulesetName == rulesetName && b.Variant == variant).Detach(); + realmFactory.Run(realm => bindings = realm.All().Where(b => b.RulesetName == rulesetName && b.Variant == variant).Detach()); foreach (var defaultGroup in Defaults.GroupBy(d => d.Action)) { diff --git a/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs b/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs index 17678775e9..60a6b70221 100644 --- a/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs +++ b/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs @@ -56,21 +56,15 @@ namespace osu.Game.Rulesets.Configuration pendingWrites.Clear(); } - if (realmFactory == null) - return true; - - using (var context = realmFactory.CreateContext()) + realmFactory?.Write(realm => { - context.Write(realm => + foreach (var c in changed) { - foreach (var c in changed) - { - var setting = realm.All().First(s => s.RulesetName == rulesetName && s.Variant == variant && s.Key == c.ToString()); + var setting = realm.All().First(s => s.RulesetName == rulesetName && s.Variant == variant && s.Key == c.ToString()); - setting.Value = ConfigStore[c].ToString(); - } - }); - } + setting.Value = ConfigStore[c].ToString(); + } + }); return true; } diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index c675fbbf63..a9e5ff797c 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -100,74 +100,71 @@ namespace osu.Game.Rulesets private void addMissingRulesets() { - using (var context = realmFactory.CreateContext()) + realmFactory.Write(realm => { - context.Write(realm => + var rulesets = realm.All(); + + List instances = loadedAssemblies.Values + .Select(r => Activator.CreateInstance(r) as Ruleset) + .Where(r => r != null) + .Select(r => r.AsNonNull()) + .ToList(); + + // add all legacy rulesets first to ensure they have exclusive choice of primary key. + foreach (var r in instances.Where(r => r is ILegacyRuleset)) { - var rulesets = realm.All(); + if (realm.All().FirstOrDefault(rr => rr.OnlineID == r.RulesetInfo.OnlineID) == null) + realm.Add(new RulesetInfo(r.RulesetInfo.ShortName, r.RulesetInfo.Name, r.RulesetInfo.InstantiationInfo, r.RulesetInfo.OnlineID)); + } - List instances = loadedAssemblies.Values - .Select(r => Activator.CreateInstance(r) as Ruleset) - .Where(r => r != null) - .Select(r => r.AsNonNull()) - .ToList(); - - // add all legacy rulesets first to ensure they have exclusive choice of primary key. - foreach (var r in instances.Where(r => r is ILegacyRuleset)) + // add any other rulesets which have assemblies present but are not yet in the database. + foreach (var r in instances.Where(r => !(r is ILegacyRuleset))) + { + if (rulesets.FirstOrDefault(ri => ri.InstantiationInfo.Equals(r.RulesetInfo.InstantiationInfo, StringComparison.Ordinal)) == null) { - if (realm.All().FirstOrDefault(rr => rr.OnlineID == r.RulesetInfo.OnlineID) == null) + var existingSameShortName = rulesets.FirstOrDefault(ri => ri.ShortName == r.RulesetInfo.ShortName); + + if (existingSameShortName != null) + { + // even if a matching InstantiationInfo was not found, there may be an existing ruleset with the same ShortName. + // this generally means the user or ruleset provider has renamed their dll but the underlying ruleset is *likely* the same one. + // in such cases, update the instantiation info of the existing entry to point to the new one. + existingSameShortName.InstantiationInfo = r.RulesetInfo.InstantiationInfo; + } + else realm.Add(new RulesetInfo(r.RulesetInfo.ShortName, r.RulesetInfo.Name, r.RulesetInfo.InstantiationInfo, r.RulesetInfo.OnlineID)); } + } - // add any other rulesets which have assemblies present but are not yet in the database. - foreach (var r in instances.Where(r => !(r is ILegacyRuleset))) + List detachedRulesets = new List(); + + // perform a consistency check and detach final rulesets from realm for cross-thread runtime usage. + foreach (var r in rulesets.OrderBy(r => r.OnlineID)) + { + try { - if (rulesets.FirstOrDefault(ri => ri.InstantiationInfo.Equals(r.RulesetInfo.InstantiationInfo, StringComparison.Ordinal)) == null) - { - var existingSameShortName = rulesets.FirstOrDefault(ri => ri.ShortName == r.RulesetInfo.ShortName); + var resolvedType = Type.GetType(r.InstantiationInfo) + ?? throw new RulesetLoadException(@"Type could not be resolved"); - if (existingSameShortName != null) - { - // even if a matching InstantiationInfo was not found, there may be an existing ruleset with the same ShortName. - // this generally means the user or ruleset provider has renamed their dll but the underlying ruleset is *likely* the same one. - // in such cases, update the instantiation info of the existing entry to point to the new one. - existingSameShortName.InstantiationInfo = r.RulesetInfo.InstantiationInfo; - } - else - realm.Add(new RulesetInfo(r.RulesetInfo.ShortName, r.RulesetInfo.Name, r.RulesetInfo.InstantiationInfo, r.RulesetInfo.OnlineID)); - } + var instanceInfo = (Activator.CreateInstance(resolvedType) as Ruleset)?.RulesetInfo + ?? throw new RulesetLoadException(@"Instantiation failure"); + + r.Name = instanceInfo.Name; + r.ShortName = instanceInfo.ShortName; + r.InstantiationInfo = instanceInfo.InstantiationInfo; + r.Available = true; + + detachedRulesets.Add(r.Clone()); } - - List detachedRulesets = new List(); - - // perform a consistency check and detach final rulesets from realm for cross-thread runtime usage. - foreach (var r in rulesets.OrderBy(r => r.OnlineID)) + catch (Exception ex) { - try - { - var resolvedType = Type.GetType(r.InstantiationInfo) - ?? throw new RulesetLoadException(@"Type could not be resolved"); - - var instanceInfo = (Activator.CreateInstance(resolvedType) as Ruleset)?.RulesetInfo - ?? throw new RulesetLoadException(@"Instantiation failure"); - - r.Name = instanceInfo.Name; - r.ShortName = instanceInfo.ShortName; - r.InstantiationInfo = instanceInfo.InstantiationInfo; - r.Available = true; - - detachedRulesets.Add(r.Clone()); - } - catch (Exception ex) - { - r.Available = false; - Logger.Log($"Could not load ruleset {r}: {ex.Message}"); - } + r.Available = false; + Logger.Log($"Could not load ruleset {r}: {ex.Message}"); } + } - availableRulesets.AddRange(detachedRulesets); - }); - } + availableRulesets.AddRange(detachedRulesets); + }); } private void loadFromAppDomain() diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index ccf3226792..f895134f97 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -51,8 +51,7 @@ namespace osu.Game.Scoring /// The first result for the provided query, or null if no results were found. public ScoreInfo Query(Expression> query) { - using (var context = contextFactory.CreateContext()) - return context.All().FirstOrDefault(query)?.Detach(); + return contextFactory.Run(realm => realm.All().FirstOrDefault(query)?.Detach()); } /// @@ -255,16 +254,16 @@ namespace osu.Game.Scoring public void Delete([CanBeNull] Expression> filter = null, bool silent = false) { - using (var context = contextFactory.CreateContext()) + contextFactory.Run(realm => { - var items = context.All() - .Where(s => !s.DeletePending); + var items = realm.All() + .Where(s => !s.DeletePending); if (filter != null) items = items.Where(filter); scoreModelManager.Delete(items.ToList(), silent); - } + }); } public void Delete(List items, bool silent = false) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 75ad0511e6..fe3d407a64 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -178,8 +178,7 @@ namespace osu.Game.Screens.Select if (!loadedTestBeatmaps) { - using (var realm = realmFactory.CreateContext()) - loadBeatmapSets(getBeatmapSets(realm)); + realmFactory.Run(realm => loadBeatmapSets(getBeatmapSets(realm))); } } diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 49f2ea5d64..da52b43ab6 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -147,7 +147,7 @@ namespace osu.Game.Screens.Select.Leaderboards if (Scope == BeatmapLeaderboardScope.Local) { - using (var realm = realmFactory.CreateContext()) + realmFactory.Run(realm => { var scores = realm.All() .AsEnumerable() @@ -171,9 +171,9 @@ namespace osu.Game.Screens.Select.Leaderboards scoreManager.OrderByTotalScoreAsync(scores.ToArray(), cancellationToken) .ContinueWith(ordered => scoresCallback?.Invoke(ordered.GetResultSafely()), TaskContinuationOptions.OnlyOnRanToCompletion); + }); - return null; - } + return null; } if (api?.IsLoggedIn != true) diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 3f6e5754fb..82bcd3b292 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -87,17 +87,14 @@ namespace osu.Game.Skinning }; // Ensure the default entries are present. - using (var context = contextFactory.CreateContext()) - using (var transaction = context.BeginWrite()) + contextFactory.Write(realm => { foreach (var skin in defaultSkins) { - if (context.Find(skin.SkinInfo.ID) == null) - context.Add(skin.SkinInfo.Value); + if (realm.Find(skin.SkinInfo.ID) == null) + realm.Add(skin.SkinInfo.Value); } - - transaction.Commit(); - } + }); CurrentSkinInfo.ValueChanged += skin => CurrentSkin.Value = skin.NewValue.PerformRead(GetSkin); @@ -292,10 +289,10 @@ namespace osu.Game.Skinning public void Delete([CanBeNull] Expression> filter = null, bool silent = false) { - using (var context = contextFactory.CreateContext()) + contextFactory.Run(realm => { - var items = context.All() - .Where(s => !s.Protected && !s.DeletePending); + var items = realm.All() + .Where(s => !s.Protected && !s.DeletePending); if (filter != null) items = items.Where(filter); @@ -306,7 +303,7 @@ namespace osu.Game.Skinning scheduler.Add(() => CurrentSkinInfo.Value = Skinning.DefaultSkin.CreateInfo().ToLiveUnmanaged()); skinModelManager.Delete(items.ToList(), silent); - } + }); } #endregion diff --git a/osu.Game/Skinning/SkinModelManager.cs b/osu.Game/Skinning/SkinModelManager.cs index b8313f63a3..a1926913a9 100644 --- a/osu.Game/Skinning/SkinModelManager.cs +++ b/osu.Game/Skinning/SkinModelManager.cs @@ -205,7 +205,7 @@ namespace osu.Game.Skinning private void populateMissingHashes() { - using (var realm = ContextFactory.CreateContext()) + ContextFactory.Run(realm => { var skinsWithoutHashes = realm.All().Where(i => !i.Protected && string.IsNullOrEmpty(i.Hash)).ToArray(); @@ -221,7 +221,7 @@ namespace osu.Game.Skinning Logger.Error(e, $"Existing skin {skin} has been deleted during hash recomputation due to being invalid"); } } - } + }); } private Skin createInstance(SkinInfo item) => item.CreateInstance(skinResources); diff --git a/osu.Game/Stores/RealmArchiveModelImporter.cs b/osu.Game/Stores/RealmArchiveModelImporter.cs index 2ea7aecc94..3d8e9f2703 100644 --- a/osu.Game/Stores/RealmArchiveModelImporter.cs +++ b/osu.Game/Stores/RealmArchiveModelImporter.cs @@ -320,7 +320,7 @@ namespace osu.Game.Stores /// An optional cancellation token. public virtual Task?> Import(TModel item, ArchiveReader? archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) { - using (var realm = ContextFactory.CreateContext()) + return ContextFactory.Run(realm => { cancellationToken.ThrowIfCancellationRequested(); @@ -414,7 +414,7 @@ namespace osu.Game.Stores } return Task.FromResult((ILive?)item.ToLive(ContextFactory)); - } + }); } private string computeHashFast(ArchiveReader reader) diff --git a/osu.Game/Stores/RealmFileStore.cs b/osu.Game/Stores/RealmFileStore.cs index f9abbda4c0..ca371e29be 100644 --- a/osu.Game/Stores/RealmFileStore.cs +++ b/osu.Game/Stores/RealmFileStore.cs @@ -92,8 +92,7 @@ namespace osu.Game.Stores int removedFiles = 0; // can potentially be run asynchronously, although we will need to consider operation order for disk deletion vs realm removal. - using (var realm = realmFactory.CreateContext()) - using (var transaction = realm.BeginWrite()) + realmFactory.Write(realm => { // TODO: consider using a realm native query to avoid iterating all files (https://github.com/realm/realm-dotnet/issues/2659#issuecomment-927823707) var files = realm.All().ToList(); @@ -116,9 +115,7 @@ namespace osu.Game.Stores Logger.Error(e, $@"Could not delete databased file {file.Hash}"); } } - - transaction.Commit(); - } + }); Logger.Log($@"Finished realm file store cleanup ({removedFiles} of {totalFiles} deleted)"); } From d2655c082546033490792d6f3e4d1806ba964e8c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 17:27:30 +0900 Subject: [PATCH 547/996] Fix `RealmLive` not necessarily being in refreshed state due to potentially using update context --- osu.Game/Database/RealmLive.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmLive.cs b/osu.Game/Database/RealmLive.cs index 05367160f3..75d84d7cf1 100644 --- a/osu.Game/Database/RealmLive.cs +++ b/osu.Game/Database/RealmLive.cs @@ -51,7 +51,21 @@ namespace osu.Game.Database return; } - realmFactory.Run(realm => perform(realm.Find(ID))); + realmFactory.Run(realm => + { + var found = realm.Find(ID); + + if (found == null) + { + // It may be that we access this from the update thread before a refresh has taken place. + // To ensure that behaviour matches what we'd expect (the object *is* available), force + // a refresh to bring in any off-thread changes immediately. + realm.Refresh(); + found = realm.Find(ID); + } + + perform(found); + }); } /// From 81b5717ae793e334fdcf8efd72d20debfb49e9ca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 17:33:03 +0900 Subject: [PATCH 548/996] Fix `RealmLive` failing to retrieve due to lack of refresh --- osu.Game/Database/RealmLive.cs | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/osu.Game/Database/RealmLive.cs b/osu.Game/Database/RealmLive.cs index 75d84d7cf1..df5e165f8e 100644 --- a/osu.Game/Database/RealmLive.cs +++ b/osu.Game/Database/RealmLive.cs @@ -53,18 +53,7 @@ namespace osu.Game.Database realmFactory.Run(realm => { - var found = realm.Find(ID); - - if (found == null) - { - // It may be that we access this from the update thread before a refresh has taken place. - // To ensure that behaviour matches what we'd expect (the object *is* available), force - // a refresh to bring in any off-thread changes immediately. - realm.Refresh(); - found = realm.Find(ID); - } - - perform(found); + perform(retrieveFromID(realm, ID)); }); } @@ -79,7 +68,7 @@ namespace osu.Game.Database return realmFactory.Run(realm => { - var returnData = perform(realm.Find(ID)); + var returnData = perform(retrieveFromID(realm, ID)); if (returnData is RealmObjectBase realmObject && realmObject.IsManaged) throw new InvalidOperationException(@$"Managed realm objects should not exit the scope of {nameof(PerformRead)}."); @@ -119,6 +108,22 @@ namespace osu.Game.Database } } + private T retrieveFromID(Realm realm, Guid id) + { + var found = realm.Find(ID); + + if (found == null) + { + // It may be that we access this from the update thread before a refresh has taken place. + // To ensure that behaviour matches what we'd expect (the object *is* available), force + // a refresh to bring in any off-thread changes immediately. + realm.Refresh(); + found = realm.Find(ID); + } + + return found; + } + public bool Equals(ILive? other) => ID == other?.ID; public override string ToString() => PerformRead(i => i.ToString()); From 495636538fc23101ec641bdf66eb0ba8b3f6a88c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 17:33:26 +0900 Subject: [PATCH 549/996] Add forced refresh on `GetAllUsableBeatmapSets()` This is commonly used in tests in a way where it's not feasible to guarantee correct results unless a refresh is called. This method shouldn't really be used outside of tests anyway, but that's for a folow-up effort. --- osu.Game/Beatmaps/BeatmapManager.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index a9340e1250..43e4b482bd 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -171,7 +171,11 @@ namespace osu.Game.Beatmaps /// A list of available . public List GetAllUsableBeatmapSets() { - return contextFactory.Run(realm => realm.All().Where(b => !b.DeletePending).Detach()); + return contextFactory.Run(realm => + { + realm.Refresh(); + return realm.All().Where(b => !b.DeletePending).Detach(); + }); } /// From 2006620a2c02d60c83792803b4276cb163a3b4a9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 17:55:27 +0900 Subject: [PATCH 550/996] Fix `IntroScreen` retrieving and iterating all realm beatmap sets --- osu.Game/Screens/Menu/IntroScreen.cs | 82 ++++++++++++++-------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index d98cb8056f..db9d9b83c4 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -21,6 +21,7 @@ using osu.Game.Overlays; using osu.Game.Screens.Backgrounds; using osuTK; using osuTK.Graphics; +using Realms; namespace osu.Game.Screens.Menu { @@ -93,58 +94,57 @@ namespace osu.Game.Screens.Menu MenuMusic = config.GetBindable(OsuSetting.MenuMusic); seeya = audio.Samples.Get(SeeyaSampleName); - ILive setInfo = null; - // if the user has requested not to play theme music, we should attempt to find a random beatmap from their collection. if (!MenuMusic.Value) { - var sets = beatmaps.GetAllUsableBeatmapSets(); - - if (sets.Count > 0) + realmContextFactory.Run(realm => { - setInfo = beatmaps.QueryBeatmapSet(s => s.ID == sets[RNG.Next(0, sets.Count - 1)].ID); - setInfo?.PerformRead(s => + var sets = realm.All().Where(s => !s.DeletePending && !s.Protected).AsRealmCollection(); + + int setCount = sets.Count; + + if (sets.Any()) + { + var found = sets[RNG.Next(0, setCount - 1)].Beatmaps.FirstOrDefault(); + + if (found != null) + initialBeatmap = beatmaps.GetWorkingBeatmap(found); + } + }); + + // we generally want a song to be playing on startup, so use the intro music even if a user has specified not to if no other track is available. + if (initialBeatmap == null) + { + if (!loadThemedIntro()) + { + // if we detect that the theme track or beatmap is unavailable this is either first startup or things are in a bad state. + // this could happen if a user has nuked their files store. for now, reimport to repair this. + var import = beatmaps.Import(new ZipArchiveReader(game.Resources.GetStream($"Tracks/{BeatmapFile}"), BeatmapFile)).GetResultSafely(); + + import?.PerformWrite(b => b.Protected = true); + + loadThemedIntro(); + } + } + + bool loadThemedIntro() + { + var setInfo = beatmaps.QueryBeatmapSet(b => b.Hash == BeatmapHash); + + if (setInfo == null) + return false; + + setInfo.PerformRead(s => { if (s.Beatmaps.Count == 0) return; - initialBeatmap = beatmaps.GetWorkingBeatmap(s.Beatmaps[0]); + initialBeatmap = beatmaps.GetWorkingBeatmap(s.Beatmaps.First()); }); + + return UsingThemedIntro = initialBeatmap != null; } } - - // we generally want a song to be playing on startup, so use the intro music even if a user has specified not to if no other track is available. - if (setInfo == null) - { - if (!loadThemedIntro()) - { - // if we detect that the theme track or beatmap is unavailable this is either first startup or things are in a bad state. - // this could happen if a user has nuked their files store. for now, reimport to repair this. - var import = beatmaps.Import(new ZipArchiveReader(game.Resources.GetStream($"Tracks/{BeatmapFile}"), BeatmapFile)).GetResultSafely(); - - import?.PerformWrite(b => b.Protected = true); - - loadThemedIntro(); - } - } - - bool loadThemedIntro() - { - setInfo = beatmaps.QueryBeatmapSet(b => b.Hash == BeatmapHash); - - if (setInfo == null) - return false; - - setInfo.PerformRead(s => - { - if (s.Beatmaps.Count == 0) - return; - - initialBeatmap = beatmaps.GetWorkingBeatmap(s.Beatmaps.First()); - }); - - return UsingThemedIntro = initialBeatmap != null; - } } public override void OnResuming(IScreen last) From 18bf690a30bc9d5c5c6d53133469fc462bcf62ec Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 18:13:21 +0900 Subject: [PATCH 551/996] Add `Register` method for subscription purposes and update safeties --- osu.Game/Database/RealmContextFactory.cs | 28 ++++++++++++++++++++++ osu.Game/Database/RealmObjectExtensions.cs | 6 ++--- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 888ffb1dd5..169333bff8 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -88,6 +88,10 @@ namespace osu.Game.Database } } + internal static bool CurrentThreadSubscriptionsAllowed => current_thread_subscriptions_allowed.Value; + + private static readonly ThreadLocal current_thread_subscriptions_allowed = new ThreadLocal(); + /// /// Construct a new instance of a realm context factory. /// @@ -222,6 +226,30 @@ namespace osu.Game.Database } } + /// + /// Run work on realm that will be run every time the update thread realm context gets recycled. + /// + /// The work to run. + public void Register(Action action) + { + if (!ThreadSafety.IsUpdateThread && context != null) + throw new InvalidOperationException(@$"{nameof(BlockAllOperations)} must be called from the update thread."); + + if (ThreadSafety.IsUpdateThread) + { + current_thread_subscriptions_allowed.Value = true; + action(Context); + current_thread_subscriptions_allowed.Value = false; + } + else + { + current_thread_subscriptions_allowed.Value = true; + using (var realm = createContext()) + action(realm); + current_thread_subscriptions_allowed.Value = false; + } + } + private Realm createContext() { if (isDisposed) diff --git a/osu.Game/Database/RealmObjectExtensions.cs b/osu.Game/Database/RealmObjectExtensions.cs index c25aeab336..c30e1699b9 100644 --- a/osu.Game/Database/RealmObjectExtensions.cs +++ b/osu.Game/Database/RealmObjectExtensions.cs @@ -7,7 +7,6 @@ using System.Linq; using System.Runtime.Serialization; using AutoMapper; using AutoMapper.Internal; -using osu.Framework.Development; using osu.Game.Beatmaps; using osu.Game.Input.Bindings; using osu.Game.Models; @@ -272,9 +271,8 @@ namespace osu.Game.Database public static IDisposable? QueryAsyncWithNotifications(this IRealmCollection collection, NotificationCallbackDelegate callback) where T : RealmObjectBase { - // Subscriptions can only work on the main thread. - if (!ThreadSafety.IsUpdateThread) - throw new InvalidOperationException("Cannot subscribe for realm notifications from a non-update thread."); + if (!RealmContextFactory.CurrentThreadSubscriptionsAllowed) + throw new InvalidOperationException($"Make sure to call {nameof(RealmContextFactory)}.{nameof(RealmContextFactory.Register)}"); return collection.SubscribeForNotifications(callback); } From 45aea9add5209953d7e76a093a74f5ad91732a60 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 18:50:25 +0900 Subject: [PATCH 552/996] Implement full subscription flow --- osu.Game/Database/RealmContextFactory.cs | 46 +++++++++++++++++------- 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 169333bff8..62717eb880 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -2,6 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; @@ -80,6 +82,10 @@ namespace osu.Game.Database { context = createContext(); Logger.Log(@$"Opened realm ""{context.Config.DatabasePath}"" at version {context.Config.SchemaVersion}"); + + // Resubscribe any subscriptions + foreach (var action in subscriptionActions.Keys) + registerSubscription(action); } // creating a context will ensure our schema is up-to-date and migrated. @@ -226,26 +232,42 @@ namespace osu.Game.Database } } + private readonly Dictionary, IDisposable?> subscriptionActions = new Dictionary, IDisposable?>(); + /// /// Run work on realm that will be run every time the update thread realm context gets recycled. /// - /// The work to run. - public void Register(Action action) + /// The work to run. Return value should be an from QueryAsyncWithNotifications, or an to clean up any bindings. + /// An which should be disposed to unsubscribe any inner subscription. + public IDisposable Register(Func action) { - if (!ThreadSafety.IsUpdateThread && context != null) - throw new InvalidOperationException(@$"{nameof(BlockAllOperations)} must be called from the update thread."); + if (!ThreadSafety.IsUpdateThread) + throw new InvalidOperationException(@$"{nameof(Register)} must be called from the update thread."); - if (ThreadSafety.IsUpdateThread) + subscriptionActions.Add(action, null); + registerSubscription(action); + + return new InvokeOnDisposal(() => { - current_thread_subscriptions_allowed.Value = true; - action(Context); - current_thread_subscriptions_allowed.Value = false; - } - else + // TODO: this likely needs to be run on the update thread. + if (subscriptionActions.TryGetValue(action, out var unsubscriptionAction)) + { + unsubscriptionAction?.Dispose(); + subscriptionActions.Remove(action); + } + }); + } + + private void registerSubscription(Func action) + { + Debug.Assert(ThreadSafety.IsUpdateThread); + + lock (contextLock) { + Debug.Assert(context != null); + current_thread_subscriptions_allowed.Value = true; - using (var realm = createContext()) - action(realm); + subscriptionActions[action] = action(context); current_thread_subscriptions_allowed.Value = false; } } From 1f157d729dbc0d1bc2cdbed374b41bbef650e121 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 19:39:49 +0900 Subject: [PATCH 553/996] Update existing subscriptions to new style Fix missing detach calls in `MusicController` --- osu.Game.Tests/Database/GeneralUsageTests.cs | 3 ++- osu.Game.Tests/Database/RealmLiveTests.cs | 4 ++- osu.Game/Database/RealmContextFactory.cs | 5 +--- .../Bindings/DatabasedKeyBindingContainer.cs | 26 +++++++++--------- osu.Game/Online/BeatmapDownloadTracker.cs | 4 +-- .../OnlinePlayBeatmapAvailabilityTracker.cs | 4 +-- osu.Game/Online/ScoreDownloadTracker.cs | 4 +-- osu.Game/Overlays/MusicController.cs | 17 +++++++----- .../Sections/DebugSettings/MemorySettings.cs | 4 +++ .../Overlays/Settings/Sections/SkinSection.cs | 22 +++++++-------- osu.Game/Screens/Select/BeatmapCarousel.cs | 17 ++++++------ .../Screens/Select/Carousel/TopLocalRank.cs | 25 ++++++++--------- .../Select/Leaderboards/BeatmapLeaderboard.cs | 27 ++++++++++--------- osu.Game/Screens/Spectate/SpectatorScreen.cs | 20 +++++++------- 14 files changed, 98 insertions(+), 84 deletions(-) diff --git a/osu.Game.Tests/Database/GeneralUsageTests.cs b/osu.Game.Tests/Database/GeneralUsageTests.cs index 9ebe94b383..c82c1b6e59 100644 --- a/osu.Game.Tests/Database/GeneralUsageTests.cs +++ b/osu.Game.Tests/Database/GeneralUsageTests.cs @@ -46,7 +46,7 @@ namespace osu.Game.Tests.Database { bool callbackRan = false; - realmFactory.Run(realm => + realmFactory.Register(realm => { var subscription = realm.All().QueryAsyncWithNotifications((sender, changes, error) => { @@ -60,6 +60,7 @@ namespace osu.Game.Tests.Database realmFactory.Run(r => r.Refresh()); subscription?.Dispose(); + return null; }); Assert.IsTrue(callbackRan); diff --git a/osu.Game.Tests/Database/RealmLiveTests.cs b/osu.Game.Tests/Database/RealmLiveTests.cs index 2f16df4624..5549c140f6 100644 --- a/osu.Game.Tests/Database/RealmLiveTests.cs +++ b/osu.Game.Tests/Database/RealmLiveTests.cs @@ -234,7 +234,7 @@ namespace osu.Game.Tests.Database { int changesTriggered = 0; - realmFactory.Run(outerRealm => + realmFactory.Register(outerRealm => { outerRealm.All().QueryAsyncWithNotifications(gotChange); ILive? liveBeatmap = null; @@ -277,6 +277,8 @@ namespace osu.Game.Tests.Database r.Remove(resolved); }); }); + + return null; }); void gotChange(IRealmCollection sender, ChangeSet changes, Exception error) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 62717eb880..c11ff61039 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -244,7 +244,6 @@ namespace osu.Game.Database if (!ThreadSafety.IsUpdateThread) throw new InvalidOperationException(@$"{nameof(Register)} must be called from the update thread."); - subscriptionActions.Add(action, null); registerSubscription(action); return new InvokeOnDisposal(() => @@ -264,10 +263,8 @@ namespace osu.Game.Database lock (contextLock) { - Debug.Assert(context != null); - current_thread_subscriptions_allowed.Value = true; - subscriptionActions[action] = action(context); + subscriptionActions[action] = action(Context); current_thread_subscriptions_allowed.Value = false; } } diff --git a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs index 03b069d431..5a9797ffce 100644 --- a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs +++ b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs @@ -23,7 +23,6 @@ namespace osu.Game.Input.Bindings private readonly int? variant; private IDisposable realmSubscription; - private IQueryable realmKeyBindings; [Resolved] private RealmContextFactory realmFactory { get; set; } @@ -47,22 +46,25 @@ namespace osu.Game.Input.Bindings throw new InvalidOperationException($"{nameof(variant)} can not be null when a non-null {nameof(ruleset)} is provided."); } + private IQueryable realmKeyBindings + { + get + { + string rulesetName = ruleset?.ShortName; + return realmFactory.Context.All() + .Where(b => b.RulesetName == rulesetName && b.Variant == variant); + } + } + protected override void LoadComplete() { - string rulesetName = ruleset?.ShortName; - - realmKeyBindings = realmFactory.Context.All() - .Where(b => b.RulesetName == rulesetName && b.Variant == variant); - - realmSubscription = realmKeyBindings + realmSubscription = realmFactory.Register(realm => realmKeyBindings .QueryAsyncWithNotifications((sender, changes, error) => { - // first subscription ignored as we are handling this in LoadComplete. - if (changes == null) - return; - + // The first fire of this is a bit redundant as this is being called in base.LoadComplete, + // but this is safest in case the subscription is restored after a context recycle. ReloadMappings(); - }); + })); base.LoadComplete(); } diff --git a/osu.Game/Online/BeatmapDownloadTracker.cs b/osu.Game/Online/BeatmapDownloadTracker.cs index be5bdea6f1..d50ab5a347 100644 --- a/osu.Game/Online/BeatmapDownloadTracker.cs +++ b/osu.Game/Online/BeatmapDownloadTracker.cs @@ -42,7 +42,7 @@ namespace osu.Game.Online // Used to interact with manager classes that don't support interface types. Will eventually be replaced. var beatmapSetInfo = new BeatmapSetInfo { OnlineID = TrackedItem.OnlineID }; - realmSubscription = realmContextFactory.Context.All().Where(s => s.OnlineID == TrackedItem.OnlineID && !s.DeletePending).QueryAsyncWithNotifications((items, changes, ___) => + realmSubscription = realmContextFactory.Register(realm => realm.All().Where(s => s.OnlineID == TrackedItem.OnlineID && !s.DeletePending).QueryAsyncWithNotifications((items, changes, ___) => { if (items.Any()) Schedule(() => UpdateState(DownloadState.LocallyAvailable)); @@ -54,7 +54,7 @@ namespace osu.Game.Online attachDownload(Downloader.GetExistingDownload(beatmapSetInfo)); }); } - }); + })); } private void downloadBegan(ArchiveDownloadRequest request) => Schedule(() => diff --git a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs index 1f77b1d383..fdcf2b39c6 100644 --- a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs @@ -78,13 +78,13 @@ namespace osu.Game.Online.Rooms // handles changes to hash that didn't occur from the import process (ie. a user editing the beatmap in the editor, somehow). realmSubscription?.Dispose(); - realmSubscription = filteredBeatmaps().QueryAsyncWithNotifications((items, changes, ___) => + realmSubscription = realmContextFactory.Register(realm => filteredBeatmaps().QueryAsyncWithNotifications((items, changes, ___) => { if (changes == null) return; Scheduler.AddOnce(updateAvailability); - }); + })); }, true); } diff --git a/osu.Game/Online/ScoreDownloadTracker.cs b/osu.Game/Online/ScoreDownloadTracker.cs index b34586567d..de1d6fd94a 100644 --- a/osu.Game/Online/ScoreDownloadTracker.cs +++ b/osu.Game/Online/ScoreDownloadTracker.cs @@ -47,7 +47,7 @@ namespace osu.Game.Online Downloader.DownloadBegan += downloadBegan; Downloader.DownloadFailed += downloadFailed; - realmSubscription = realmContextFactory.Context.All().Where(s => ((s.OnlineID > 0 && s.OnlineID == TrackedItem.OnlineID) || s.Hash == TrackedItem.Hash) && !s.DeletePending).QueryAsyncWithNotifications((items, changes, ___) => + realmSubscription = realmContextFactory.Register(realm => realm.All().Where(s => ((s.OnlineID > 0 && s.OnlineID == TrackedItem.OnlineID) || s.Hash == TrackedItem.Hash) && !s.DeletePending).QueryAsyncWithNotifications((items, changes, ___) => { if (items.Any()) Schedule(() => UpdateState(DownloadState.LocallyAvailable)); @@ -59,7 +59,7 @@ namespace osu.Game.Online attachDownload(Downloader.GetExistingDownload(scoreInfo)); }); } - }); + })); } private void downloadBegan(ArchiveDownloadRequest request) => Schedule(() => diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 70f8332295..01a2c9d354 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -80,26 +80,31 @@ namespace osu.Game.Overlays mods.BindValueChanged(_ => ResetTrackAdjustments(), true); } + private IQueryable availableBeatmaps => realmFactory.Context + .All() + .Where(s => !s.DeletePending); + protected override void LoadComplete() { base.LoadComplete(); - var availableBeatmaps = realmFactory.Context - .All() - .Where(s => !s.DeletePending); - // ensure we're ready before completing async load. // probably not a good way of handling this (as there is a period we aren't watching for changes until the realm subscription finishes up. foreach (var s in availableBeatmaps) - beatmapSets.Add(s); + beatmapSets.Add(s.Detach()); - beatmapSubscription = availableBeatmaps.QueryAsyncWithNotifications(beatmapsChanged); + beatmapSubscription = realmFactory.Register(realm => availableBeatmaps.QueryAsyncWithNotifications(beatmapsChanged)); } private void beatmapsChanged(IRealmCollection sender, ChangeSet changes, Exception error) { if (changes == null) + { + beatmapSets.Clear(); + foreach (var s in sender) + beatmapSets.Add(s.Detach()); return; + } foreach (int i in changes.InsertedIndices) beatmapSets.Insert(i, sender[i].Detach()); diff --git a/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs b/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs index 8d4fc5fc9f..84a54b208c 100644 --- a/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs +++ b/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs @@ -33,6 +33,10 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings using (realmFactory.BlockAllOperations()) { } + + // retrieve context to revive realm subscriptions. + // TODO: should we do this from OsuGame or RealmContextFactory or something? Answer: yes. + var _ = realmFactory.Context; } }, }; diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index 0fa6d78d4b..11a7275168 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -50,7 +50,12 @@ namespace osu.Game.Overlays.Settings.Sections private RealmContextFactory realmFactory { get; set; } private IDisposable realmSubscription; - private IQueryable realmSkins; + + private IQueryable realmSkins => + realmFactory.Context.All() + .Where(s => !s.DeletePending) + .OrderByDescending(s => s.Protected) // protected skins should be at the top. + .ThenBy(s => s.Name, StringComparer.OrdinalIgnoreCase); [BackgroundDependencyLoader(permitNulls: true)] private void load(OsuConfigManager config, [CanBeNull] SkinEditorOverlay skinEditor) @@ -78,20 +83,13 @@ namespace osu.Game.Overlays.Settings.Sections skinDropdown.Current = dropdownBindable; - realmSkins = realmFactory.Context.All() - .Where(s => !s.DeletePending) - .OrderByDescending(s => s.Protected) // protected skins should be at the top. - .ThenBy(s => s.Name, StringComparer.OrdinalIgnoreCase); - - realmSubscription = realmSkins + realmSubscription = realmFactory.Register(realm => realmSkins .QueryAsyncWithNotifications((sender, changes, error) => { - if (changes == null) - return; - - // Eventually this should be handling the individual changes rather than refreshing the whole dropdown. + // The first fire of this is a bit redundant due to the call below, + // but this is safest in case the subscription is restored after a context recycle. updateItems(); - }); + })); updateItems(); diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index f8cee2704b..6f3f467170 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -190,13 +190,13 @@ namespace osu.Game.Screens.Select { base.LoadComplete(); - subscriptionSets = getBeatmapSets(realmFactory.Context).QueryAsyncWithNotifications(beatmapSetsChanged); - subscriptionBeatmaps = realmFactory.Context.All().Where(b => !b.Hidden).QueryAsyncWithNotifications(beatmapsChanged); + subscriptionSets = realmFactory.Register(realm => getBeatmapSets(realm).QueryAsyncWithNotifications(beatmapSetsChanged)); + subscriptionBeatmaps = realmFactory.Register(realm => realm.All().Where(b => !b.Hidden).QueryAsyncWithNotifications(beatmapsChanged)); // Can't use main subscriptions because we can't lookup deleted indices. // https://github.com/realm/realm-dotnet/discussions/2634#discussioncomment-1605595. - subscriptionDeletedSets = realmFactory.Context.All().Where(s => s.DeletePending && !s.Protected).QueryAsyncWithNotifications(deletedBeatmapSetsChanged); - subscriptionHiddenBeatmaps = realmFactory.Context.All().Where(b => b.Hidden).QueryAsyncWithNotifications(beatmapsChanged); + subscriptionDeletedSets = realmFactory.Register(realm => realm.All().Where(s => s.DeletePending && !s.Protected).QueryAsyncWithNotifications(deletedBeatmapSetsChanged)); + subscriptionHiddenBeatmaps = realmFactory.Register(realm => realm.All().Where(b => b.Hidden).QueryAsyncWithNotifications(beatmapsChanged)); } private void deletedBeatmapSetsChanged(IRealmCollection sender, ChangeSet changes, Exception error) @@ -552,10 +552,11 @@ namespace osu.Game.Screens.Select private void signalBeatmapsLoaded() { - Debug.Assert(BeatmapSetsLoaded == false); - - BeatmapSetsChanged?.Invoke(); - BeatmapSetsLoaded = true; + if (!BeatmapSetsLoaded) + { + BeatmapSetsChanged?.Invoke(); + BeatmapSetsLoaded = true; + } itemsCache.Invalidate(); } diff --git a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs index 7ac99f4935..ee3930364b 100644 --- a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs +++ b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs @@ -48,18 +48,19 @@ namespace osu.Game.Screens.Select.Carousel ruleset.BindValueChanged(_ => { scoreSubscription?.Dispose(); - scoreSubscription = realmFactory.Context.All() - .Filter($"{nameof(ScoreInfo.User)}.{nameof(RealmUser.OnlineID)} == $0" - + $" && {nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} == $1" - + $" && {nameof(ScoreInfo.Ruleset)}.{nameof(RulesetInfo.ShortName)} == $2" - + $" && {nameof(ScoreInfo.DeletePending)} == false", api.LocalUser.Value.Id, beatmapInfo.ID, ruleset.Value.ShortName) - .OrderByDescending(s => s.TotalScore) - .QueryAsyncWithNotifications((items, changes, ___) => - { - Rank = items.FirstOrDefault()?.Rank; - // Required since presence is changed via IsPresent override - Invalidate(Invalidation.Presence); - }); + scoreSubscription = realmFactory.Register(realm => + realm.All() + .Filter($"{nameof(ScoreInfo.User)}.{nameof(RealmUser.OnlineID)} == $0" + + $" && {nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} == $1" + + $" && {nameof(ScoreInfo.Ruleset)}.{nameof(RulesetInfo.ShortName)} == $2" + + $" && {nameof(ScoreInfo.DeletePending)} == false", api.LocalUser.Value.Id, beatmapInfo.ID, ruleset.Value.ShortName) + .OrderByDescending(s => s.TotalScore) + .QueryAsyncWithNotifications((items, changes, ___) => + { + Rank = items.FirstOrDefault()?.Rank; + // Required since presence is changed via IsPresent override + Invalidate(Invalidation.Presence); + })); }, true); } diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index da52b43ab6..954c2a6413 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -44,9 +44,13 @@ namespace osu.Game.Screens.Select.Leaderboards beatmapInfo = value; Scores = null; - UpdateScores(); - if (IsLoaded) - refreshRealmSubscription(); + if (IsOnlineScope) + UpdateScores(); + else + { + if (IsLoaded) + refreshRealmSubscription(); + } } } @@ -109,15 +113,14 @@ namespace osu.Game.Screens.Select.Leaderboards if (beatmapInfo == null) return; - scoreSubscription = realmFactory.Context.All() - .Filter($"{nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} = $0", beatmapInfo.ID) - .QueryAsyncWithNotifications((_, changes, ___) => - { - if (changes == null) - return; - - RefreshScores(); - }); + scoreSubscription = realmFactory.Register(realm => + realm.All() + .Filter($"{nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} = $0", beatmapInfo.ID) + .QueryAsyncWithNotifications((_, changes, ___) => + { + if (!IsOnlineScope) + RefreshScores(); + })); } protected override void Reset() diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs index dd586bdd37..9fa5bb8562 100644 --- a/osu.Game/Screens/Spectate/SpectatorScreen.cs +++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs @@ -79,17 +79,17 @@ namespace osu.Game.Screens.Spectate playingUserStates.BindTo(spectatorClient.PlayingUserStates); playingUserStates.BindCollectionChanged(onPlayingUserStatesChanged, true); - realmSubscription = realmContextFactory.Context - .All() - .Where(s => !s.DeletePending) - .QueryAsyncWithNotifications((items, changes, ___) => - { - if (changes?.InsertedIndices == null) - return; + realmSubscription = realmContextFactory.Register(realm => + realm.All() + .Where(s => !s.DeletePending) + .QueryAsyncWithNotifications((items, changes, ___) => + { + if (changes?.InsertedIndices == null) + return; - foreach (int c in changes.InsertedIndices) - beatmapUpdated(items[c]); - }); + foreach (int c in changes.InsertedIndices) + beatmapUpdated(items[c]); + })); foreach ((int id, var _) in userMap) spectatorClient.WatchUser(id); From 63226f7def9f10d10460e43be72a139852f83cc8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 19:56:32 +0900 Subject: [PATCH 554/996] Remove pointless initial `MusicController` beatmap set population Looks to pass tests and all usages look safe enough. --- osu.Game/Overlays/MusicController.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 01a2c9d354..c19a069343 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -87,12 +87,6 @@ namespace osu.Game.Overlays protected override void LoadComplete() { base.LoadComplete(); - - // ensure we're ready before completing async load. - // probably not a good way of handling this (as there is a period we aren't watching for changes until the realm subscription finishes up. - foreach (var s in availableBeatmaps) - beatmapSets.Add(s.Detach()); - beatmapSubscription = realmFactory.Register(realm => availableBeatmaps.QueryAsyncWithNotifications(beatmapsChanged)); } From a86c0014fe3093069a23f7014c537d31f6fdf8bf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 20:07:43 +0900 Subject: [PATCH 555/996] Remove unnecessary exception/check --- osu.Game/Overlays/MusicController.cs | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index c19a069343..0671e6d2c7 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -30,16 +30,7 @@ namespace osu.Game.Overlays [Resolved] private BeatmapManager beatmaps { get; set; } - public IBindableList BeatmapSets - { - get - { - if (LoadState < LoadState.Ready) - throw new InvalidOperationException($"{nameof(BeatmapSets)} should not be accessed before the music controller is loaded."); - - return beatmapSets; - } - } + public IBindableList BeatmapSets => beatmapSets; /// /// Point in time after which the current track will be restarted on triggering a "previous track" action. From 3b11235d3c65c7416e8e092d0a8d4aeb45c0c85c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 20:45:10 +0900 Subject: [PATCH 556/996] Fix potentially cyclic subscription registration if a subscription's delegate accesses `Context` --- osu.Game/Database/RealmContextFactory.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index c11ff61039..d9c66ccc69 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -88,6 +88,8 @@ namespace osu.Game.Database registerSubscription(action); } + Debug.Assert(context != null); + // creating a context will ensure our schema is up-to-date and migrated. return context; } @@ -261,10 +263,13 @@ namespace osu.Game.Database { Debug.Assert(ThreadSafety.IsUpdateThread); + // Get context outside of flag update to ensure beyond doubt this can't be cyclic. + var realm = Context; + lock (contextLock) { current_thread_subscriptions_allowed.Value = true; - subscriptionActions[action] = action(Context); + subscriptionActions[action] = action(realm); current_thread_subscriptions_allowed.Value = false; } } From 9b63f15e68577a2d2b88d2613a6f61aff554097a Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Fri, 21 Jan 2022 13:58:30 +0100 Subject: [PATCH 557/996] Add failing test --- .../Visual/Menus/TestSceneLoginPanel.cs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneLoginPanel.cs b/osu.Game.Tests/Visual/Menus/TestSceneLoginPanel.cs index 4754a73f83..642cc68de5 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneLoginPanel.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneLoginPanel.cs @@ -8,6 +8,8 @@ using osu.Framework.Testing; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Overlays.Login; +using osu.Game.Users.Drawables; +using osuTK.Input; namespace osu.Game.Tests.Visual.Menus { @@ -15,6 +17,7 @@ namespace osu.Game.Tests.Visual.Menus public class TestSceneLoginPanel : OsuManualInputManagerTestScene { private LoginPanel loginPanel; + private int hideCount; [SetUpSteps] public void SetUpSteps() @@ -26,6 +29,7 @@ namespace osu.Game.Tests.Visual.Menus Anchor = Anchor.Centre, Origin = Anchor.Centre, Width = 0.5f, + RequestHide = () => hideCount++, }); }); } @@ -51,5 +55,22 @@ namespace osu.Game.Tests.Visual.Menus AddStep("enter password", () => loginPanel.ChildrenOfType().First().Text = "password"); AddStep("submit", () => loginPanel.ChildrenOfType().First(b => b.Text.ToString() == "Sign in").TriggerClick()); } + + [Test] + public void TestClickingOnFlagClosesPanel() + { + AddStep("reset hide count", () => hideCount = 0); + + AddStep("logout", () => API.Logout()); + AddStep("enter password", () => loginPanel.ChildrenOfType().First().Text = "password"); + AddStep("submit", () => loginPanel.ChildrenOfType().First(b => b.Text.ToString() == "Sign in").TriggerClick()); + + AddStep("click on flag", () => + { + InputManager.MoveMouseTo(loginPanel.ChildrenOfType().First()); + InputManager.Click(MouseButton.Left); + }); + AddAssert("hide requested", () => hideCount == 1); + } } } From 529610ee2e9c0d73c3a9c72c0531d4b716264644 Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Fri, 21 Jan 2022 14:01:49 +0100 Subject: [PATCH 558/996] Call the UserPanel `Action` when clicking on the flag --- osu.Game/Users/Drawables/UpdateableFlag.cs | 8 ++++++++ osu.Game/Users/ExtendedUserPanel.cs | 3 ++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game/Users/Drawables/UpdateableFlag.cs b/osu.Game/Users/Drawables/UpdateableFlag.cs index 7db834bf83..e5debc0683 100644 --- a/osu.Game/Users/Drawables/UpdateableFlag.cs +++ b/osu.Game/Users/Drawables/UpdateableFlag.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -23,6 +24,12 @@ namespace osu.Game.Users.Drawables /// public bool ShowPlaceholderOnNull = true; + /// + /// Perform an action in addition to showing the country ranking. + /// This should be used to perform auxiliary tasks and not as a primary action for clicking a flag (to maintain a consistent UX). + /// + public Action Action; + public UpdateableFlag(Country country = null) { Country = country; @@ -52,6 +59,7 @@ namespace osu.Game.Users.Drawables protected override bool OnClick(ClickEvent e) { + Action?.Invoke(); rankingsOverlay?.ShowCountry(Country); return true; } diff --git a/osu.Game/Users/ExtendedUserPanel.cs b/osu.Game/Users/ExtendedUserPanel.cs index fc5e1eca5f..d0f693c37c 100644 --- a/osu.Game/Users/ExtendedUserPanel.cs +++ b/osu.Game/Users/ExtendedUserPanel.cs @@ -53,7 +53,8 @@ namespace osu.Game.Users protected UpdateableFlag CreateFlag() => new UpdateableFlag(User.Country) { - Size = new Vector2(39, 26) + Size = new Vector2(39, 26), + Action = Action, }; protected SpriteIcon CreateStatusIcon() => statusIcon = new SpriteIcon From ad3a01dc06215e17458357189cac15b9c603d093 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 22:40:18 +0900 Subject: [PATCH 559/996] Use a more reliable method of reviving the update thread realm after blocking --- osu.Game/Database/RealmContextFactory.cs | 44 +++++++++++-------- .../Sections/DebugSettings/MemorySettings.cs | 4 -- 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index d9c66ccc69..6fb9a2996b 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -69,30 +69,29 @@ namespace osu.Game.Database private Realm? context; - public Realm Context + public Realm Context => ensureUpdateContext(); + + private Realm ensureUpdateContext() { - get + if (!ThreadSafety.IsUpdateThread) + throw new InvalidOperationException(@$"Use {nameof(createContext)} when performing realm operations from a non-update thread"); + + lock (contextLock) { - if (!ThreadSafety.IsUpdateThread) - throw new InvalidOperationException(@$"Use {nameof(createContext)} when performing realm operations from a non-update thread"); - - lock (contextLock) + if (context == null) { - if (context == null) - { - context = createContext(); - Logger.Log(@$"Opened realm ""{context.Config.DatabasePath}"" at version {context.Config.SchemaVersion}"); + context = createContext(); + Logger.Log(@$"Opened realm ""{context.Config.DatabasePath}"" at version {context.Config.SchemaVersion}"); - // Resubscribe any subscriptions - foreach (var action in subscriptionActions.Keys) - registerSubscription(action); - } - - Debug.Assert(context != null); - - // creating a context will ensure our schema is up-to-date and migrated. - return context; + // Resubscribe any subscriptions + foreach (var action in subscriptionActions.Keys) + registerSubscription(action); } + + Debug.Assert(context != null); + + // creating a context will ensure our schema is up-to-date and migrated. + return context; } } @@ -506,6 +505,8 @@ namespace osu.Game.Database if (isDisposed) throw new ObjectDisposedException(nameof(RealmContextFactory)); + SynchronizationContext syncContext; + try { contextCreationLock.Wait(); @@ -515,6 +516,8 @@ namespace osu.Game.Database if (!ThreadSafety.IsUpdateThread && context != null) throw new InvalidOperationException(@$"{nameof(BlockAllOperations)} must be called from the update thread."); + syncContext = SynchronizationContext.Current; + Logger.Log(@"Blocking realm operations.", LoggingTarget.Database); context?.Dispose(); @@ -553,6 +556,9 @@ namespace osu.Game.Database { factory.contextCreationLock.Release(); Logger.Log(@"Restoring realm operations.", LoggingTarget.Database); + + // Post back to the update thread to revive any subscriptions. + syncContext?.Post(_ => ensureUpdateContext(), null); }); } diff --git a/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs b/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs index 84a54b208c..8d4fc5fc9f 100644 --- a/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs +++ b/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs @@ -33,10 +33,6 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings using (realmFactory.BlockAllOperations()) { } - - // retrieve context to revive realm subscriptions. - // TODO: should we do this from OsuGame or RealmContextFactory or something? Answer: yes. - var _ = realmFactory.Context; } }, }; From a2b6bc9e5334e4357c10a5a6553486b2d9bd5e24 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 Jan 2022 18:10:00 +0900 Subject: [PATCH 560/996] Add benchmark coverage of variuos methods of reading properties from realm --- osu.Game.Benchmarks/BenchmarkRealmReads.cs | 139 +++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 osu.Game.Benchmarks/BenchmarkRealmReads.cs diff --git a/osu.Game.Benchmarks/BenchmarkRealmReads.cs b/osu.Game.Benchmarks/BenchmarkRealmReads.cs new file mode 100644 index 0000000000..5b5bdf595d --- /dev/null +++ b/osu.Game.Benchmarks/BenchmarkRealmReads.cs @@ -0,0 +1,139 @@ +// 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 System.Threading; +using BenchmarkDotNet.Attributes; +using osu.Framework.Testing; +using osu.Framework.Threading; +using osu.Game.Beatmaps; +using osu.Game.Database; +using osu.Game.Rulesets.Osu; +using osu.Game.Tests.Resources; + +namespace osu.Game.Benchmarks +{ + public class BenchmarkRealmReads : BenchmarkTest + { + private TemporaryNativeStorage storage; + private RealmContextFactory realmFactory; + private UpdateThread updateThread; + + [Params(1, 100, 1000)] + public int ReadsPerFetch { get; set; } + + public override void SetUp() + { + storage = new TemporaryNativeStorage("realm-benchmark"); + storage.DeleteDirectory(string.Empty); + + realmFactory = new RealmContextFactory(storage, "client"); + + using (var context = realmFactory.CreateContext()) + context.Write(c => c.Add(TestResources.CreateTestBeatmapSetInfo(rulesets: new[] { new OsuRuleset().RulesetInfo }))); + + updateThread = new UpdateThread(() => { }, null); + updateThread.Start(); + } + + [Benchmark] + public void BenchmarkDirectPropertyRead() + { + using (var context = realmFactory.CreateContext()) + { + var beatmapSet = context.All().First(); + + for (int i = 0; i < ReadsPerFetch; i++) + { + string _ = beatmapSet.Beatmaps.First().Hash; + } + } + } + + [Benchmark] + public void BenchmarkDirectPropertyReadUpdateThread() + { + var done = new ManualResetEventSlim(); + + updateThread.Scheduler.Add(() => + { + try + { + var beatmapSet = realmFactory.Context.All().First(); + + for (int i = 0; i < ReadsPerFetch; i++) + { + string _ = beatmapSet.Beatmaps.First().Hash; + } + } + finally + { + done.Set(); + } + }); + + done.Wait(); + } + + [Benchmark] + public void BenchmarkRealmLivePropertyRead() + { + using (var context = realmFactory.CreateContext()) + { + var beatmapSet = context.All().First().ToLive(realmFactory); + + for (int i = 0; i < ReadsPerFetch; i++) + { + string _ = beatmapSet.PerformRead(b => b.Beatmaps.First().Hash); + } + } + } + + [Benchmark] + public void BenchmarkRealmLivePropertyReadUpdateThread() + { + var done = new ManualResetEventSlim(); + + updateThread.Scheduler.Add(() => + { + try + { + var beatmapSet = realmFactory.Context.All().First().ToLive(realmFactory); + + for (int i = 0; i < ReadsPerFetch; i++) + { + string _ = beatmapSet.PerformRead(b => b.Beatmaps.First().Hash); + } + } + finally + { + done.Set(); + } + }); + + done.Wait(); + } + + [Benchmark] + public void BenchmarkDetachedPropertyRead() + { + using (var context = realmFactory.CreateContext()) + { + var beatmapSet = context.All().First().Detach(); + + for (int i = 0; i < ReadsPerFetch; i++) + { + string _ = beatmapSet.Beatmaps.First().Hash; + } + } + } + + [GlobalCleanup] + public void Cleanup() + { + realmFactory?.Dispose(); + storage?.Dispose(); + updateThread?.Exit(); + } + } +} From 8ef50ff42dab85eaae680274ccd7d3b25a78de2f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 17 Jan 2022 17:28:01 +0900 Subject: [PATCH 561/996] Add safety to ensure `RealmLiveUnmanaged` is not used with managed instances --- osu.Game/Database/RealmLiveUnmanaged.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Database/RealmLiveUnmanaged.cs b/osu.Game/Database/RealmLiveUnmanaged.cs index ea50ccc1ff..97f2faa656 100644 --- a/osu.Game/Database/RealmLiveUnmanaged.cs +++ b/osu.Game/Database/RealmLiveUnmanaged.cs @@ -21,6 +21,9 @@ namespace osu.Game.Database /// The realm data. public RealmLiveUnmanaged(T data) { + if (data.IsManaged) + throw new InvalidOperationException($"Cannot use {nameof(RealmLiveUnmanaged)} with managed instances"); + Value = data; } From 9946003069f7666f74bc383ac1371043cf6d0b35 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 22 Jan 2022 05:09:40 +0900 Subject: [PATCH 562/996] Update osu.Game/Screens/Menu/IntroScreen.cs 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 db9d9b83c4..10be90a5f0 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -103,7 +103,7 @@ namespace osu.Game.Screens.Menu int setCount = sets.Count; - if (sets.Any()) + if (setCount > 0) { var found = sets[RNG.Next(0, setCount - 1)].Beatmaps.FirstOrDefault(); From 855ef3fa92ef76e53d2db54eeddf7ec70482e248 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 22 Jan 2022 11:50:47 +0900 Subject: [PATCH 563/996] Create backup before any realm contexts are used --- osu.Game/Database/EFToRealmMigrator.cs | 16 ---------------- osu.Game/OsuGameBase.cs | 15 +++++++++++++++ 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index 85d65fea82..1e21107628 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -100,10 +100,6 @@ namespace osu.Game.Database { base.LoadComplete(); - // needs to be run on the update thread because of realm BlockAllOperations. - // maybe we can work around this? not sure.. - createBackup(); - Task.Factory.StartNew(() => { using (var ef = efContextFactory.Get()) @@ -470,17 +466,5 @@ namespace osu.Game.Database private string? getRulesetShortNameFromLegacyID(long rulesetId) => efContextFactory.Get().RulesetInfo.FirstOrDefault(r => r.ID == rulesetId)?.ShortName; - - private void createBackup() - { - string migration = $"before_final_migration_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}"; - - efContextFactory.CreateBackup($"client.{migration}.db"); - realmContextFactory.CreateBackup($"client.{migration}.realm"); - - using (var source = storage.GetStream("collection.db")) - using (var destination = storage.GetStream($"collection.{migration}.db", FileAccess.Write, FileMode.CreateNew)) - source.CopyTo(destination); - } } } diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 3d40126b4f..710a7be8d4 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -197,6 +197,21 @@ namespace osu.Game dependencies.Cache(RulesetStore = new RulesetStore(realmFactory, Storage)); dependencies.CacheAs(RulesetStore); + // Backup is taken here rather than in EFToRealmMigrator to avoid recycling realm contexts + // after initial usages below. It can be moved once a direction is established for handling re-subscription. + // See https://github.com/ppy/osu/pull/16547 for more discussion. + if (EFContextFactory != null) + { + string migration = $"before_final_migration_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}"; + + EFContextFactory.CreateBackup($"client.{migration}.db"); + realmFactory.CreateBackup($"client.{migration}.realm"); + + using (var source = Storage.GetStream("collection.db")) + using (var destination = Storage.GetStream($"collection.{migration}.db", FileAccess.Write, FileMode.CreateNew)) + source.CopyTo(destination); + } + dependencies.CacheAs(Storage); var largeStore = new LargeTextureStore(Host.CreateTextureLoaderStore(new NamespacedResourceStore(Resources, @"Textures"))); From daed0b04dc97e8a50375f6955451bd3175af6946 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 22 Jan 2022 11:54:40 +0900 Subject: [PATCH 564/996] Remove using statements --- osu.Game/Database/EFToRealmMigrator.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index 1e21107628..f5b338062a 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.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. -using System; -using System.IO; using System.Linq; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; From b23f4674b12251a94fd8bb0f054adcdc60f8abf2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 22 Jan 2022 12:02:18 +0900 Subject: [PATCH 565/996] Update outdated exception message Co-authored-by: Salman Ahmed --- osu.Game/Database/RealmContextFactory.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 888ffb1dd5..ea6a4b9636 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -72,7 +72,7 @@ namespace osu.Game.Database get { if (!ThreadSafety.IsUpdateThread) - throw new InvalidOperationException(@$"Use {nameof(createContext)} when performing realm operations from a non-update thread"); + throw new InvalidOperationException(@$"Use {nameof(Run)}/{nameof(Write)} when performing realm operations from a non-update thread"); lock (contextLock) { From 7025191fdd45767321565cf28bfbb403b29148ef Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 22 Jan 2022 12:02:44 +0900 Subject: [PATCH 566/996] Move target field outside of `Run` usage Co-authored-by: Salman Ahmed --- .../Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs index 9075dfefd4..2ee3372f80 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs @@ -36,7 +36,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input List bindings = null; - realmFactory.Run(realm => bindings = realm.All().Where(b => b.RulesetName == rulesetName && b.Variant == variant).Detach()); + bindings = realmFactory.Run(realm => realm.All().Where(b => b.RulesetName == rulesetName && b.Variant == variant).Detach()); foreach (var defaultGroup in Defaults.GroupBy(d => d.Action)) { From a89954d67f0b1f3b77edab3b8b8f7e71c1db1de8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 22 Jan 2022 12:12:13 +0900 Subject: [PATCH 567/996] Update benchmarks in line with new structure --- osu.Game.Benchmarks/BenchmarkRealmReads.cs | 24 ++++++++++++---------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/osu.Game.Benchmarks/BenchmarkRealmReads.cs b/osu.Game.Benchmarks/BenchmarkRealmReads.cs index 5b5bdf595d..bb22fab51c 100644 --- a/osu.Game.Benchmarks/BenchmarkRealmReads.cs +++ b/osu.Game.Benchmarks/BenchmarkRealmReads.cs @@ -29,8 +29,10 @@ namespace osu.Game.Benchmarks realmFactory = new RealmContextFactory(storage, "client"); - using (var context = realmFactory.CreateContext()) - context.Write(c => c.Add(TestResources.CreateTestBeatmapSetInfo(rulesets: new[] { new OsuRuleset().RulesetInfo }))); + realmFactory.Run(realm => + { + realm.Write(c => c.Add(TestResources.CreateTestBeatmapSetInfo(rulesets: new[] { new OsuRuleset().RulesetInfo }))); + }); updateThread = new UpdateThread(() => { }, null); updateThread.Start(); @@ -39,15 +41,15 @@ namespace osu.Game.Benchmarks [Benchmark] public void BenchmarkDirectPropertyRead() { - using (var context = realmFactory.CreateContext()) + realmFactory.Run(realm => { - var beatmapSet = context.All().First(); + var beatmapSet = realm.All().First(); for (int i = 0; i < ReadsPerFetch; i++) { string _ = beatmapSet.Beatmaps.First().Hash; } - } + }); } [Benchmark] @@ -78,15 +80,15 @@ namespace osu.Game.Benchmarks [Benchmark] public void BenchmarkRealmLivePropertyRead() { - using (var context = realmFactory.CreateContext()) + realmFactory.Run(realm => { - var beatmapSet = context.All().First().ToLive(realmFactory); + var beatmapSet = realm.All().First().ToLive(realmFactory); for (int i = 0; i < ReadsPerFetch; i++) { string _ = beatmapSet.PerformRead(b => b.Beatmaps.First().Hash); } - } + }); } [Benchmark] @@ -117,15 +119,15 @@ namespace osu.Game.Benchmarks [Benchmark] public void BenchmarkDetachedPropertyRead() { - using (var context = realmFactory.CreateContext()) + realmFactory.Run(realm => { - var beatmapSet = context.All().First().Detach(); + var beatmapSet = realm.All().First().Detach(); for (int i = 0; i < ReadsPerFetch; i++) { string _ = beatmapSet.Beatmaps.First().Hash; } - } + }); } [GlobalCleanup] From c9db0181d0611554f68e70ac4ed2da1fc96e9550 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 22 Jan 2022 12:24:05 +0900 Subject: [PATCH 568/996] Attempt to fix test failures on windows due to context being held open --- osu.Game.Tests/Database/RealmLiveTests.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Tests/Database/RealmLiveTests.cs b/osu.Game.Tests/Database/RealmLiveTests.cs index 2f16df4624..7b1cf763d6 100644 --- a/osu.Game.Tests/Database/RealmLiveTests.cs +++ b/osu.Game.Tests/Database/RealmLiveTests.cs @@ -47,6 +47,11 @@ namespace osu.Game.Tests.Database liveBeatmap = beatmap.ToLive(realmFactory); }); + using (realmFactory.BlockAllOperations()) + { + // recycle realm before migrating + } + using (var migratedStorage = new TemporaryNativeStorage("realm-test-migration-target")) { migratedStorage.DeleteDirectory(string.Empty); From 25dbe6b27c092e5ce992e7cb4d7663cf3e8656df Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 22 Jan 2022 12:58:30 +0900 Subject: [PATCH 569/996] Fix unused null assignment --- .../Settings/Sections/Input/KeyBindingsSubsection.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs index 2ee3372f80..5b8a52240e 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs @@ -34,9 +34,9 @@ namespace osu.Game.Overlays.Settings.Sections.Input { string rulesetName = Ruleset?.ShortName; - List bindings = null; - - bindings = realmFactory.Run(realm => realm.All().Where(b => b.RulesetName == rulesetName && b.Variant == variant).Detach()); + var bindings = realmFactory.Run(realm => realm.All() + .Where(b => b.RulesetName == rulesetName && b.Variant == variant) + .Detach()); foreach (var defaultGroup in Defaults.GroupBy(d => d.Action)) { From c99f2278797abd9c2e259b422ccd602ae14eea30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 22 Jan 2022 13:48:49 +0100 Subject: [PATCH 570/996] Remove no longer used resolved storage --- osu.Game/Database/EFToRealmMigrator.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index f5b338062a..161c12e35e 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -38,9 +38,6 @@ namespace osu.Game.Database [Resolved] private OsuConfigManager config { get; set; } = null!; - [Resolved] - private Storage storage { get; set; } = null!; - private readonly OsuSpriteText currentOperationText; public EFToRealmMigrator() From 7e68371d28b9ae109a8e89039bf1db1ed801436b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 22 Jan 2022 14:20:28 +0100 Subject: [PATCH 571/996] Move log statement about migration completed closer to rest of migration code --- osu.Game/Database/DatabaseContextFactory.cs | 4 ---- osu.Game/Database/EFToRealmMigrator.cs | 4 ++++ 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Database/DatabaseContextFactory.cs b/osu.Game/Database/DatabaseContextFactory.cs index 635c4373cd..c84edbfb81 100644 --- a/osu.Game/Database/DatabaseContextFactory.cs +++ b/osu.Game/Database/DatabaseContextFactory.cs @@ -5,7 +5,6 @@ using System.IO; using System.Linq; using System.Threading; using Microsoft.EntityFrameworkCore.Storage; -using osu.Framework.Development; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Statistics; @@ -151,9 +150,6 @@ namespace osu.Game.Database { Logger.Log($"Creating full EF database backup at {backupFilename}", LoggingTarget.Database); - if (DebugUtils.IsDebugBuild) - Logger.Log("Your development database has been fully migrated to realm. If you switch back to a pre-realm branch and need your previous database, rename the backup file back to \"client.db\".\n\nNote that doing this can potentially leave your file store in a bad state.", level: LogLevel.Important); - using (var source = storage.GetStream(DATABASE_NAME)) using (var destination = storage.GetStream(backupFilename, FileAccess.Write, FileMode.CreateNew)) source.CopyTo(destination); diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index c224399dbc..bbbdac352e 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using osu.Framework.Allocation; +using osu.Framework.Development; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Logging; @@ -108,6 +109,9 @@ namespace osu.Game.Database // Will cause future startups to not attempt migration. log("Migration successful, deleting EF database"); efContextFactory.ResetDatabase(); + + if (DebugUtils.IsDebugBuild) + Logger.Log("Your development database has been fully migrated to realm. If you switch back to a pre-realm branch and need your previous database, rename the backup file back to \"client.db\".\n\nNote that doing this can potentially leave your file store in a bad state.", level: LogLevel.Important); }, TaskCreationOptions.LongRunning).ContinueWith(t => { FinishedMigrating = true; From b2d1bd029d9bd463714b69256b906caad62c1813 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 22 Jan 2022 16:17:46 +0300 Subject: [PATCH 572/996] Turn on high poll rate when tournament chat is expanded --- osu.Game.Tournament/Components/TournamentMatchChatDisplay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tournament/Components/TournamentMatchChatDisplay.cs b/osu.Game.Tournament/Components/TournamentMatchChatDisplay.cs index fe22d1e76d..a5ead6c2f0 100644 --- a/osu.Game.Tournament/Components/TournamentMatchChatDisplay.cs +++ b/osu.Game.Tournament/Components/TournamentMatchChatDisplay.cs @@ -45,7 +45,7 @@ namespace osu.Game.Tournament.Components if (manager == null) { - AddInternal(manager = new ChannelManager()); + AddInternal(manager = new ChannelManager { HighPollRate = { Value = true } }); Channel.BindTo(manager.CurrentChannel); } From 955bab926fac76513a660737a42e48e01cfd9bc0 Mon Sep 17 00:00:00 2001 From: mk-56 Date: Sat, 22 Jan 2022 19:38:56 +0100 Subject: [PATCH 573/996] Separate the settings for each modes radiuses --- .../Mods/CatchModFlashlight.cs | 19 ++++++++-- .../Mods/ManiaModFlashlight.cs | 19 ++++++++-- .../Mods/OsuModFlashlight.cs | 36 ++++++++++++------- .../Mods/TaikoModFlashlight.cs | 19 ++++++++-- osu.Game/Rulesets/Mods/ModFlashlight.cs | 20 ++--------- 5 files changed, 75 insertions(+), 38 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs index 9d9fa5aed4..f75772b04e 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs @@ -3,6 +3,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Game.Configuration; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Mods; @@ -15,8 +16,22 @@ namespace osu.Game.Rulesets.Catch.Mods { public override double ScoreMultiplier => 1.12; - public override bool DefaultComboDependency => true; - public override float DefaultRadius => 350; + [SettingSource("Change radius based on combo", "Decrease the flashlight radius as combo increases.")] + public override BindableBool ChangeRadius { get; } = new BindableBool + { + Default = true, + Value = true + }; + + [SettingSource("Initial radius", "Initial radius of the flashlight area.")] + public override BindableNumber InitialRadius { get; } = new BindableNumber + { + MinValue = 150f, + MaxValue = 600f, + Default = 350f, + Value = 350f, + Precision = 5f + }; public override Flashlight CreateFlashlight() => new CatchFlashlight(playfield, ChangeRadius.Value, InitialRadius.Value); diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs index 4fff736c57..a6a3c3be73 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs @@ -5,6 +5,7 @@ using System; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Layout; +using osu.Game.Configuration; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mods; using osuTK; @@ -16,8 +17,22 @@ namespace osu.Game.Rulesets.Mania.Mods public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(ModHidden) }; - public override bool DefaultComboDependency => false; - public override float DefaultRadius => 180; + [SettingSource("Change radius based on combo", "Decrease the flashlight radius as combo increases.")] + public override BindableBool ChangeRadius { get; } = new BindableBool + { + Default = false, + Value = false + }; + + [SettingSource("Initial radius", "Initial radius of the flashlight area.")] + public override BindableNumber InitialRadius { get; } = new BindableNumber + { + MinValue = 0f, + MaxValue = 230f, + Default = 50f, + Value = 50f, + Precision = 5f + }; public override Flashlight CreateFlashlight() => new ManiaFlashlight(ChangeRadius.Value, InitialRadius.Value); diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs index f381d14ffe..e2a6d0f0dc 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs @@ -20,14 +20,32 @@ namespace osu.Game.Rulesets.Osu.Mods { public override double ScoreMultiplier => 1.12; - public override bool DefaultComboDependency => true; - - //private const float default_flashlight_size = 180; - public override float DefaultRadius => 180; - private const double default_follow_delay = 120; + [SettingSource("Follow delay", "Milliseconds until the flashlight reaches the cursor")] + public BindableNumber FollowDelay { get; } = new BindableDouble(default_follow_delay) + { + MinValue = default_follow_delay, + MaxValue = default_follow_delay * 10, + Precision = default_follow_delay, + }; + [SettingSource("Change radius based on combo", "Decrease the flashlight radius as combo increases.")] + public override BindableBool ChangeRadius { get; } = new BindableBool + { + Default = true, + Value = true + }; + + [SettingSource("Initial radius", "Initial radius of the flashlight area.")] + public override BindableNumber InitialRadius { get; } = new BindableNumber + { + MinValue = 90f, + MaxValue = 360f, + Default = 180f, + Value = 180f, + Precision = 5f + }; private OsuFlashlight flashlight; @@ -39,14 +57,6 @@ namespace osu.Game.Rulesets.Osu.Mods s.Tracking.ValueChanged += flashlight.OnSliderTrackingChange; } - [SettingSource("Follow delay", "Milliseconds until the flashlight reaches the cursor")] - public BindableNumber FollowDelay { get; } = new BindableDouble(default_follow_delay) - { - MinValue = default_follow_delay, - MaxValue = default_follow_delay * 10, - Precision = default_follow_delay, - }; - private class OsuFlashlight : Flashlight, IRequireHighFrequencyMousePosition { public double FollowDelay { private get; set; } diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs index 76f7c45b75..71c9d777ec 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs @@ -4,6 +4,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Layout; +using osu.Game.Configuration; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.UI; @@ -16,10 +17,22 @@ namespace osu.Game.Rulesets.Taiko.Mods { public override double ScoreMultiplier => 1.12; - public override bool DefaultComboDependency => true; + [SettingSource("Change radius based on combo", "Decrease the flashlight radius as combo increases.")] + public override BindableBool ChangeRadius { get; } = new BindableBool + { + Default = true, + Value = true + }; - //private const float default_flashlight_size = 250; - public override float DefaultRadius => 250; + [SettingSource("Initial radius", "Initial radius of the flashlight area.")] + public override BindableNumber InitialRadius { get; } = new BindableNumber + { + MinValue = 0f, + MaxValue = 400f, + Default = 250f, + Value = 250f, + Precision = 5f + }; public override Flashlight CreateFlashlight() => new TaikoFlashlight(playfield, ChangeRadius.Value, InitialRadius.Value); diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index c218ab45fe..51006d96e8 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -34,26 +34,10 @@ namespace osu.Game.Rulesets.Mods public override string Description => "Restricted view area."; [SettingSource("Change radius based on combo", "Decrease the flashlight radius as combo increases.")] - public Bindable ChangeRadius { get; private set; } + public abstract BindableBool ChangeRadius { get; } [SettingSource("Initial radius", "Initial radius of the flashlight area.")] - public BindableNumber InitialRadius { get; private set; } - - public abstract float DefaultRadius { get; } - - public abstract bool DefaultComboDependency { get; } - - internal ModFlashlight() - { - InitialRadius = new BindableFloat(DefaultRadius) - { - MinValue = DefaultRadius * .5f, - MaxValue = DefaultRadius * 1.5f, - Precision = 5f, - }; - - ChangeRadius = new BindableBool(DefaultComboDependency); - } + public abstract BindableNumber InitialRadius { get; } } public abstract class ModFlashlight : ModFlashlight, IApplicableToDrawableRuleset, IApplicableToScoreProcessor From 735414bc49381df02d6fb243c25054ba8f35e204 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 22 Jan 2022 17:27:27 +0100 Subject: [PATCH 574/996] Replace `TimeSignatures` enum with struct for storage of arbitrary meter --- .../Skinning/Default/CirclePiece.cs | 2 +- .../Formats/LegacyBeatmapDecoderTest.cs | 6 +-- .../NonVisual/BarLineGeneratorTest.cs | 6 +-- .../TestSceneNightcoreBeatContainer.cs | 4 +- .../ControlPoints/TimingControlPoint.cs | 4 +- .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 4 +- .../Beatmaps/Formats/LegacyBeatmapEncoder.cs | 2 +- osu.Game/Beatmaps/Timing/TimeSignature.cs | 45 +++++++++++++++++++ osu.Game/Beatmaps/Timing/TimeSignatures.cs | 4 +- osu.Game/Rulesets/Mods/Metronome.cs | 2 +- osu.Game/Rulesets/Mods/ModNightcore.cs | 10 ++--- osu.Game/Rulesets/Objects/BarLineGenerator.cs | 6 +-- .../Timeline/TimelineTickDisplay.cs | 2 +- .../RowAttributes/TimingRowAttribute.cs | 2 +- osu.Game/Screens/Edit/Timing/TimingSection.cs | 11 +++-- osu.Game/Screens/Menu/MenuSideFlashes.cs | 4 +- osu.Game/Screens/Menu/OsuLogo.cs | 2 +- 17 files changed, 84 insertions(+), 32 deletions(-) create mode 100644 osu.Game/Beatmaps/Timing/TimeSignature.cs diff --git a/osu.Game.Rulesets.Taiko/Skinning/Default/CirclePiece.cs b/osu.Game.Rulesets.Taiko/Skinning/Default/CirclePiece.cs index 8ca996159b..a106c4f629 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Default/CirclePiece.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Default/CirclePiece.cs @@ -153,7 +153,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Default if (!effectPoint.KiaiMode) return; - if (beatIndex % (int)timingPoint.TimeSignature != 0) + if (beatIndex % timingPoint.TimeSignature.Numerator != 0) return; double duration = timingPoint.BeatLength * 2; diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 6ec14e6351..0459753b28 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -178,17 +178,17 @@ namespace osu.Game.Tests.Beatmaps.Formats var timingPoint = controlPoints.TimingPointAt(0); Assert.AreEqual(956, timingPoint.Time); Assert.AreEqual(329.67032967033, timingPoint.BeatLength); - Assert.AreEqual(TimeSignatures.SimpleQuadruple, timingPoint.TimeSignature); + Assert.AreEqual(TimeSignature.SimpleQuadruple, timingPoint.TimeSignature); timingPoint = controlPoints.TimingPointAt(48428); Assert.AreEqual(956, timingPoint.Time); Assert.AreEqual(329.67032967033d, timingPoint.BeatLength); - Assert.AreEqual(TimeSignatures.SimpleQuadruple, timingPoint.TimeSignature); + Assert.AreEqual(TimeSignature.SimpleQuadruple, timingPoint.TimeSignature); timingPoint = controlPoints.TimingPointAt(119637); Assert.AreEqual(119637, timingPoint.Time); Assert.AreEqual(659.340659340659, timingPoint.BeatLength); - Assert.AreEqual(TimeSignatures.SimpleQuadruple, timingPoint.TimeSignature); + Assert.AreEqual(TimeSignature.SimpleQuadruple, timingPoint.TimeSignature); var difficultyPoint = controlPoints.DifficultyPointAt(0); Assert.AreEqual(0, difficultyPoint.Time); diff --git a/osu.Game.Tests/NonVisual/BarLineGeneratorTest.cs b/osu.Game.Tests/NonVisual/BarLineGeneratorTest.cs index 834c05fd08..6ae8231deb 100644 --- a/osu.Game.Tests/NonVisual/BarLineGeneratorTest.cs +++ b/osu.Game.Tests/NonVisual/BarLineGeneratorTest.cs @@ -26,7 +26,7 @@ namespace osu.Game.Tests.NonVisual const int beat_length_numerator = 2000; const int beat_length_denominator = 7; - const TimeSignatures signature = TimeSignatures.SimpleQuadruple; + TimeSignature signature = TimeSignature.SimpleQuadruple; var beatmap = new Beatmap { @@ -49,7 +49,7 @@ namespace osu.Game.Tests.NonVisual for (int i = 0; i * beat_length_denominator < barLines.Count; i++) { var barLine = barLines[i * beat_length_denominator]; - int expectedTime = beat_length_numerator * (int)signature * i; + int expectedTime = beat_length_numerator * signature.Numerator * i; // every seventh bar's start time should be at least greater than the whole number we expect. // It cannot be less, as that can affect overlapping scroll algorithms @@ -60,7 +60,7 @@ namespace osu.Game.Tests.NonVisual Assert.IsTrue(Precision.AlmostEquals(barLine.StartTime, expectedTime)); // check major/minor lines for good measure too - Assert.AreEqual(i % (int)signature == 0, barLine.Major); + Assert.AreEqual(i % signature.Numerator == 0, barLine.Major); } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneNightcoreBeatContainer.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneNightcoreBeatContainer.cs index 951ee1489d..759e4fa4ec 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneNightcoreBeatContainer.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneNightcoreBeatContainer.cs @@ -24,8 +24,8 @@ namespace osu.Game.Tests.Visual.Gameplay Add(new ModNightcore.NightcoreBeatContainer()); - AddStep("change signature to quadruple", () => Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints.ForEach(p => p.TimeSignature = TimeSignatures.SimpleQuadruple)); - AddStep("change signature to triple", () => Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints.ForEach(p => p.TimeSignature = TimeSignatures.SimpleTriple)); + AddStep("change signature to quadruple", () => Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints.ForEach(p => p.TimeSignature = TimeSignature.SimpleQuadruple)); + AddStep("change signature to triple", () => Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints.ForEach(p => p.TimeSignature = TimeSignature.SimpleTriple)); } } } diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs index ec20328fab..922439fcb8 100644 --- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs @@ -13,7 +13,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// /// The time signature at this control point. /// - public readonly Bindable TimeSignatureBindable = new Bindable(TimeSignatures.SimpleQuadruple) { Default = TimeSignatures.SimpleQuadruple }; + public readonly Bindable TimeSignatureBindable = new Bindable(TimeSignature.SimpleQuadruple); /// /// Default length of a beat in milliseconds. Used whenever there is no beatmap or track playing. @@ -35,7 +35,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// /// The time signature at this control point. /// - public TimeSignatures TimeSignature + public TimeSignature TimeSignature { get => TimeSignatureBindable.Value; set => TimeSignatureBindable.Value = value; diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 893eb8ab78..8f3f05aa9f 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -340,9 +340,9 @@ namespace osu.Game.Beatmaps.Formats double beatLength = Parsing.ParseDouble(split[1].Trim()); double speedMultiplier = beatLength < 0 ? 100.0 / -beatLength : 1; - TimeSignatures timeSignature = TimeSignatures.SimpleQuadruple; + TimeSignature timeSignature = TimeSignature.SimpleQuadruple; if (split.Length >= 3) - timeSignature = split[2][0] == '0' ? TimeSignatures.SimpleQuadruple : (TimeSignatures)Parsing.ParseInt(split[2]); + timeSignature = split[2][0] == '0' ? TimeSignature.SimpleQuadruple : new TimeSignature(Parsing.ParseInt(split[2])); LegacySampleBank sampleSet = defaultSampleBank; if (split.Length >= 4) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index ebdc882d2f..4cf6d3335f 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -227,7 +227,7 @@ namespace osu.Game.Beatmaps.Formats if (effectPoint.OmitFirstBarLine) effectFlags |= LegacyEffectFlags.OmitFirstBarLine; - writer.Write(FormattableString.Invariant($"{(int)legacyControlPoints.TimingPointAt(time).TimeSignature},")); + writer.Write(FormattableString.Invariant($"{legacyControlPoints.TimingPointAt(time).TimeSignature.Numerator},")); writer.Write(FormattableString.Invariant($"{(int)toLegacySampleBank(tempHitSample.Bank)},")); writer.Write(FormattableString.Invariant($"{toLegacyCustomSampleBank(tempHitSample)},")); writer.Write(FormattableString.Invariant($"{tempHitSample.Volume},")); diff --git a/osu.Game/Beatmaps/Timing/TimeSignature.cs b/osu.Game/Beatmaps/Timing/TimeSignature.cs new file mode 100644 index 0000000000..5bfeea5e9b --- /dev/null +++ b/osu.Game/Beatmaps/Timing/TimeSignature.cs @@ -0,0 +1,45 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; + +namespace osu.Game.Beatmaps.Timing +{ + /// + /// Stores the time signature of a track. + /// For now, the lower numeral can only be 4; support for other denominators can be considered at a later date. + /// + public class TimeSignature : IEquatable + { + /// + /// The numerator of a signature. + /// + public int Numerator { get; } + + // TODO: support time signatures with a denominator other than 4 + // this in particular requires a new beatmap format. + + public TimeSignature(int numerator) + { + if (numerator < 1) + throw new ArgumentOutOfRangeException(nameof(numerator), numerator, "The numerator of a time signature must be positive."); + + Numerator = numerator; + } + + public static TimeSignature SimpleTriple => new TimeSignature(3); + public static TimeSignature SimpleQuadruple => new TimeSignature(4); + + public override string ToString() => $"{Numerator}/4"; + + public bool Equals(TimeSignature other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + + return Numerator == other.Numerator; + } + + public override int GetHashCode() => Numerator; + } +} diff --git a/osu.Game/Beatmaps/Timing/TimeSignatures.cs b/osu.Game/Beatmaps/Timing/TimeSignatures.cs index 33e6342ae6..d783d3f9ec 100644 --- a/osu.Game/Beatmaps/Timing/TimeSignatures.cs +++ b/osu.Game/Beatmaps/Timing/TimeSignatures.cs @@ -1,11 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.ComponentModel; namespace osu.Game.Beatmaps.Timing { - public enum TimeSignatures + [Obsolete("Use osu.Game.Beatmaps.Timing.TimeSignature instead.")] + public enum TimeSignatures // can be removed 20220722 { [Description("4/4")] SimpleQuadruple = 4, diff --git a/osu.Game/Rulesets/Mods/Metronome.cs b/osu.Game/Rulesets/Mods/Metronome.cs index 8b6d86c45f..b85a341577 100644 --- a/osu.Game/Rulesets/Mods/Metronome.cs +++ b/osu.Game/Rulesets/Mods/Metronome.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Mods if (!IsBeatSyncedWithTrack) return; - int timeSignature = (int)timingPoint.TimeSignature; + int timeSignature = timingPoint.TimeSignature.Numerator; // play metronome from one measure before the first object. if (BeatSyncClock.CurrentTime < firstHitTime - timingPoint.BeatLength * timeSignature) diff --git a/osu.Game/Rulesets/Mods/ModNightcore.cs b/osu.Game/Rulesets/Mods/ModNightcore.cs index a44967c21c..993efead33 100644 --- a/osu.Game/Rulesets/Mods/ModNightcore.cs +++ b/osu.Game/Rulesets/Mods/ModNightcore.cs @@ -85,7 +85,7 @@ namespace osu.Game.Rulesets.Mods { base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes); - int beatsPerBar = (int)timingPoint.TimeSignature; + int beatsPerBar = timingPoint.TimeSignature.Numerator; int segmentLength = beatsPerBar * Divisor * bars_per_segment; if (!IsBeatSyncedWithTrack) @@ -102,14 +102,14 @@ namespace osu.Game.Rulesets.Mods playBeatFor(beatIndex % segmentLength, timingPoint.TimeSignature); } - private void playBeatFor(int beatIndex, TimeSignatures signature) + private void playBeatFor(int beatIndex, TimeSignature signature) { if (beatIndex == 0) finishSample?.Play(); - switch (signature) + switch (signature.Numerator) { - case TimeSignatures.SimpleTriple: + case 3: switch (beatIndex % 6) { case 0: @@ -127,7 +127,7 @@ namespace osu.Game.Rulesets.Mods break; - case TimeSignatures.SimpleQuadruple: + case 4: switch (beatIndex % 4) { case 0: diff --git a/osu.Game/Rulesets/Objects/BarLineGenerator.cs b/osu.Game/Rulesets/Objects/BarLineGenerator.cs index e78aa5a5a0..d71a499119 100644 --- a/osu.Game/Rulesets/Objects/BarLineGenerator.cs +++ b/osu.Game/Rulesets/Objects/BarLineGenerator.cs @@ -41,9 +41,9 @@ namespace osu.Game.Rulesets.Objects int currentBeat = 0; // Stop on the beat before the next timing point, or if there is no next timing point stop slightly past the last object - double endTime = i < timingPoints.Count - 1 ? timingPoints[i + 1].Time - currentTimingPoint.BeatLength : lastHitTime + currentTimingPoint.BeatLength * (int)currentTimingPoint.TimeSignature; + double endTime = i < timingPoints.Count - 1 ? timingPoints[i + 1].Time - currentTimingPoint.BeatLength : lastHitTime + currentTimingPoint.BeatLength * currentTimingPoint.TimeSignature.Numerator; - double barLength = currentTimingPoint.BeatLength * (int)currentTimingPoint.TimeSignature; + double barLength = currentTimingPoint.BeatLength * currentTimingPoint.TimeSignature.Numerator; for (double t = currentTimingPoint.Time; Precision.DefinitelyBigger(endTime, t); t += barLength, currentBeat++) { @@ -60,7 +60,7 @@ namespace osu.Game.Rulesets.Objects BarLines.Add(new TBarLine { StartTime = t, - Major = currentBeat % (int)currentTimingPoint.TimeSignature == 0 + Major = currentBeat % currentTimingPoint.TimeSignature.Numerator == 0 }); } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs index 1415014e59..cc4041394d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs @@ -125,7 +125,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (beat == 0 && i == 0) nextMinTick = float.MinValue; - int indexInBar = beat % ((int)point.TimeSignature * beatDivisor.Value); + int indexInBar = beat % (point.TimeSignature.Numerator * beatDivisor.Value); int divisor = BindableBeatDivisor.GetDivisorForBeatIndex(beat, beatDivisor.Value); var colour = BindableBeatDivisor.GetColourFor(divisor, colours); diff --git a/osu.Game/Screens/Edit/Timing/RowAttributes/TimingRowAttribute.cs b/osu.Game/Screens/Edit/Timing/RowAttributes/TimingRowAttribute.cs index ab840e56a7..f8ec4aef25 100644 --- a/osu.Game/Screens/Edit/Timing/RowAttributes/TimingRowAttribute.cs +++ b/osu.Game/Screens/Edit/Timing/RowAttributes/TimingRowAttribute.cs @@ -13,7 +13,7 @@ namespace osu.Game.Screens.Edit.Timing.RowAttributes public class TimingRowAttribute : RowAttribute { private readonly BindableNumber beatLength; - private readonly Bindable timeSignature; + private readonly Bindable timeSignature; private OsuSpriteText text; public TimingRowAttribute(TimingControlPoint timing) diff --git a/osu.Game/Screens/Edit/Timing/TimingSection.cs b/osu.Game/Screens/Edit/Timing/TimingSection.cs index a0bb9ac506..e0b09ce980 100644 --- a/osu.Game/Screens/Edit/Timing/TimingSection.cs +++ b/osu.Game/Screens/Edit/Timing/TimingSection.cs @@ -15,7 +15,7 @@ namespace osu.Game.Screens.Edit.Timing internal class TimingSection : Section { private SettingsSlider bpmSlider; - private SettingsEnumDropdown timeSignature; + private SettingsDropdown timeSignature; private BPMTextBox bpmTextEntry; [BackgroundDependencyLoader] @@ -25,9 +25,14 @@ namespace osu.Game.Screens.Edit.Timing { bpmTextEntry = new BPMTextBox(), bpmSlider = new BPMSlider(), - timeSignature = new SettingsEnumDropdown + timeSignature = new SettingsDropdown { - LabelText = "Time Signature" + LabelText = "Time Signature", + Items = new[] + { + TimeSignature.SimpleTriple, + TimeSignature.SimpleQuadruple + } }, }); } diff --git a/osu.Game/Screens/Menu/MenuSideFlashes.cs b/osu.Game/Screens/Menu/MenuSideFlashes.cs index bdcd3020f8..cd0c75c1a1 100644 --- a/osu.Game/Screens/Menu/MenuSideFlashes.cs +++ b/osu.Game/Screens/Menu/MenuSideFlashes.cs @@ -94,9 +94,9 @@ namespace osu.Game.Screens.Menu if (beatIndex < 0) return; - if (effectPoint.KiaiMode ? beatIndex % 2 == 0 : beatIndex % (int)timingPoint.TimeSignature == 0) + if (effectPoint.KiaiMode ? beatIndex % 2 == 0 : beatIndex % timingPoint.TimeSignature.Numerator == 0) flash(leftBox, timingPoint.BeatLength, effectPoint.KiaiMode, amplitudes); - if (effectPoint.KiaiMode ? beatIndex % 2 == 1 : beatIndex % (int)timingPoint.TimeSignature == 0) + if (effectPoint.KiaiMode ? beatIndex % 2 == 1 : beatIndex % timingPoint.TimeSignature.Numerator == 0) flash(rightBox, timingPoint.BeatLength, effectPoint.KiaiMode, amplitudes); } diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index f9388097ac..c82efe2d32 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -282,7 +282,7 @@ namespace osu.Game.Screens.Menu { this.Delay(early_activation).Schedule(() => { - if (beatIndex % (int)timingPoint.TimeSignature == 0) + if (beatIndex % timingPoint.TimeSignature.Numerator == 0) sampleDownbeat.Play(); else sampleBeat.Play(); From f39f2c93b52526a3e5d5aa06e18d0f0ac85020c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 22 Jan 2022 18:54:14 +0100 Subject: [PATCH 575/996] Add control for arbitrary-numerator time signatures --- .../Editing/TestSceneLabelledTimeSignature.cs | 88 ++++++++++++++++++ .../Edit/Timing/LabelledTimeSignature.cs | 93 +++++++++++++++++++ 2 files changed, 181 insertions(+) create mode 100644 osu.Game.Tests/Visual/Editing/TestSceneLabelledTimeSignature.cs create mode 100644 osu.Game/Screens/Edit/Timing/LabelledTimeSignature.cs diff --git a/osu.Game.Tests/Visual/Editing/TestSceneLabelledTimeSignature.cs b/osu.Game.Tests/Visual/Editing/TestSceneLabelledTimeSignature.cs new file mode 100644 index 0000000000..cedbb5025f --- /dev/null +++ b/osu.Game.Tests/Visual/Editing/TestSceneLabelledTimeSignature.cs @@ -0,0 +1,88 @@ +// 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.Graphics; +using osu.Framework.Testing; +using osu.Game.Beatmaps.Timing; +using osu.Game.Graphics.UserInterface; +using osu.Game.Screens.Edit.Timing; + +namespace osu.Game.Tests.Visual.Editing +{ + public class TestSceneLabelledTimeSignature : OsuManualInputManagerTestScene + { + private LabelledTimeSignature timeSignature; + + private void createLabelledTimeSignature(TimeSignature initial) => AddStep("create labelled time signature", () => + { + Child = timeSignature = new LabelledTimeSignature + { + Label = "Time Signature", + RelativeSizeAxes = Axes.None, + Width = 400, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Current = { Value = initial } + }; + }); + + private OsuTextBox numeratorTextBox => timeSignature.ChildrenOfType().Single(); + + [Test] + public void TestInitialValue() + { + createLabelledTimeSignature(TimeSignature.SimpleTriple); + AddAssert("current is 3/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleTriple)); + } + + [Test] + public void TestChangeViaCurrent() + { + createLabelledTimeSignature(TimeSignature.SimpleQuadruple); + AddAssert("current is 4/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleQuadruple)); + + AddStep("set current to 5/4", () => timeSignature.Current.Value = new TimeSignature(5)); + + AddAssert("current is 5/4", () => timeSignature.Current.Value.Equals(new TimeSignature(5))); + AddAssert("numerator is 5", () => numeratorTextBox.Current.Value == "5"); + + AddStep("set current to 3/4", () => timeSignature.Current.Value = TimeSignature.SimpleTriple); + + AddAssert("current is 3/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleTriple)); + AddAssert("numerator is 5", () => numeratorTextBox.Current.Value == "3"); + } + + [Test] + public void TestChangeNumerator() + { + createLabelledTimeSignature(TimeSignature.SimpleQuadruple); + AddAssert("current is 4/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleQuadruple)); + + AddStep("focus text box", () => InputManager.ChangeFocus(numeratorTextBox)); + + AddStep("set numerator to 7", () => numeratorTextBox.Current.Value = "7"); + AddAssert("current is 4/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleQuadruple)); + + AddStep("drop focus", () => InputManager.ChangeFocus(null)); + AddAssert("current is 7/4", () => timeSignature.Current.Value.Equals(new TimeSignature(7))); + } + + [Test] + public void TestInvalidChangeRollbackOnCommit() + { + createLabelledTimeSignature(TimeSignature.SimpleQuadruple); + AddAssert("current is 4/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleQuadruple)); + + AddStep("focus text box", () => InputManager.ChangeFocus(numeratorTextBox)); + + AddStep("set numerator to 0", () => numeratorTextBox.Current.Value = "0"); + AddAssert("current is 4/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleQuadruple)); + + AddStep("drop focus", () => InputManager.ChangeFocus(null)); + AddAssert("current is 4/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleQuadruple)); + AddAssert("numerator is 4", () => numeratorTextBox.Current.Value == "4"); + } + } +} diff --git a/osu.Game/Screens/Edit/Timing/LabelledTimeSignature.cs b/osu.Game/Screens/Edit/Timing/LabelledTimeSignature.cs new file mode 100644 index 0000000000..52aece75ad --- /dev/null +++ b/osu.Game/Screens/Edit/Timing/LabelledTimeSignature.cs @@ -0,0 +1,93 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Beatmaps.Timing; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Graphics.UserInterfaceV2; + +namespace osu.Game.Screens.Edit.Timing +{ + internal class LabelledTimeSignature : LabelledComponent + { + public LabelledTimeSignature() + : base(false) + { + } + + protected override TimeSignatureBox CreateComponent() => new TimeSignatureBox(); + + internal class TimeSignatureBox : CompositeDrawable, IHasCurrentValue + { + private readonly BindableWithCurrent current = new BindableWithCurrent(TimeSignature.SimpleQuadruple); + + public Bindable Current + { + get => current.Current; + set => current.Current = value; + } + + private OsuNumberBox numeratorBox; + + [BackgroundDependencyLoader] + private void load() + { + AutoSizeAxes = Axes.Both; + InternalChild = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + numeratorBox = new OsuNumberBox + { + Width = 40, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + CornerRadius = CORNER_RADIUS, + CommitOnFocusLost = true + }, + new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Margin = new MarginPadding(10), + Text = "/4", + Font = OsuFont.Default.With(size: 20) + } + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Current.BindValueChanged(_ => updateFromCurrent(), true); + numeratorBox.OnCommit += (_, __) => updateFromNumeratorBox(); + } + + private void updateFromCurrent() + { + numeratorBox.Current.Value = Current.Value.Numerator.ToString(); + } + + private void updateFromNumeratorBox() + { + if (int.TryParse(numeratorBox.Current.Value, out int numerator) && numerator > 0) + Current.Value = new TimeSignature(numerator); + else + { + // trigger `Current` change to restore the numerator box's text to a valid value. + Current.TriggerChange(); + } + } + } + } +} From 54f7b1b8d0cec1638c67492fadf7c7fca8577f5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 22 Jan 2022 18:54:28 +0100 Subject: [PATCH 576/996] Use new time signature control on timing screen --- osu.Game/Screens/Edit/Timing/TimingSection.cs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/TimingSection.cs b/osu.Game/Screens/Edit/Timing/TimingSection.cs index e0b09ce980..cd0b56d338 100644 --- a/osu.Game/Screens/Edit/Timing/TimingSection.cs +++ b/osu.Game/Screens/Edit/Timing/TimingSection.cs @@ -6,7 +6,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Beatmaps.Timing; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Overlays.Settings; @@ -15,7 +14,7 @@ namespace osu.Game.Screens.Edit.Timing internal class TimingSection : Section { private SettingsSlider bpmSlider; - private SettingsDropdown timeSignature; + private LabelledTimeSignature timeSignature; private BPMTextBox bpmTextEntry; [BackgroundDependencyLoader] @@ -25,15 +24,10 @@ namespace osu.Game.Screens.Edit.Timing { bpmTextEntry = new BPMTextBox(), bpmSlider = new BPMSlider(), - timeSignature = new SettingsDropdown + timeSignature = new LabelledTimeSignature { - LabelText = "Time Signature", - Items = new[] - { - TimeSignature.SimpleTriple, - TimeSignature.SimpleQuadruple - } - }, + Label = "Time Signature" + } }); } From 48aa1677dcd28e0477bb84ae17075ba53d46e56e Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Sun, 23 Jan 2022 11:01:30 +0800 Subject: [PATCH 577/996] Include hit results of nested hit objects in statistics of perfect score --- .../Difficulty/PerformanceBreakdownCalculator.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs b/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs index 5cf63d0102..46342b237c 100644 --- a/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs @@ -1,12 +1,14 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; @@ -64,7 +66,7 @@ namespace osu.Game.Rulesets.Difficulty // create statistics assuming all hit objects have perfect hit result var statistics = beatmap.HitObjects - .Select(ho => ho.CreateJudgement().MaxResult) + .SelectMany(getPerfectHitResults) .GroupBy(hr => hr, (hr, list) => (hitResult: hr, count: list.Count())) .ToDictionary(pair => pair.hitResult, pair => pair.count); perfectPlay.Statistics = statistics; @@ -89,5 +91,13 @@ namespace osu.Game.Rulesets.Difficulty return ruleset.CreatePerformanceCalculator(difficulty.Value.Attributes, perfectPlay)?.Calculate(); }, cancellationToken); } + + private IEnumerable getPerfectHitResults(HitObject hitObject) + { + foreach (HitObject nested in hitObject.NestedHitObjects) + yield return nested.CreateJudgement().MaxResult; + + yield return hitObject.CreateJudgement().MaxResult; + } } } From 1ea5a2e6a75301341355f7a934b6b79a5f282d7c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 23 Jan 2022 10:11:07 +0300 Subject: [PATCH 578/996] Fix incorrect assert step name --- osu.Game.Tests/Visual/Editing/TestSceneLabelledTimeSignature.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneLabelledTimeSignature.cs b/osu.Game.Tests/Visual/Editing/TestSceneLabelledTimeSignature.cs index cedbb5025f..b34974dfc7 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneLabelledTimeSignature.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneLabelledTimeSignature.cs @@ -51,7 +51,7 @@ namespace osu.Game.Tests.Visual.Editing AddStep("set current to 3/4", () => timeSignature.Current.Value = TimeSignature.SimpleTriple); AddAssert("current is 3/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleTriple)); - AddAssert("numerator is 5", () => numeratorTextBox.Current.Value == "3"); + AddAssert("numerator is 3", () => numeratorTextBox.Current.Value == "3"); } [Test] From e4758c9dbbee726593078de2c111fd243b4db582 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 23 Jan 2022 10:13:32 +0300 Subject: [PATCH 579/996] Mark `LabelledTimeSignature` as public --- osu.Game/Screens/Edit/Timing/LabelledTimeSignature.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/LabelledTimeSignature.cs b/osu.Game/Screens/Edit/Timing/LabelledTimeSignature.cs index 52aece75ad..66bd341393 100644 --- a/osu.Game/Screens/Edit/Timing/LabelledTimeSignature.cs +++ b/osu.Game/Screens/Edit/Timing/LabelledTimeSignature.cs @@ -14,7 +14,7 @@ using osu.Game.Graphics.UserInterfaceV2; namespace osu.Game.Screens.Edit.Timing { - internal class LabelledTimeSignature : LabelledComponent + public class LabelledTimeSignature : LabelledComponent { public LabelledTimeSignature() : base(false) @@ -23,7 +23,7 @@ namespace osu.Game.Screens.Edit.Timing protected override TimeSignatureBox CreateComponent() => new TimeSignatureBox(); - internal class TimeSignatureBox : CompositeDrawable, IHasCurrentValue + public class TimeSignatureBox : CompositeDrawable, IHasCurrentValue { private readonly BindableWithCurrent current = new BindableWithCurrent(TimeSignature.SimpleQuadruple); From a5493ce0d1f70b6db0d9ce846ed14c42f360946e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 23 Jan 2022 17:51:32 +0900 Subject: [PATCH 580/996] Fix incorrect nesting of statements causing completely broken logic --- osu.Game/Screens/Menu/IntroScreen.cs | 54 ++++++++++++++-------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index 10be90a5f0..e66ecc74e1 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -99,51 +99,51 @@ namespace osu.Game.Screens.Menu { realmContextFactory.Run(realm => { - var sets = realm.All().Where(s => !s.DeletePending && !s.Protected).AsRealmCollection(); + var usableBeatmapSets = realm.All().Where(s => !s.DeletePending && !s.Protected).AsRealmCollection(); - int setCount = sets.Count; + int setCount = usableBeatmapSets.Count; if (setCount > 0) { - var found = sets[RNG.Next(0, setCount - 1)].Beatmaps.FirstOrDefault(); + var found = usableBeatmapSets[RNG.Next(0, setCount - 1)].Beatmaps.FirstOrDefault(); if (found != null) initialBeatmap = beatmaps.GetWorkingBeatmap(found); } }); + } - // we generally want a song to be playing on startup, so use the intro music even if a user has specified not to if no other track is available. - if (initialBeatmap == null) + // we generally want a song to be playing on startup, so use the intro music even if a user has specified not to if no other track is available. + if (initialBeatmap == null) + { + if (!loadThemedIntro()) { - if (!loadThemedIntro()) - { - // if we detect that the theme track or beatmap is unavailable this is either first startup or things are in a bad state. - // this could happen if a user has nuked their files store. for now, reimport to repair this. - var import = beatmaps.Import(new ZipArchiveReader(game.Resources.GetStream($"Tracks/{BeatmapFile}"), BeatmapFile)).GetResultSafely(); + // if we detect that the theme track or beatmap is unavailable this is either first startup or things are in a bad state. + // this could happen if a user has nuked their files store. for now, reimport to repair this. + var import = beatmaps.Import(new ZipArchiveReader(game.Resources.GetStream($"Tracks/{BeatmapFile}"), BeatmapFile)).GetResultSafely(); - import?.PerformWrite(b => b.Protected = true); + import?.PerformWrite(b => b.Protected = true); - loadThemedIntro(); - } + loadThemedIntro(); } + } - bool loadThemedIntro() + bool loadThemedIntro() + { + var setInfo = beatmaps.QueryBeatmapSet(b => b.Hash == BeatmapHash); + + if (setInfo == null) + return false; + + setInfo.PerformRead(s => { - var setInfo = beatmaps.QueryBeatmapSet(b => b.Hash == BeatmapHash); + if (s.Beatmaps.Count == 0) + return; - if (setInfo == null) - return false; + initialBeatmap = beatmaps.GetWorkingBeatmap(s.Beatmaps.First()); + }); - setInfo.PerformRead(s => - { - if (s.Beatmaps.Count == 0) - return; - - initialBeatmap = beatmaps.GetWorkingBeatmap(s.Beatmaps.First()); - }); - - return UsingThemedIntro = initialBeatmap != null; - } + return UsingThemedIntro = initialBeatmap != null; } } From 70a120ea8a5e7694c068624b6495fa857f95a4e8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 23 Jan 2022 18:00:24 +0900 Subject: [PATCH 581/996] Add missing lock coverage when using `subscriptionActions` dictionary --- osu.Game/Database/RealmContextFactory.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 6fb9a2996b..606871337c 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -249,11 +249,13 @@ namespace osu.Game.Database return new InvokeOnDisposal(() => { - // TODO: this likely needs to be run on the update thread. - if (subscriptionActions.TryGetValue(action, out var unsubscriptionAction)) + lock (contextLock) { - unsubscriptionAction?.Dispose(); - subscriptionActions.Remove(action); + if (subscriptionActions.TryGetValue(action, out var unsubscriptionAction)) + { + unsubscriptionAction?.Dispose(); + subscriptionActions.Remove(action); + } } }); } From bd0eda7e908e7366198e0007d6db9434faded441 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 23 Jan 2022 18:01:39 +0900 Subject: [PATCH 582/996] Use method instead of property for realm query retrieval --- .../Bindings/DatabasedKeyBindingContainer.cs | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs index 5a9797ffce..ac33c64391 100644 --- a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs +++ b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs @@ -46,19 +46,16 @@ namespace osu.Game.Input.Bindings throw new InvalidOperationException($"{nameof(variant)} can not be null when a non-null {nameof(ruleset)} is provided."); } - private IQueryable realmKeyBindings + private IQueryable queryRealmKeyBindings() { - get - { - string rulesetName = ruleset?.ShortName; - return realmFactory.Context.All() - .Where(b => b.RulesetName == rulesetName && b.Variant == variant); - } + string rulesetName = ruleset?.ShortName; + return realmFactory.Context.All() + .Where(b => b.RulesetName == rulesetName && b.Variant == variant); } protected override void LoadComplete() { - realmSubscription = realmFactory.Register(realm => realmKeyBindings + realmSubscription = realmFactory.Register(realm => queryRealmKeyBindings() .QueryAsyncWithNotifications((sender, changes, error) => { // The first fire of this is a bit redundant as this is being called in base.LoadComplete, @@ -80,11 +77,11 @@ namespace osu.Game.Input.Bindings { var defaults = DefaultKeyBindings.ToList(); - List newBindings = realmKeyBindings.Detach() - // this ordering is important to ensure that we read entries from the database in the order - // enforced by DefaultKeyBindings. allow for song select to handle actions that may otherwise - // have been eaten by the music controller due to query order. - .OrderBy(b => defaults.FindIndex(d => (int)d.Action == b.ActionInt)).ToList(); + List newBindings = queryRealmKeyBindings().Detach() + // this ordering is important to ensure that we read entries from the database in the order + // enforced by DefaultKeyBindings. allow for song select to handle actions that may otherwise + // have been eaten by the music controller due to query order. + .OrderBy(b => defaults.FindIndex(d => (int)d.Action == b.ActionInt)).ToList(); // In the case no bindings were found in the database, presume this usage is for a non-databased ruleset. // This actually should never be required and can be removed if it is ever deemed to cause a problem. From f39ff1eacb3b5179f70aa47f0a892d23e2651e7d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 23 Jan 2022 18:13:28 +0900 Subject: [PATCH 583/996] Add unregistration on blocking This is the first part of the requirement of sending a `ChangeSet` event to ensure correct state during blocking time --- osu.Game/Database/RealmContextFactory.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 606871337c..3e0f278e30 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -247,6 +247,8 @@ namespace osu.Game.Database registerSubscription(action); + // This token is returned to the consumer only. + // It will cause the registration to be permanently removed. return new InvokeOnDisposal(() => { lock (contextLock) @@ -269,12 +271,27 @@ namespace osu.Game.Database lock (contextLock) { + Debug.Assert(!customSubscriptionActions.TryGetValue(action, out var found) || found == null); + current_thread_subscriptions_allowed.Value = true; subscriptionActions[action] = action(realm); current_thread_subscriptions_allowed.Value = false; } } + /// + /// Unregister all subscriptions when the realm context is to be recycled. + /// Subscriptions will still remain and will be re-subscribed when the realm context returns. + /// + private void unregisterAllSubscriptions() + { + foreach (var action in subscriptionActions) + { + action.Value?.Dispose(); + subscriptionActions[action.Key] = null; + } + } + private Realm createContext() { if (isDisposed) @@ -519,6 +536,7 @@ namespace osu.Game.Database throw new InvalidOperationException(@$"{nameof(BlockAllOperations)} must be called from the update thread."); syncContext = SynchronizationContext.Current; + unregisterAllSubscriptions(); Logger.Log(@"Blocking realm operations.", LoggingTarget.Database); From 61cef42be977be4fe3cd612122de1f52556e03d1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 23 Jan 2022 19:42:26 +0900 Subject: [PATCH 584/996] Proof of concept realm subscriptions via `Register` --- osu.Game/Database/EmptyRealmSet.cs | 74 ++++++++++++++++++++ osu.Game/Database/RealmContextFactory.cs | 33 +++++++-- osu.Game/Screens/Select/BeatmapCarousel.cs | 4 +- osu.Game/Screens/Spectate/SpectatorScreen.cs | 21 +++--- 4 files changed, 112 insertions(+), 20 deletions(-) create mode 100644 osu.Game/Database/EmptyRealmSet.cs diff --git a/osu.Game/Database/EmptyRealmSet.cs b/osu.Game/Database/EmptyRealmSet.cs new file mode 100644 index 0000000000..2fecfcbe07 --- /dev/null +++ b/osu.Game/Database/EmptyRealmSet.cs @@ -0,0 +1,74 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.ComponentModel; +using Realms; +using Realms.Schema; + +#nullable enable + +namespace osu.Game.Database +{ + public class EmptyRealmSet : IRealmCollection + { + private static List emptySet => new List(); + + public IEnumerator GetEnumerator() + { + return emptySet.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)emptySet).GetEnumerator(); + } + + public int Count => emptySet.Count; + + public T this[int index] => emptySet[index]; + + public event NotifyCollectionChangedEventHandler? CollectionChanged + { + add => throw new NotImplementedException(); + remove => throw new NotImplementedException(); + } + + public event PropertyChangedEventHandler? PropertyChanged + { + add => throw new NotImplementedException(); + remove => throw new NotImplementedException(); + } + + public int IndexOf(object item) + { + return emptySet.IndexOf((T)item); + } + + public bool Contains(object item) + { + return emptySet.Contains((T)item); + } + + public IRealmCollection Freeze() + { + throw new NotImplementedException(); + } + + public IDisposable SubscribeForNotifications(NotificationCallbackDelegate callback) + { + throw new NotImplementedException(); + } + + public bool IsValid => throw new NotImplementedException(); + + public Realm Realm => throw new NotImplementedException(); + + public ObjectSchema ObjectSchema => throw new NotImplementedException(); + + public bool IsFrozen => throw new NotImplementedException(); + } +} diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 3e0f278e30..32f7ac99c1 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -84,7 +84,7 @@ namespace osu.Game.Database Logger.Log(@$"Opened realm ""{context.Config.DatabasePath}"" at version {context.Config.SchemaVersion}"); // Resubscribe any subscriptions - foreach (var action in subscriptionActions.Keys) + foreach (var action in customSubscriptionActions.Keys) registerSubscription(action); } @@ -233,7 +233,22 @@ namespace osu.Game.Database } } - private readonly Dictionary, IDisposable?> subscriptionActions = new Dictionary, IDisposable?>(); + private readonly Dictionary, IDisposable?> customSubscriptionActions = new Dictionary, IDisposable?>(); + + private readonly Dictionary, Action> realmSubscriptionsResetMap = new Dictionary, Action>(); + + public IDisposable Register(Func> query, NotificationCallbackDelegate onChanged) + where T : RealmObjectBase + { + if (!ThreadSafety.IsUpdateThread) + throw new InvalidOperationException(@$"{nameof(Register)} must be called from the update thread."); + + realmSubscriptionsResetMap.Add(action, () => onChanged(new EmptyRealmSet(), null, null)); + + return Register(action); + + IDisposable? action(Realm realm) => query(realm).QueryAsyncWithNotifications(onChanged); + } /// /// Run work on realm that will be run every time the update thread realm context gets recycled. @@ -253,10 +268,11 @@ namespace osu.Game.Database { lock (contextLock) { - if (subscriptionActions.TryGetValue(action, out var unsubscriptionAction)) + if (customSubscriptionActions.TryGetValue(action, out var unsubscriptionAction)) { unsubscriptionAction?.Dispose(); - subscriptionActions.Remove(action); + customSubscriptionActions.Remove(action); + realmSubscriptionsResetMap.Remove(action); } } }); @@ -274,7 +290,7 @@ namespace osu.Game.Database Debug.Assert(!customSubscriptionActions.TryGetValue(action, out var found) || found == null); current_thread_subscriptions_allowed.Value = true; - subscriptionActions[action] = action(realm); + customSubscriptionActions[action] = action(realm); current_thread_subscriptions_allowed.Value = false; } } @@ -285,10 +301,13 @@ namespace osu.Game.Database /// private void unregisterAllSubscriptions() { - foreach (var action in subscriptionActions) + foreach (var action in realmSubscriptionsResetMap.Values) + action(); + + foreach (var action in customSubscriptionActions) { action.Value?.Dispose(); - subscriptionActions[action.Key] = null; + customSubscriptionActions[action.Key] = null; } } diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 6f3f467170..10ba23985e 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -190,7 +190,7 @@ namespace osu.Game.Screens.Select { base.LoadComplete(); - subscriptionSets = realmFactory.Register(realm => getBeatmapSets(realm).QueryAsyncWithNotifications(beatmapSetsChanged)); + subscriptionSets = realmFactory.Register(getBeatmapSets, beatmapSetsChanged); subscriptionBeatmaps = realmFactory.Register(realm => realm.All().Where(b => !b.Hidden).QueryAsyncWithNotifications(beatmapsChanged)); // Can't use main subscriptions because we can't lookup deleted indices. @@ -274,7 +274,7 @@ namespace osu.Game.Screens.Select } } - private IRealmCollection getBeatmapSets(Realm realm) => realm.All().Where(s => !s.DeletePending && !s.Protected).AsRealmCollection(); + private IQueryable getBeatmapSets(Realm realm) => realm.All().Where(s => !s.DeletePending && !s.Protected); public void RemoveBeatmapSet(BeatmapSetInfo beatmapSet) => removeBeatmapSet(beatmapSet.ID); diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs index 9fa5bb8562..904648f727 100644 --- a/osu.Game/Screens/Spectate/SpectatorScreen.cs +++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs @@ -17,6 +17,7 @@ using osu.Game.Online.Spectator; using osu.Game.Replays; using osu.Game.Rulesets; using osu.Game.Scoring; +using Realms; namespace osu.Game.Screens.Spectate { @@ -79,23 +80,21 @@ namespace osu.Game.Screens.Spectate playingUserStates.BindTo(spectatorClient.PlayingUserStates); playingUserStates.BindCollectionChanged(onPlayingUserStatesChanged, true); - realmSubscription = realmContextFactory.Register(realm => - realm.All() - .Where(s => !s.DeletePending) - .QueryAsyncWithNotifications((items, changes, ___) => - { - if (changes?.InsertedIndices == null) - return; - - foreach (int c in changes.InsertedIndices) - beatmapUpdated(items[c]); - })); + realmSubscription = realmContextFactory.Register( + realm => realm.All().Where(s => !s.DeletePending), beatmapsChanged); foreach ((int id, var _) in userMap) spectatorClient.WatchUser(id); })); } + private void beatmapsChanged(IRealmCollection items, ChangeSet changes, Exception ___) + { + if (changes?.InsertedIndices == null) return; + + foreach (int c in changes.InsertedIndices) beatmapUpdated(items[c]); + } + private void beatmapUpdated(BeatmapSetInfo beatmapSet) { foreach ((int userId, _) in userMap) From e9e3e024a19d4867673152efa06f89842937d032 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 23 Jan 2022 19:50:29 +0900 Subject: [PATCH 585/996] Update all usages of `QueryAsyncWithNotifications` to use new `Register` pathway --- .../Bindings/DatabasedKeyBindingContainer.cs | 13 +++++----- osu.Game/Online/BeatmapDownloadTracker.cs | 4 ++-- .../OnlinePlayBeatmapAvailabilityTracker.cs | 4 ++-- osu.Game/Online/ScoreDownloadTracker.cs | 4 ++-- osu.Game/Overlays/MusicController.cs | 2 +- .../Overlays/Settings/Sections/SkinSection.cs | 19 +++++++-------- osu.Game/Screens/Select/BeatmapCarousel.cs | 6 ++--- .../Screens/Select/Carousel/TopLocalRank.cs | 24 +++++++++---------- .../Select/Leaderboards/BeatmapLeaderboard.cs | 14 +++++------ 9 files changed, 44 insertions(+), 46 deletions(-) diff --git a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs index ac33c64391..4ad5693867 100644 --- a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs +++ b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs @@ -55,13 +55,12 @@ namespace osu.Game.Input.Bindings protected override void LoadComplete() { - realmSubscription = realmFactory.Register(realm => queryRealmKeyBindings() - .QueryAsyncWithNotifications((sender, changes, error) => - { - // The first fire of this is a bit redundant as this is being called in base.LoadComplete, - // but this is safest in case the subscription is restored after a context recycle. - ReloadMappings(); - })); + realmSubscription = realmFactory.Register(realm => queryRealmKeyBindings(), (sender, changes, error) => + { + // The first fire of this is a bit redundant as this is being called in base.LoadComplete, + // but this is safest in case the subscription is restored after a context recycle. + ReloadMappings(); + }); base.LoadComplete(); } diff --git a/osu.Game/Online/BeatmapDownloadTracker.cs b/osu.Game/Online/BeatmapDownloadTracker.cs index d50ab5a347..70599a167b 100644 --- a/osu.Game/Online/BeatmapDownloadTracker.cs +++ b/osu.Game/Online/BeatmapDownloadTracker.cs @@ -42,7 +42,7 @@ namespace osu.Game.Online // Used to interact with manager classes that don't support interface types. Will eventually be replaced. var beatmapSetInfo = new BeatmapSetInfo { OnlineID = TrackedItem.OnlineID }; - realmSubscription = realmContextFactory.Register(realm => realm.All().Where(s => s.OnlineID == TrackedItem.OnlineID && !s.DeletePending).QueryAsyncWithNotifications((items, changes, ___) => + realmSubscription = realmContextFactory.Register(realm => realm.All().Where(s => s.OnlineID == TrackedItem.OnlineID && !s.DeletePending), (items, changes, ___) => { if (items.Any()) Schedule(() => UpdateState(DownloadState.LocallyAvailable)); @@ -54,7 +54,7 @@ namespace osu.Game.Online attachDownload(Downloader.GetExistingDownload(beatmapSetInfo)); }); } - })); + }); } private void downloadBegan(ArchiveDownloadRequest request) => Schedule(() => diff --git a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs index fdcf2b39c6..8dd28f5417 100644 --- a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs @@ -78,13 +78,13 @@ namespace osu.Game.Online.Rooms // handles changes to hash that didn't occur from the import process (ie. a user editing the beatmap in the editor, somehow). realmSubscription?.Dispose(); - realmSubscription = realmContextFactory.Register(realm => filteredBeatmaps().QueryAsyncWithNotifications((items, changes, ___) => + realmSubscription = realmContextFactory.Register(realm => filteredBeatmaps(), (items, changes, ___) => { if (changes == null) return; Scheduler.AddOnce(updateAvailability); - })); + }); }, true); } diff --git a/osu.Game/Online/ScoreDownloadTracker.cs b/osu.Game/Online/ScoreDownloadTracker.cs index de1d6fd94a..72e95bd6df 100644 --- a/osu.Game/Online/ScoreDownloadTracker.cs +++ b/osu.Game/Online/ScoreDownloadTracker.cs @@ -47,7 +47,7 @@ namespace osu.Game.Online Downloader.DownloadBegan += downloadBegan; Downloader.DownloadFailed += downloadFailed; - realmSubscription = realmContextFactory.Register(realm => realm.All().Where(s => ((s.OnlineID > 0 && s.OnlineID == TrackedItem.OnlineID) || s.Hash == TrackedItem.Hash) && !s.DeletePending).QueryAsyncWithNotifications((items, changes, ___) => + realmSubscription = realmContextFactory.Register(realm => realm.All().Where(s => ((s.OnlineID > 0 && s.OnlineID == TrackedItem.OnlineID) || s.Hash == TrackedItem.Hash) && !s.DeletePending), (items, changes, ___) => { if (items.Any()) Schedule(() => UpdateState(DownloadState.LocallyAvailable)); @@ -59,7 +59,7 @@ namespace osu.Game.Online attachDownload(Downloader.GetExistingDownload(scoreInfo)); }); } - })); + }); } private void downloadBegan(ArchiveDownloadRequest request) => Schedule(() => diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 01a2c9d354..24d907285a 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -93,7 +93,7 @@ namespace osu.Game.Overlays foreach (var s in availableBeatmaps) beatmapSets.Add(s.Detach()); - beatmapSubscription = realmFactory.Register(realm => availableBeatmaps.QueryAsyncWithNotifications(beatmapsChanged)); + beatmapSubscription = realmFactory.Register(realm => availableBeatmaps, beatmapsChanged); } private void beatmapsChanged(IRealmCollection sender, ChangeSet changes, Exception error) diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index 11a7275168..c767edec71 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -51,7 +51,7 @@ namespace osu.Game.Overlays.Settings.Sections private IDisposable realmSubscription; - private IQueryable realmSkins => + private IQueryable queryRealmSkins() => realmFactory.Context.All() .Where(s => !s.DeletePending) .OrderByDescending(s => s.Protected) // protected skins should be at the top. @@ -83,13 +83,12 @@ namespace osu.Game.Overlays.Settings.Sections skinDropdown.Current = dropdownBindable; - realmSubscription = realmFactory.Register(realm => realmSkins - .QueryAsyncWithNotifications((sender, changes, error) => - { - // The first fire of this is a bit redundant due to the call below, - // but this is safest in case the subscription is restored after a context recycle. - updateItems(); - })); + realmSubscription = realmFactory.Register(realm => queryRealmSkins(), (sender, changes, error) => + { + // The first fire of this is a bit redundant due to the call below, + // but this is safest in case the subscription is restored after a context recycle. + updateItems(); + }); updateItems(); @@ -129,9 +128,9 @@ namespace osu.Game.Overlays.Settings.Sections private void updateItems() { - int protectedCount = realmSkins.Count(s => s.Protected); + int protectedCount = queryRealmSkins().Count(s => s.Protected); - skinItems = realmSkins.ToLive(realmFactory); + skinItems = queryRealmSkins().ToLive(realmFactory); skinItems.Insert(protectedCount, random_skin_info); diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 10ba23985e..889a6f5b79 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -191,12 +191,12 @@ namespace osu.Game.Screens.Select base.LoadComplete(); subscriptionSets = realmFactory.Register(getBeatmapSets, beatmapSetsChanged); - subscriptionBeatmaps = realmFactory.Register(realm => realm.All().Where(b => !b.Hidden).QueryAsyncWithNotifications(beatmapsChanged)); + subscriptionBeatmaps = realmFactory.Register(realm => realm.All().Where(b => !b.Hidden), beatmapsChanged); // Can't use main subscriptions because we can't lookup deleted indices. // https://github.com/realm/realm-dotnet/discussions/2634#discussioncomment-1605595. - subscriptionDeletedSets = realmFactory.Register(realm => realm.All().Where(s => s.DeletePending && !s.Protected).QueryAsyncWithNotifications(deletedBeatmapSetsChanged)); - subscriptionHiddenBeatmaps = realmFactory.Register(realm => realm.All().Where(b => b.Hidden).QueryAsyncWithNotifications(beatmapsChanged)); + subscriptionDeletedSets = realmFactory.Register(realm => realm.All().Where(s => s.DeletePending && !s.Protected), deletedBeatmapSetsChanged); + subscriptionHiddenBeatmaps = realmFactory.Register(realm => realm.All().Where(b => b.Hidden), beatmapsChanged); } private void deletedBeatmapSetsChanged(IRealmCollection sender, ChangeSet changes, Exception error) diff --git a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs index ee3930364b..fc2188e597 100644 --- a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs +++ b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs @@ -49,18 +49,18 @@ namespace osu.Game.Screens.Select.Carousel { scoreSubscription?.Dispose(); scoreSubscription = realmFactory.Register(realm => - realm.All() - .Filter($"{nameof(ScoreInfo.User)}.{nameof(RealmUser.OnlineID)} == $0" - + $" && {nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} == $1" - + $" && {nameof(ScoreInfo.Ruleset)}.{nameof(RulesetInfo.ShortName)} == $2" - + $" && {nameof(ScoreInfo.DeletePending)} == false", api.LocalUser.Value.Id, beatmapInfo.ID, ruleset.Value.ShortName) - .OrderByDescending(s => s.TotalScore) - .QueryAsyncWithNotifications((items, changes, ___) => - { - Rank = items.FirstOrDefault()?.Rank; - // Required since presence is changed via IsPresent override - Invalidate(Invalidation.Presence); - })); + realm.All() + .Filter($"{nameof(ScoreInfo.User)}.{nameof(RealmUser.OnlineID)} == $0" + + $" && {nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} == $1" + + $" && {nameof(ScoreInfo.Ruleset)}.{nameof(RulesetInfo.ShortName)} == $2" + + $" && {nameof(ScoreInfo.DeletePending)} == false", api.LocalUser.Value.Id, beatmapInfo.ID, ruleset.Value.ShortName) + .OrderByDescending(s => s.TotalScore), + (items, changes, ___) => + { + Rank = items.FirstOrDefault()?.Rank; + // Required since presence is changed via IsPresent override + Invalidate(Invalidation.Presence); + }); }, true); } diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 954c2a6413..463f878e2a 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -114,13 +114,13 @@ namespace osu.Game.Screens.Select.Leaderboards return; scoreSubscription = realmFactory.Register(realm => - realm.All() - .Filter($"{nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} = $0", beatmapInfo.ID) - .QueryAsyncWithNotifications((_, changes, ___) => - { - if (!IsOnlineScope) - RefreshScores(); - })); + realm.All() + .Filter($"{nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} = $0", beatmapInfo.ID), + (_, changes, ___) => + { + if (!IsOnlineScope) + RefreshScores(); + }); } protected override void Reset() From db8639435538108982acffad2d9e8da710dd71e7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 23 Jan 2022 20:27:35 +0900 Subject: [PATCH 586/996] Fix `TestResources` returning a test `BeatmapSetInfo` that can't be laoded directly into realm --- osu.Game.Tests/Resources/TestResources.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Resources/TestResources.cs b/osu.Game.Tests/Resources/TestResources.cs index d2cab09ac9..81b624f908 100644 --- a/osu.Game.Tests/Resources/TestResources.cs +++ b/osu.Game.Tests/Resources/TestResources.cs @@ -80,7 +80,10 @@ namespace osu.Game.Tests.Resources public static BeatmapSetInfo CreateTestBeatmapSetInfo(int? difficultyCount = null, RulesetInfo[] rulesets = null) { int j = 0; - RulesetInfo getRuleset() => rulesets?[j++ % rulesets.Length] ?? new OsuRuleset().RulesetInfo; + + rulesets ??= new[] { new OsuRuleset().RulesetInfo }; + + RulesetInfo getRuleset() => rulesets?[j++ % rulesets.Length]; int setId = Interlocked.Increment(ref importId); From 0709a2ac9be3376193a4aeb266b497683b1bde7b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 23 Jan 2022 20:28:13 +0900 Subject: [PATCH 587/996] Add test coverage of realm subscription scenarios --- .../RealmSubscriptionRegistrationTests.cs | 138 ++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs diff --git a/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs b/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs new file mode 100644 index 0000000000..16e9f31c7b --- /dev/null +++ b/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs @@ -0,0 +1,138 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Tests.Resources; +using Realms; + +#nullable enable + +namespace osu.Game.Tests.Database +{ + [TestFixture] + public class RealmSubscriptionRegistrationTests : RealmTest + { + [Test] + public void TestSubscriptionWithContextLoss() + { + IEnumerable? resolvedItems = null; + ChangeSet? lastChanges = null; + + RunTestWithRealm((realmFactory, _) => + { + realmFactory.Write(realm => realm.Add(TestResources.CreateTestBeatmapSetInfo())); + + var registration = realmFactory.Register(realm => realm.All(), onChanged); + + testEventsArriving(true); + + // All normal until here. + // Now let's yank the main realm context. + resolvedItems = null; + lastChanges = null; + + using (realmFactory.BlockAllOperations()) + Assert.That(resolvedItems, Is.Empty); + + realmFactory.Write(realm => realm.Add(TestResources.CreateTestBeatmapSetInfo())); + + testEventsArriving(true); + + // Now let's try unsubscribing. + resolvedItems = null; + lastChanges = null; + + registration.Dispose(); + + realmFactory.Write(realm => realm.Add(TestResources.CreateTestBeatmapSetInfo())); + + testEventsArriving(false); + + // And make sure even after another context loss we don't get firings. + using (realmFactory.BlockAllOperations()) + Assert.That(resolvedItems, Is.Null); + + realmFactory.Write(realm => realm.Add(TestResources.CreateTestBeatmapSetInfo())); + + testEventsArriving(false); + + void testEventsArriving(bool shouldArrive) + { + realmFactory.Run(realm => realm.Refresh()); + + if (shouldArrive) + Assert.That(resolvedItems, Has.One.Items); + else + Assert.That(resolvedItems, Is.Null); + + realmFactory.Write(realm => + { + realm.RemoveAll(); + realm.RemoveAll(); + }); + + realmFactory.Run(realm => realm.Refresh()); + + if (shouldArrive) + Assert.That(lastChanges?.DeletedIndices, Has.One.Items); + else + Assert.That(lastChanges, Is.Null); + } + }); + + void onChanged(IRealmCollection sender, ChangeSet? changes, Exception error) + { + if (changes == null) + resolvedItems = sender; + + lastChanges = changes; + } + } + + [Test] + public void TestCustomRegisterWithContextLoss() + { + RunTestWithRealm((realmFactory, _) => + { + BeatmapSetInfo? beatmapSetInfo = null; + + realmFactory.Write(realm => realm.Add(TestResources.CreateTestBeatmapSetInfo())); + + var subscription = realmFactory.Register(realm => + { + beatmapSetInfo = realm.All().First(); + + return new InvokeOnDisposal(() => beatmapSetInfo = null); + }); + + Assert.That(beatmapSetInfo, Is.Not.Null); + + using (realmFactory.BlockAllOperations()) + { + // custom disposal action fired when context lost. + Assert.That(beatmapSetInfo, Is.Null); + } + + // re-registration after context restore. + realmFactory.Run(realm => realm.Refresh()); + Assert.That(beatmapSetInfo, Is.Not.Null); + + subscription.Dispose(); + + Assert.That(beatmapSetInfo, Is.Null); + + using (realmFactory.BlockAllOperations()) + Assert.That(beatmapSetInfo, Is.Null); + + realmFactory.Run(realm => realm.Refresh()); + Assert.That(beatmapSetInfo, Is.Null); + }); + } + } +} From 5e7993c35afaae58a3dec41dddd0463b4e07a53a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 23 Jan 2022 20:38:34 +0900 Subject: [PATCH 588/996] Post disposal to synchronisation context --- osu.Game/Database/RealmContextFactory.cs | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 32f7ac99c1..26943c1951 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -260,19 +260,29 @@ namespace osu.Game.Database if (!ThreadSafety.IsUpdateThread) throw new InvalidOperationException(@$"{nameof(Register)} must be called from the update thread."); + var syncContext = SynchronizationContext.Current; + registerSubscription(action); // This token is returned to the consumer only. // It will cause the registration to be permanently removed. return new InvokeOnDisposal(() => { - lock (contextLock) + if (ThreadSafety.IsUpdateThread) + unsubscribe(); + else + syncContext.Post(_ => unsubscribe(), null); + + void unsubscribe() { - if (customSubscriptionActions.TryGetValue(action, out var unsubscriptionAction)) + lock (contextLock) { - unsubscriptionAction?.Dispose(); - customSubscriptionActions.Remove(action); - realmSubscriptionsResetMap.Remove(action); + if (customSubscriptionActions.TryGetValue(action, out var unsubscriptionAction)) + { + unsubscriptionAction?.Dispose(); + customSubscriptionActions.Remove(action); + realmSubscriptionsResetMap.Remove(action); + } } } }); From 249f0f9697f828649c1734eeec281e0b0876ba77 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 23 Jan 2022 23:15:39 +0900 Subject: [PATCH 589/996] Add more lengthy comment explaining cyclic avoidance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Database/RealmContextFactory.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 26943c1951..c8fa298f91 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -292,7 +292,9 @@ namespace osu.Game.Database { Debug.Assert(ThreadSafety.IsUpdateThread); - // Get context outside of flag update to ensure beyond doubt this can't be cyclic. + // Retrieve context outside of flag update to ensure that the context is constructed, + // as attempting to access it inside the subscription if it's not constructed would lead to + // cyclic invocations of the subscription callback. var realm = Context; lock (contextLock) From deb167086212faa8d6c51fe911bcd54c1cc85c73 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 23 Jan 2022 23:17:33 +0900 Subject: [PATCH 590/996] Use `Array.Empty` instead of constructed list --- osu.Game/Database/EmptyRealmSet.cs | 42 +++++------------------------- 1 file changed, 7 insertions(+), 35 deletions(-) diff --git a/osu.Game/Database/EmptyRealmSet.cs b/osu.Game/Database/EmptyRealmSet.cs index 2fecfcbe07..b7f27ba035 100644 --- a/osu.Game/Database/EmptyRealmSet.cs +++ b/osu.Game/Database/EmptyRealmSet.cs @@ -15,21 +15,14 @@ namespace osu.Game.Database { public class EmptyRealmSet : IRealmCollection { - private static List emptySet => new List(); - - public IEnumerator GetEnumerator() - { - return emptySet.GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return ((IEnumerable)emptySet).GetEnumerator(); - } + private IList emptySet => Array.Empty(); + public IEnumerator GetEnumerator() => emptySet.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => emptySet.GetEnumerator(); public int Count => emptySet.Count; - public T this[int index] => emptySet[index]; + public int IndexOf(object item) => emptySet.IndexOf((T)item); + public bool Contains(object item) => emptySet.Contains((T)item); public event NotifyCollectionChangedEventHandler? CollectionChanged { @@ -43,32 +36,11 @@ namespace osu.Game.Database remove => throw new NotImplementedException(); } - public int IndexOf(object item) - { - return emptySet.IndexOf((T)item); - } - - public bool Contains(object item) - { - return emptySet.Contains((T)item); - } - - public IRealmCollection Freeze() - { - throw new NotImplementedException(); - } - - public IDisposable SubscribeForNotifications(NotificationCallbackDelegate callback) - { - throw new NotImplementedException(); - } - + public IRealmCollection Freeze() => throw new NotImplementedException(); + public IDisposable SubscribeForNotifications(NotificationCallbackDelegate callback) => throw new NotImplementedException(); public bool IsValid => throw new NotImplementedException(); - public Realm Realm => throw new NotImplementedException(); - public ObjectSchema ObjectSchema => throw new NotImplementedException(); - public bool IsFrozen => throw new NotImplementedException(); } } From 351c766ea1f211b8eebe8ff58cb7d5fd0e813739 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 23 Jan 2022 23:20:03 +0900 Subject: [PATCH 591/996] Fix one remaining instance of realm query as property --- osu.Game/Overlays/MusicController.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 24d907285a..8caa69e78c 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -80,9 +80,10 @@ namespace osu.Game.Overlays mods.BindValueChanged(_ => ResetTrackAdjustments(), true); } - private IQueryable availableBeatmaps => realmFactory.Context - .All() - .Where(s => !s.DeletePending); + private IQueryable queryRealmBeatmapSets() => + realmFactory.Context + .All() + .Where(s => !s.DeletePending); protected override void LoadComplete() { @@ -90,10 +91,10 @@ namespace osu.Game.Overlays // ensure we're ready before completing async load. // probably not a good way of handling this (as there is a period we aren't watching for changes until the realm subscription finishes up. - foreach (var s in availableBeatmaps) + foreach (var s in queryRealmBeatmapSets()) beatmapSets.Add(s.Detach()); - beatmapSubscription = realmFactory.Register(realm => availableBeatmaps, beatmapsChanged); + beatmapSubscription = realmFactory.Register(realm => queryRealmBeatmapSets(), beatmapsChanged); } private void beatmapsChanged(IRealmCollection sender, ChangeSet changes, Exception error) From 4e5a1f27a8c35f462385dfecdeffe9e11d8c14b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 23 Jan 2022 15:14:53 +0100 Subject: [PATCH 592/996] Initialise `Simple{Triple,Quadruple}` only once ever rather than create every time --- osu.Game/Beatmaps/Timing/TimeSignature.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/Timing/TimeSignature.cs b/osu.Game/Beatmaps/Timing/TimeSignature.cs index 5bfeea5e9b..eebbcc34cd 100644 --- a/osu.Game/Beatmaps/Timing/TimeSignature.cs +++ b/osu.Game/Beatmaps/Timing/TimeSignature.cs @@ -27,8 +27,8 @@ namespace osu.Game.Beatmaps.Timing Numerator = numerator; } - public static TimeSignature SimpleTriple => new TimeSignature(3); - public static TimeSignature SimpleQuadruple => new TimeSignature(4); + public static TimeSignature SimpleTriple { get; } = new TimeSignature(3); + public static TimeSignature SimpleQuadruple { get; } = new TimeSignature(4); public override string ToString() => $"{Numerator}/4"; From bd748686fad63ccb40f13ae9498a53f2c3f47570 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 23 Jan 2022 15:20:51 +0100 Subject: [PATCH 593/996] Adjust spacing of time signature numerator input box --- osu.Game/Screens/Edit/Timing/LabelledTimeSignature.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/LabelledTimeSignature.cs b/osu.Game/Screens/Edit/Timing/LabelledTimeSignature.cs index 66bd341393..51b58bd3dc 100644 --- a/osu.Game/Screens/Edit/Timing/LabelledTimeSignature.cs +++ b/osu.Game/Screens/Edit/Timing/LabelledTimeSignature.cs @@ -57,8 +57,12 @@ namespace osu.Game.Screens.Edit.Timing { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Margin = new MarginPadding(10), - Text = "/4", + Margin = new MarginPadding + { + Left = 5, + Right = CONTENT_PADDING_HORIZONTAL + }, + Text = "/ 4", Font = OsuFont.Default.With(size: 20) } } From e236f5d604ee30d92fffccac17702ea4d341abc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 23 Jan 2022 20:28:19 +0100 Subject: [PATCH 594/996] Add failing test coverage for correct beatmap filename generation on save --- 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 f89be0adf3..bf3b46c6f7 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs @@ -46,6 +46,7 @@ namespace osu.Game.Tests.Visual.Editing editorBeatmap.BeatmapInfo.Metadata.Artist = "artist"; editorBeatmap.BeatmapInfo.Metadata.Title = "title"; }); + AddStep("Set author", () => editorBeatmap.BeatmapInfo.Metadata.Author.Username = "author"); AddStep("Set difficulty name", () => editorBeatmap.BeatmapInfo.DifficultyName = "difficulty"); AddStep("Add timing point", () => editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint())); @@ -64,6 +65,7 @@ namespace osu.Game.Tests.Visual.Editing AddStep("Save", () => InputManager.Keys(PlatformAction.Save)); checkMutations(); + AddAssert("Beatmap has correct .osu file path", () => editorBeatmap.BeatmapInfo.Path == "artist - title (author) [difficulty].osu"); AddStep("Exit", () => InputManager.Key(Key.Escape)); @@ -88,6 +90,7 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("Beatmap contains single hitcircle", () => editorBeatmap.HitObjects.Count == 1); AddAssert("Beatmap has correct overall difficulty", () => editorBeatmap.Difficulty.OverallDifficulty == 7); 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"); } } From 838a9f69ed078e6e954ca393e1aa5d6fee9ac4b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 23 Jan 2022 19:41:41 +0100 Subject: [PATCH 595/996] Fix saved beatmap filename depending on `ToString()` implementation --- osu.Game/Beatmaps/BeatmapModelManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs index 44d6af5b73..ead86c1059 100644 --- a/osu.Game/Beatmaps/BeatmapModelManager.cs +++ b/osu.Game/Beatmaps/BeatmapModelManager.cs @@ -88,7 +88,7 @@ namespace osu.Game.Beatmaps private static string getFilename(BeatmapInfo beatmapInfo) { var metadata = beatmapInfo.Metadata; - return $"{metadata.Artist} - {metadata.Title} ({metadata.Author}) [{beatmapInfo.DifficultyName}].osu".GetValidArchiveContentFilename(); + return $"{metadata.Artist} - {metadata.Title} ({metadata.Author.Username}) [{beatmapInfo.DifficultyName}].osu".GetValidArchiveContentFilename(); } /// From ed84ae0ac0d8f57f66ee7e2360b3259e6a786e2c Mon Sep 17 00:00:00 2001 From: mk-56 Date: Mon, 24 Jan 2022 00:42:43 +0100 Subject: [PATCH 596/996] Adjust values to Bdach's refined taste --- .../Mods/CatchModFlashlight.cs | 22 ++++++++++++------- .../Mods/ManiaModFlashlight.cs | 20 +++++++++++------ .../Mods/OsuModFlashlight.cs | 22 ++++++++++++------- .../Mods/TaikoModFlashlight.cs | 22 ++++++++++++------- osu.Game/Rulesets/Mods/ModFlashlight.cs | 13 +++++++---- 5 files changed, 64 insertions(+), 35 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs index f75772b04e..e8f1ebdd10 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs @@ -26,14 +26,20 @@ namespace osu.Game.Rulesets.Catch.Mods [SettingSource("Initial radius", "Initial radius of the flashlight area.")] public override BindableNumber InitialRadius { get; } = new BindableNumber { - MinValue = 150f, - MaxValue = 600f, - Default = 350f, - Value = 350f, - Precision = 5f + MinValue = 0.4f, + MaxValue = 1.7f, + Default = 1f, + Value = 1f, + Precision = 0.1f }; - public override Flashlight CreateFlashlight() => new CatchFlashlight(playfield, ChangeRadius.Value, InitialRadius.Value); + protected override BindableNumber ModeMultiplier { get; } = new BindableNumber + { + Default = 350, + Value = 350, + }; + + public override Flashlight CreateFlashlight() => new CatchFlashlight(playfield, ChangeRadius.Value, InitialRadius.Value, ModeMultiplier.Value); private CatchPlayfield playfield; @@ -47,8 +53,8 @@ namespace osu.Game.Rulesets.Catch.Mods { private readonly CatchPlayfield playfield; - public CatchFlashlight(CatchPlayfield playfield, bool isRadiusBasedOnCombo, float initialRadius) - : base(isRadiusBasedOnCombo, initialRadius) + public CatchFlashlight(CatchPlayfield playfield, bool isRadiusBasedOnCombo, float initialRadius, float modeMultiplier) + : base(isRadiusBasedOnCombo, initialRadius, modeMultiplier) { this.playfield = playfield; FlashlightSize = new Vector2(0, GetRadiusFor(0)); diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs index a6a3c3be73..9ca1a72584 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs @@ -28,20 +28,26 @@ namespace osu.Game.Rulesets.Mania.Mods public override BindableNumber InitialRadius { get; } = new BindableNumber { MinValue = 0f, - MaxValue = 230f, - Default = 50f, - Value = 50f, - Precision = 5f + MaxValue = 4.5f, + Default = 1f, + Value = 1f, + Precision = 0.1f }; - public override Flashlight CreateFlashlight() => new ManiaFlashlight(ChangeRadius.Value, InitialRadius.Value); + protected override BindableNumber ModeMultiplier { get; } = new BindableNumber + { + Default = 50, + Value = 50, + }; + + public override Flashlight CreateFlashlight() => new ManiaFlashlight(ChangeRadius.Value, InitialRadius.Value, ModeMultiplier.Value); private class ManiaFlashlight : Flashlight { private readonly LayoutValue flashlightProperties = new LayoutValue(Invalidation.DrawSize); - public ManiaFlashlight(bool isRadiusBasedOnCombo, float initialRadius) - : base(isRadiusBasedOnCombo, initialRadius) + public ManiaFlashlight(bool isRadiusBasedOnCombo, float initialRadius, float modeMultiplier) + : base(isRadiusBasedOnCombo, initialRadius, modeMultiplier) { FlashlightSize = new Vector2(DrawWidth, GetRadiusFor(0)); diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs index e2a6d0f0dc..c6cf5ce4b5 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs @@ -40,16 +40,22 @@ namespace osu.Game.Rulesets.Osu.Mods [SettingSource("Initial radius", "Initial radius of the flashlight area.")] public override BindableNumber InitialRadius { get; } = new BindableNumber { - MinValue = 90f, - MaxValue = 360f, - Default = 180f, - Value = 180f, - Precision = 5f + MinValue = 0.5f, + MaxValue = 2f, + Default = 1f, + Value = 1f, + Precision = 0.1f + }; + + protected override BindableNumber ModeMultiplier { get; } = new BindableNumber + { + Default = 180, + Value = 180, }; private OsuFlashlight flashlight; - public override Flashlight CreateFlashlight() => flashlight = new OsuFlashlight(ChangeRadius.Value, InitialRadius.Value, FollowDelay.Value); + public override Flashlight CreateFlashlight() => flashlight = new OsuFlashlight(ChangeRadius.Value, InitialRadius.Value, FollowDelay.Value, ModeMultiplier.Value); public void ApplyToDrawableHitObject(DrawableHitObject drawable) { @@ -64,8 +70,8 @@ namespace osu.Game.Rulesets.Osu.Mods //public float InitialRadius { private get; set; } public bool ChangeRadius { private get; set; } - public OsuFlashlight(bool isRadiusBasedOnCombo, float initialRadius, double followDelay) - : base(isRadiusBasedOnCombo, initialRadius) + public OsuFlashlight(bool isRadiusBasedOnCombo, float initialRadius, double followDelay, float modeMultiplier) + : base(isRadiusBasedOnCombo, initialRadius, modeMultiplier) { FollowDelay = followDelay; diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs index 71c9d777ec..f235698b55 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs @@ -27,14 +27,20 @@ namespace osu.Game.Rulesets.Taiko.Mods [SettingSource("Initial radius", "Initial radius of the flashlight area.")] public override BindableNumber InitialRadius { get; } = new BindableNumber { - MinValue = 0f, - MaxValue = 400f, - Default = 250f, - Value = 250f, - Precision = 5f + MinValue = 0, + MaxValue = 1.66f, + Default = 1f, + Value = 1f, + Precision = 0.1f }; - public override Flashlight CreateFlashlight() => new TaikoFlashlight(playfield, ChangeRadius.Value, InitialRadius.Value); + protected override BindableNumber ModeMultiplier { get; } = new BindableNumber + { + Default = 250, + Value = 250, + }; + + public override Flashlight CreateFlashlight() => new TaikoFlashlight(playfield, ChangeRadius.Value, InitialRadius.Value, ModeMultiplier.Value); private TaikoPlayfield playfield; @@ -49,8 +55,8 @@ namespace osu.Game.Rulesets.Taiko.Mods private readonly LayoutValue flashlightProperties = new LayoutValue(Invalidation.DrawSize); private readonly TaikoPlayfield taikoPlayfield; - public TaikoFlashlight(TaikoPlayfield taikoPlayfield, bool isRadiusBasedOnCombo, float initialRadius) - : base(isRadiusBasedOnCombo, initialRadius) + public TaikoFlashlight(TaikoPlayfield taikoPlayfield, bool isRadiusBasedOnCombo, float initialRadius, float modeMultiplier) + : base(isRadiusBasedOnCombo, initialRadius, modeMultiplier) { this.taikoPlayfield = taikoPlayfield; FlashlightSize = getSizeFor(0); diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index 51006d96e8..d16f310582 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -38,6 +38,8 @@ namespace osu.Game.Rulesets.Mods [SettingSource("Initial radius", "Initial radius of the flashlight area.")] public abstract BindableNumber InitialRadius { get; } + + protected abstract BindableNumber ModeMultiplier { get; } } public abstract class ModFlashlight : ModFlashlight, IApplicableToDrawableRuleset, IApplicableToScoreProcessor @@ -100,10 +102,13 @@ namespace osu.Game.Rulesets.Mods public readonly float InitialRadius; - protected Flashlight(bool isRadiusBasedOnCombo, float initialRadius) + public readonly float ModeMultiplier; + + protected Flashlight(bool isRadiusBasedOnCombo, float initialRadius, float modeMultiplier) { IsRadiusBasedOnCombo = isRadiusBasedOnCombo; InitialRadius = initialRadius; + ModeMultiplier = modeMultiplier; } [BackgroundDependencyLoader] @@ -142,12 +147,12 @@ namespace osu.Game.Rulesets.Mods if (IsRadiusBasedOnCombo) { if (combo > 200) - return InitialRadius * 0.8f; + return InitialRadius * 0.8f * ModeMultiplier; else if (combo > 100) - return InitialRadius * 0.9f; + return InitialRadius * 0.9f * ModeMultiplier; } - return InitialRadius; + return InitialRadius * ModeMultiplier; } private Vector2 flashlightPosition; From 997c13f64367f07fcd81f0107408774d2f0fd241 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Jan 2022 13:36:51 +0900 Subject: [PATCH 597/996] Add locking over `realmSubscriptionsResetMap` for sanity --- osu.Game/Database/RealmContextFactory.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index c8fa298f91..522a5fdfd9 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -243,9 +243,11 @@ namespace osu.Game.Database if (!ThreadSafety.IsUpdateThread) throw new InvalidOperationException(@$"{nameof(Register)} must be called from the update thread."); - realmSubscriptionsResetMap.Add(action, () => onChanged(new EmptyRealmSet(), null, null)); - - return Register(action); + lock (contextLock) + { + realmSubscriptionsResetMap.Add(action, () => onChanged(new EmptyRealmSet(), null, null)); + return Register(action); + } IDisposable? action(Realm realm) => query(realm).QueryAsyncWithNotifications(onChanged); } From d7a9c5fd410e370f89e83ef1bd3475a418ffe4ed Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Jan 2022 14:36:48 +0900 Subject: [PATCH 598/996] Add settings buttons to allow temporarily blocking realm access --- .../Sections/DebugSettings/MemorySettings.cs | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs b/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs index 8d4fc5fc9f..f058c274e4 100644 --- a/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs +++ b/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.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. +using System.Threading; +using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Localisation; @@ -17,6 +19,9 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings [BackgroundDependencyLoader] private void load(GameHost host, RealmContextFactory realmFactory) { + SettingsButton blockAction; + SettingsButton unblockAction; + Children = new Drawable[] { new SettingsButton @@ -35,6 +40,43 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings } } }, + blockAction = new SettingsButton + { + Text = "Block realm", + }, + unblockAction = new SettingsButton + { + Text = "Unblock realm", + }, + }; + + blockAction.Action = () => + { + var blocking = realmFactory.BlockAllOperations(); + blockAction.Enabled.Value = false; + + // As a safety measure, unblock after 10 seconds. + // This is to handle the case where a dev may block, but then something on the update thread + // accesses realm and blocks for eternity. + Task.Factory.StartNew(() => + { + Thread.Sleep(10000); + unblock(); + }); + + unblockAction.Action = unblock; + + void unblock() + { + blocking?.Dispose(); + blocking = null; + + Scheduler.Add(() => + { + blockAction.Enabled.Value = true; + unblockAction.Action = null; + }); + } }; } } From 40aa8731900bd7f856e5f5b417aa85897bf79f0f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Jan 2022 14:37:36 +0900 Subject: [PATCH 599/996] Rename register methods to better explain their purpose --- osu.Game.Tests/Database/GeneralUsageTests.cs | 2 +- osu.Game.Tests/Database/RealmLiveTests.cs | 2 +- .../RealmSubscriptionRegistrationTests.cs | 4 ++-- osu.Game/Database/RealmContextFactory.cs | 18 +++++++++--------- osu.Game/Database/RealmObjectExtensions.cs | 2 +- .../Bindings/DatabasedKeyBindingContainer.cs | 2 +- osu.Game/Online/BeatmapDownloadTracker.cs | 2 +- .../OnlinePlayBeatmapAvailabilityTracker.cs | 2 +- osu.Game/Online/ScoreDownloadTracker.cs | 2 +- osu.Game/Overlays/MusicController.cs | 2 +- .../Overlays/Settings/Sections/SkinSection.cs | 2 +- osu.Game/Screens/Select/BeatmapCarousel.cs | 8 ++++---- .../Screens/Select/Carousel/TopLocalRank.cs | 2 +- .../Select/Leaderboards/BeatmapLeaderboard.cs | 2 +- osu.Game/Screens/Spectate/SpectatorScreen.cs | 2 +- 15 files changed, 27 insertions(+), 27 deletions(-) diff --git a/osu.Game.Tests/Database/GeneralUsageTests.cs b/osu.Game.Tests/Database/GeneralUsageTests.cs index c82c1b6e59..3c62153d9e 100644 --- a/osu.Game.Tests/Database/GeneralUsageTests.cs +++ b/osu.Game.Tests/Database/GeneralUsageTests.cs @@ -46,7 +46,7 @@ namespace osu.Game.Tests.Database { bool callbackRan = false; - realmFactory.Register(realm => + realmFactory.RegisterCustomSubscription(realm => { var subscription = realm.All().QueryAsyncWithNotifications((sender, changes, error) => { diff --git a/osu.Game.Tests/Database/RealmLiveTests.cs b/osu.Game.Tests/Database/RealmLiveTests.cs index 0e6ad910d9..d53fcb9ac7 100644 --- a/osu.Game.Tests/Database/RealmLiveTests.cs +++ b/osu.Game.Tests/Database/RealmLiveTests.cs @@ -239,7 +239,7 @@ namespace osu.Game.Tests.Database { int changesTriggered = 0; - realmFactory.Register(outerRealm => + realmFactory.RegisterCustomSubscription(outerRealm => { outerRealm.All().QueryAsyncWithNotifications(gotChange); ILive? liveBeatmap = null; diff --git a/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs b/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs index 16e9f31c7b..1799b95905 100644 --- a/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs +++ b/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs @@ -28,7 +28,7 @@ namespace osu.Game.Tests.Database { realmFactory.Write(realm => realm.Add(TestResources.CreateTestBeatmapSetInfo())); - var registration = realmFactory.Register(realm => realm.All(), onChanged); + var registration = realmFactory.RegisterForNotifications(realm => realm.All(), onChanged); testEventsArriving(true); @@ -104,7 +104,7 @@ namespace osu.Game.Tests.Database realmFactory.Write(realm => realm.Add(TestResources.CreateTestBeatmapSetInfo())); - var subscription = realmFactory.Register(realm => + var subscription = realmFactory.RegisterCustomSubscription(realm => { beatmapSetInfo = realm.All().First(); diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 522a5fdfd9..697caf8cd3 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -63,6 +63,10 @@ namespace osu.Game.Database private readonly ThreadLocal currentThreadCanCreateContexts = new ThreadLocal(); + private readonly Dictionary, IDisposable?> customSubscriptionActions = new Dictionary, IDisposable?>(); + + private readonly Dictionary, Action> realmSubscriptionsResetMap = new Dictionary, Action>(); + private static readonly GlobalStatistic contexts_created = GlobalStatistics.Get(@"Realm", @"Contexts (Created)"); private readonly object contextLock = new object(); @@ -233,20 +237,16 @@ namespace osu.Game.Database } } - private readonly Dictionary, IDisposable?> customSubscriptionActions = new Dictionary, IDisposable?>(); - - private readonly Dictionary, Action> realmSubscriptionsResetMap = new Dictionary, Action>(); - - public IDisposable Register(Func> query, NotificationCallbackDelegate onChanged) + public IDisposable RegisterForNotifications(Func> query, NotificationCallbackDelegate onChanged) where T : RealmObjectBase { if (!ThreadSafety.IsUpdateThread) - throw new InvalidOperationException(@$"{nameof(Register)} must be called from the update thread."); + throw new InvalidOperationException(@$"{nameof(RegisterForNotifications)} must be called from the update thread."); lock (contextLock) { realmSubscriptionsResetMap.Add(action, () => onChanged(new EmptyRealmSet(), null, null)); - return Register(action); + return RegisterCustomSubscription(action); } IDisposable? action(Realm realm) => query(realm).QueryAsyncWithNotifications(onChanged); @@ -257,10 +257,10 @@ namespace osu.Game.Database /// /// The work to run. Return value should be an from QueryAsyncWithNotifications, or an to clean up any bindings. /// An which should be disposed to unsubscribe any inner subscription. - public IDisposable Register(Func action) + public IDisposable RegisterCustomSubscription(Func action) { if (!ThreadSafety.IsUpdateThread) - throw new InvalidOperationException(@$"{nameof(Register)} must be called from the update thread."); + throw new InvalidOperationException(@$"{nameof(RegisterForNotifications)} must be called from the update thread."); var syncContext = SynchronizationContext.Current; diff --git a/osu.Game/Database/RealmObjectExtensions.cs b/osu.Game/Database/RealmObjectExtensions.cs index c30e1699b9..d9026d165d 100644 --- a/osu.Game/Database/RealmObjectExtensions.cs +++ b/osu.Game/Database/RealmObjectExtensions.cs @@ -272,7 +272,7 @@ namespace osu.Game.Database where T : RealmObjectBase { if (!RealmContextFactory.CurrentThreadSubscriptionsAllowed) - throw new InvalidOperationException($"Make sure to call {nameof(RealmContextFactory)}.{nameof(RealmContextFactory.Register)}"); + throw new InvalidOperationException($"Make sure to call {nameof(RealmContextFactory)}.{nameof(RealmContextFactory.RegisterForNotifications)}"); return collection.SubscribeForNotifications(callback); } diff --git a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs index 4ad5693867..9cd9865441 100644 --- a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs +++ b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs @@ -55,7 +55,7 @@ namespace osu.Game.Input.Bindings protected override void LoadComplete() { - realmSubscription = realmFactory.Register(realm => queryRealmKeyBindings(), (sender, changes, error) => + realmSubscription = realmFactory.RegisterForNotifications(realm => queryRealmKeyBindings(), (sender, changes, error) => { // The first fire of this is a bit redundant as this is being called in base.LoadComplete, // but this is safest in case the subscription is restored after a context recycle. diff --git a/osu.Game/Online/BeatmapDownloadTracker.cs b/osu.Game/Online/BeatmapDownloadTracker.cs index 70599a167b..3f45afb6b2 100644 --- a/osu.Game/Online/BeatmapDownloadTracker.cs +++ b/osu.Game/Online/BeatmapDownloadTracker.cs @@ -42,7 +42,7 @@ namespace osu.Game.Online // Used to interact with manager classes that don't support interface types. Will eventually be replaced. var beatmapSetInfo = new BeatmapSetInfo { OnlineID = TrackedItem.OnlineID }; - realmSubscription = realmContextFactory.Register(realm => realm.All().Where(s => s.OnlineID == TrackedItem.OnlineID && !s.DeletePending), (items, changes, ___) => + realmSubscription = realmContextFactory.RegisterForNotifications(realm => realm.All().Where(s => s.OnlineID == TrackedItem.OnlineID && !s.DeletePending), (items, changes, ___) => { if (items.Any()) Schedule(() => UpdateState(DownloadState.LocallyAvailable)); diff --git a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs index 8dd28f5417..28c41bec33 100644 --- a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs @@ -78,7 +78,7 @@ namespace osu.Game.Online.Rooms // handles changes to hash that didn't occur from the import process (ie. a user editing the beatmap in the editor, somehow). realmSubscription?.Dispose(); - realmSubscription = realmContextFactory.Register(realm => filteredBeatmaps(), (items, changes, ___) => + realmSubscription = realmContextFactory.RegisterForNotifications(realm => filteredBeatmaps(), (items, changes, ___) => { if (changes == null) return; diff --git a/osu.Game/Online/ScoreDownloadTracker.cs b/osu.Game/Online/ScoreDownloadTracker.cs index 72e95bd6df..64ceee9fe6 100644 --- a/osu.Game/Online/ScoreDownloadTracker.cs +++ b/osu.Game/Online/ScoreDownloadTracker.cs @@ -47,7 +47,7 @@ namespace osu.Game.Online Downloader.DownloadBegan += downloadBegan; Downloader.DownloadFailed += downloadFailed; - realmSubscription = realmContextFactory.Register(realm => realm.All().Where(s => ((s.OnlineID > 0 && s.OnlineID == TrackedItem.OnlineID) || s.Hash == TrackedItem.Hash) && !s.DeletePending), (items, changes, ___) => + realmSubscription = realmContextFactory.RegisterForNotifications(realm => realm.All().Where(s => ((s.OnlineID > 0 && s.OnlineID == TrackedItem.OnlineID) || s.Hash == TrackedItem.Hash) && !s.DeletePending), (items, changes, ___) => { if (items.Any()) Schedule(() => UpdateState(DownloadState.LocallyAvailable)); diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 8caa69e78c..8df7ed9736 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -94,7 +94,7 @@ namespace osu.Game.Overlays foreach (var s in queryRealmBeatmapSets()) beatmapSets.Add(s.Detach()); - beatmapSubscription = realmFactory.Register(realm => queryRealmBeatmapSets(), beatmapsChanged); + beatmapSubscription = realmFactory.RegisterForNotifications(realm => queryRealmBeatmapSets(), beatmapsChanged); } private void beatmapsChanged(IRealmCollection sender, ChangeSet changes, Exception error) diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index c767edec71..6e5cd0da2d 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -83,7 +83,7 @@ namespace osu.Game.Overlays.Settings.Sections skinDropdown.Current = dropdownBindable; - realmSubscription = realmFactory.Register(realm => queryRealmSkins(), (sender, changes, error) => + realmSubscription = realmFactory.RegisterForNotifications(realm => queryRealmSkins(), (sender, changes, error) => { // The first fire of this is a bit redundant due to the call below, // but this is safest in case the subscription is restored after a context recycle. diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 889a6f5b79..5a6295fe74 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -190,13 +190,13 @@ namespace osu.Game.Screens.Select { base.LoadComplete(); - subscriptionSets = realmFactory.Register(getBeatmapSets, beatmapSetsChanged); - subscriptionBeatmaps = realmFactory.Register(realm => realm.All().Where(b => !b.Hidden), beatmapsChanged); + subscriptionSets = realmFactory.RegisterForNotifications(getBeatmapSets, beatmapSetsChanged); + subscriptionBeatmaps = realmFactory.RegisterForNotifications(realm => realm.All().Where(b => !b.Hidden), beatmapsChanged); // Can't use main subscriptions because we can't lookup deleted indices. // https://github.com/realm/realm-dotnet/discussions/2634#discussioncomment-1605595. - subscriptionDeletedSets = realmFactory.Register(realm => realm.All().Where(s => s.DeletePending && !s.Protected), deletedBeatmapSetsChanged); - subscriptionHiddenBeatmaps = realmFactory.Register(realm => realm.All().Where(b => b.Hidden), beatmapsChanged); + subscriptionDeletedSets = realmFactory.RegisterForNotifications(realm => realm.All().Where(s => s.DeletePending && !s.Protected), deletedBeatmapSetsChanged); + subscriptionHiddenBeatmaps = realmFactory.RegisterForNotifications(realm => realm.All().Where(b => b.Hidden), beatmapsChanged); } private void deletedBeatmapSetsChanged(IRealmCollection sender, ChangeSet changes, Exception error) diff --git a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs index fc2188e597..c03d464ef6 100644 --- a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs +++ b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs @@ -48,7 +48,7 @@ namespace osu.Game.Screens.Select.Carousel ruleset.BindValueChanged(_ => { scoreSubscription?.Dispose(); - scoreSubscription = realmFactory.Register(realm => + scoreSubscription = realmFactory.RegisterForNotifications(realm => realm.All() .Filter($"{nameof(ScoreInfo.User)}.{nameof(RealmUser.OnlineID)} == $0" + $" && {nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} == $1" diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 463f878e2a..5f288b972b 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -113,7 +113,7 @@ namespace osu.Game.Screens.Select.Leaderboards if (beatmapInfo == null) return; - scoreSubscription = realmFactory.Register(realm => + scoreSubscription = realmFactory.RegisterForNotifications(realm => realm.All() .Filter($"{nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} = $0", beatmapInfo.ID), (_, changes, ___) => diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs index 904648f727..4abde56ea6 100644 --- a/osu.Game/Screens/Spectate/SpectatorScreen.cs +++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs @@ -80,7 +80,7 @@ namespace osu.Game.Screens.Spectate playingUserStates.BindTo(spectatorClient.PlayingUserStates); playingUserStates.BindCollectionChanged(onPlayingUserStatesChanged, true); - realmSubscription = realmContextFactory.Register( + realmSubscription = realmContextFactory.RegisterForNotifications( realm => realm.All().Where(s => !s.DeletePending), beatmapsChanged); foreach ((int id, var _) in userMap) From cb319cebdbae8cf70e4b750b1bd80ce655e41183 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Jan 2022 14:48:55 +0900 Subject: [PATCH 600/996] Refactor naming and add more comments to help understanding in `RealmContextFactory` subscription logic --- osu.Game/Database/RealmContextFactory.cs | 39 ++++++++++++++++-------- 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 697caf8cd3..0137e22e94 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -63,8 +63,22 @@ namespace osu.Game.Database private readonly ThreadLocal currentThreadCanCreateContexts = new ThreadLocal(); - private readonly Dictionary, IDisposable?> customSubscriptionActions = new Dictionary, IDisposable?>(); + /// + /// Holds a map of functions registered via and and a coinciding action which when triggered, + /// will unregister the subscription from realm. + /// + /// Put another way, the key is an action which registers the subscription with realm. The returned from the action is stored as the value and only + /// used internally. + /// + /// Entries in this dictionary are only removed when a consumer signals that the subscription should be permanently ceased (via their own ). + /// + private readonly Dictionary, IDisposable?> customSubscriptionsResetMap = new Dictionary, IDisposable?>(); + /// + /// Holds a map of functions registered via and a coinciding action which when triggered, + /// fires a change set event with an empty collection. This is used to inform subscribers when a realm context goes away, and ensure they don't use invalidated + /// managed realm objects from a previous firing. + /// private readonly Dictionary, Action> realmSubscriptionsResetMap = new Dictionary, Action>(); private static readonly GlobalStatistic contexts_created = GlobalStatistics.Get(@"Realm", @"Contexts (Created)"); @@ -88,7 +102,7 @@ namespace osu.Game.Database Logger.Log(@$"Opened realm ""{context.Config.DatabasePath}"" at version {context.Config.SchemaVersion}"); // Resubscribe any subscriptions - foreach (var action in customSubscriptionActions.Keys) + foreach (var action in customSubscriptionsResetMap.Keys) registerSubscription(action); } @@ -245,11 +259,12 @@ namespace osu.Game.Database lock (contextLock) { + Func action = realm => query(realm).QueryAsyncWithNotifications(onChanged); + + // Store an action which is used when blocking to ensure consumers don't use results of a stale changeset firing. realmSubscriptionsResetMap.Add(action, () => onChanged(new EmptyRealmSet(), null, null)); return RegisterCustomSubscription(action); } - - IDisposable? action(Realm realm) => query(realm).QueryAsyncWithNotifications(onChanged); } /// @@ -266,8 +281,8 @@ namespace osu.Game.Database registerSubscription(action); - // This token is returned to the consumer only. - // It will cause the registration to be permanently removed. + // This token is returned to the consumer. + // When disposed, it will cause the registration to be permanently ceased (unsubscribed with realm and unregistered by this class). return new InvokeOnDisposal(() => { if (ThreadSafety.IsUpdateThread) @@ -279,10 +294,10 @@ namespace osu.Game.Database { lock (contextLock) { - if (customSubscriptionActions.TryGetValue(action, out var unsubscriptionAction)) + if (customSubscriptionsResetMap.TryGetValue(action, out var unsubscriptionAction)) { unsubscriptionAction?.Dispose(); - customSubscriptionActions.Remove(action); + customSubscriptionsResetMap.Remove(action); realmSubscriptionsResetMap.Remove(action); } } @@ -301,10 +316,10 @@ namespace osu.Game.Database lock (contextLock) { - Debug.Assert(!customSubscriptionActions.TryGetValue(action, out var found) || found == null); + Debug.Assert(!customSubscriptionsResetMap.TryGetValue(action, out var found) || found == null); current_thread_subscriptions_allowed.Value = true; - customSubscriptionActions[action] = action(realm); + customSubscriptionsResetMap[action] = action(realm); current_thread_subscriptions_allowed.Value = false; } } @@ -318,10 +333,10 @@ namespace osu.Game.Database foreach (var action in realmSubscriptionsResetMap.Values) action(); - foreach (var action in customSubscriptionActions) + foreach (var action in customSubscriptionsResetMap) { action.Value?.Dispose(); - customSubscriptionActions[action.Key] = null; + customSubscriptionsResetMap[action.Key] = null; } } From 1e483ece322f4c78c4ff8eab95c1993d388a1f9c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Jan 2022 16:40:16 +0900 Subject: [PATCH 601/996] Avoid adding "exit all screens" step when running tests interactively --- osu.Game/Tests/Visual/ScreenTestScene.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/ScreenTestScene.cs b/osu.Game/Tests/Visual/ScreenTestScene.cs index c44a848275..b6f6ca6daa 100644 --- a/osu.Game/Tests/Visual/ScreenTestScene.cs +++ b/osu.Game/Tests/Visual/ScreenTestScene.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Development; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Logging; @@ -48,7 +49,11 @@ namespace osu.Game.Tests.Visual public virtual void SetUpSteps() => addExitAllScreensStep(); [TearDownSteps] - public virtual void TearDownSteps() => addExitAllScreensStep(); + public virtual void TearDownSteps() + { + if (DebugUtils.IsNUnitRunning) + addExitAllScreensStep(); + } private void addExitAllScreensStep() { From e22aea0613698af3fa5870fd745764306a9273dd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Jan 2022 17:05:49 +0900 Subject: [PATCH 602/996] Apply same fix to `OsuGameTestScene` --- osu.Game/Tests/Visual/OsuGameTestScene.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Tests/Visual/OsuGameTestScene.cs b/osu.Game/Tests/Visual/OsuGameTestScene.cs index 6a11bd3fea..ebbd9bb5dd 100644 --- a/osu.Game/Tests/Visual/OsuGameTestScene.cs +++ b/osu.Game/Tests/Visual/OsuGameTestScene.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Development; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -72,8 +73,11 @@ namespace osu.Game.Tests.Visual [TearDownSteps] public void TearDownSteps() { - AddStep("exit game", () => Game.Exit()); - AddUntilStep("wait for game exit", () => Game.Parent == null); + if (DebugUtils.IsNUnitRunning) + { + AddStep("exit game", () => Game.Exit()); + AddUntilStep("wait for game exit", () => Game.Parent == null); + } } protected void CreateGame() From 161a2a321ef5ecc93dfeb9d5a99af66f7881be2a Mon Sep 17 00:00:00 2001 From: mk-56 Date: Mon, 24 Jan 2022 09:07:07 +0100 Subject: [PATCH 603/996] Remove bindable from ModeMultiplier --- osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs | 8 ++------ osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs | 8 ++------ osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs | 8 ++------ osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs | 8 ++------ osu.Game/Rulesets/Mods/ModFlashlight.cs | 2 +- 5 files changed, 9 insertions(+), 25 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs index e8f1ebdd10..4fbbf63abf 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs @@ -33,13 +33,9 @@ namespace osu.Game.Rulesets.Catch.Mods Precision = 0.1f }; - protected override BindableNumber ModeMultiplier { get; } = new BindableNumber - { - Default = 350, - Value = 350, - }; + protected override float ModeMultiplier => 350; - public override Flashlight CreateFlashlight() => new CatchFlashlight(playfield, ChangeRadius.Value, InitialRadius.Value, ModeMultiplier.Value); + public override Flashlight CreateFlashlight() => new CatchFlashlight(playfield, ChangeRadius.Value, InitialRadius.Value, ModeMultiplier); private CatchPlayfield playfield; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs index 9ca1a72584..61f73a1ee5 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs @@ -34,13 +34,9 @@ namespace osu.Game.Rulesets.Mania.Mods Precision = 0.1f }; - protected override BindableNumber ModeMultiplier { get; } = new BindableNumber - { - Default = 50, - Value = 50, - }; + protected override float ModeMultiplier => 50; - public override Flashlight CreateFlashlight() => new ManiaFlashlight(ChangeRadius.Value, InitialRadius.Value, ModeMultiplier.Value); + public override Flashlight CreateFlashlight() => new ManiaFlashlight(ChangeRadius.Value, InitialRadius.Value, ModeMultiplier); private class ManiaFlashlight : Flashlight { diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs index c6cf5ce4b5..75299863a9 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs @@ -47,15 +47,11 @@ namespace osu.Game.Rulesets.Osu.Mods Precision = 0.1f }; - protected override BindableNumber ModeMultiplier { get; } = new BindableNumber - { - Default = 180, - Value = 180, - }; + protected override float ModeMultiplier => 180; private OsuFlashlight flashlight; - public override Flashlight CreateFlashlight() => flashlight = new OsuFlashlight(ChangeRadius.Value, InitialRadius.Value, FollowDelay.Value, ModeMultiplier.Value); + public override Flashlight CreateFlashlight() => flashlight = new OsuFlashlight(ChangeRadius.Value, InitialRadius.Value, FollowDelay.Value, ModeMultiplier); public void ApplyToDrawableHitObject(DrawableHitObject drawable) { diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs index f235698b55..65a173b491 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs @@ -34,13 +34,9 @@ namespace osu.Game.Rulesets.Taiko.Mods Precision = 0.1f }; - protected override BindableNumber ModeMultiplier { get; } = new BindableNumber - { - Default = 250, - Value = 250, - }; + protected override float ModeMultiplier => 250; - public override Flashlight CreateFlashlight() => new TaikoFlashlight(playfield, ChangeRadius.Value, InitialRadius.Value, ModeMultiplier.Value); + public override Flashlight CreateFlashlight() => new TaikoFlashlight(playfield, ChangeRadius.Value, InitialRadius.Value, ModeMultiplier); private TaikoPlayfield playfield; diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index d16f310582..7b980e8097 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Mods [SettingSource("Initial radius", "Initial radius of the flashlight area.")] public abstract BindableNumber InitialRadius { get; } - protected abstract BindableNumber ModeMultiplier { get; } + protected abstract float ModeMultiplier { get; } } public abstract class ModFlashlight : ModFlashlight, IApplicableToDrawableRuleset, IApplicableToScoreProcessor From 52cd906af6552ecb099d40454d10e2e266a4e92f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Jan 2022 17:45:31 +0900 Subject: [PATCH 604/996] Move context retrieval inside lock --- osu.Game/Database/RealmContextFactory.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 0137e22e94..be484329bc 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -309,13 +309,13 @@ namespace osu.Game.Database { Debug.Assert(ThreadSafety.IsUpdateThread); - // Retrieve context outside of flag update to ensure that the context is constructed, - // as attempting to access it inside the subscription if it's not constructed would lead to - // cyclic invocations of the subscription callback. - var realm = Context; - lock (contextLock) { + // Retrieve context outside of flag update to ensure that the context is constructed, + // as attempting to access it inside the subscription if it's not constructed would lead to + // cyclic invocations of the subscription callback. + var realm = Context; + Debug.Assert(!customSubscriptionsResetMap.TryGetValue(action, out var found) || found == null); current_thread_subscriptions_allowed.Value = true; From abf14f09820bb8f5859604135174f36f0e9e7cc5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Jan 2022 17:46:53 +0900 Subject: [PATCH 605/996] Lock unregistration for sanity --- osu.Game/Database/RealmContextFactory.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index be484329bc..5dd4e88ccd 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -330,13 +330,16 @@ namespace osu.Game.Database /// private void unregisterAllSubscriptions() { - foreach (var action in realmSubscriptionsResetMap.Values) - action(); - - foreach (var action in customSubscriptionsResetMap) + lock (contextLock) { - action.Value?.Dispose(); - customSubscriptionsResetMap[action.Key] = null; + foreach (var action in realmSubscriptionsResetMap.Values) + action(); + + foreach (var action in customSubscriptionsResetMap) + { + action.Value?.Dispose(); + customSubscriptionsResetMap[action.Key] = null; + } } } From f4e7211ef1356f6598f4ad6038d896c771f39cbd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Jan 2022 17:52:36 +0900 Subject: [PATCH 606/996] Add xmldoc for `RegisterForNotifications` --- osu.Game/Database/RealmContextFactory.cs | 27 +++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 5dd4e88ccd..f8470b8c73 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -251,7 +251,28 @@ namespace osu.Game.Database } } - public IDisposable RegisterForNotifications(Func> query, NotificationCallbackDelegate onChanged) + /// + /// Subscribe to a realm collection and begin watching for asynchronous changes. + /// + /// + /// This adds osu! specific thread and managed state safety checks on top of . + /// + /// In addition to the documented realm behaviour, we have the additional requirement of handling subscriptions over potential context loss. + /// When this happens, callback events will be automatically fired: + /// - On context loss, a callback with an empty collection and null will be invoked. + /// - On context revival, a standard initial realm callback will arrive, with null and an up-to-date collection. + /// + /// The to observe for changes. + /// Type of the elements in the list. + /// + /// The callback to be invoked with the updated . + /// + /// A subscription token. It must be kept alive for as long as you want to receive change notifications. + /// To stop receiving notifications, call . + /// + /// May be null in the case the provided collection is not managed. + /// + public IDisposable RegisterForNotifications(Func> query, NotificationCallbackDelegate callback) where T : RealmObjectBase { if (!ThreadSafety.IsUpdateThread) @@ -259,10 +280,10 @@ namespace osu.Game.Database lock (contextLock) { - Func action = realm => query(realm).QueryAsyncWithNotifications(onChanged); + Func action = realm => query(realm).QueryAsyncWithNotifications(callback); // Store an action which is used when blocking to ensure consumers don't use results of a stale changeset firing. - realmSubscriptionsResetMap.Add(action, () => onChanged(new EmptyRealmSet(), null, null)); + realmSubscriptionsResetMap.Add(action, () => callback(new EmptyRealmSet(), null, null)); return RegisterCustomSubscription(action); } } From bf5bf8d1fd147f1a0e27756321512eaa0676e3d7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Jan 2022 17:58:53 +0900 Subject: [PATCH 607/996] Rename dictionaries to match methods --- osu.Game/Database/RealmContextFactory.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index f8470b8c73..738d0a70a9 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -79,7 +79,7 @@ namespace osu.Game.Database /// fires a change set event with an empty collection. This is used to inform subscribers when a realm context goes away, and ensure they don't use invalidated /// managed realm objects from a previous firing. /// - private readonly Dictionary, Action> realmSubscriptionsResetMap = new Dictionary, Action>(); + private readonly Dictionary, Action> notificationsResetMap = new Dictionary, Action>(); private static readonly GlobalStatistic contexts_created = GlobalStatistics.Get(@"Realm", @"Contexts (Created)"); @@ -283,7 +283,7 @@ namespace osu.Game.Database Func action = realm => query(realm).QueryAsyncWithNotifications(callback); // Store an action which is used when blocking to ensure consumers don't use results of a stale changeset firing. - realmSubscriptionsResetMap.Add(action, () => callback(new EmptyRealmSet(), null, null)); + notificationsResetMap.Add(action, () => callback(new EmptyRealmSet(), null, null)); return RegisterCustomSubscription(action); } } @@ -319,7 +319,7 @@ namespace osu.Game.Database { unsubscriptionAction?.Dispose(); customSubscriptionsResetMap.Remove(action); - realmSubscriptionsResetMap.Remove(action); + notificationsResetMap.Remove(action); } } } @@ -353,7 +353,7 @@ namespace osu.Game.Database { lock (contextLock) { - foreach (var action in realmSubscriptionsResetMap.Values) + foreach (var action in notificationsResetMap.Values) action(); foreach (var action in customSubscriptionsResetMap) From e3083c2477ae80b23f5c17c9360c7aa51b07dc88 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Jan 2022 18:05:30 +0900 Subject: [PATCH 608/996] Fix copy pasted xmldoc --- osu.Game/Database/RealmContextFactory.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 738d0a70a9..3956738147 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -264,14 +264,12 @@ namespace osu.Game.Database /// /// The to observe for changes. /// Type of the elements in the list. - /// /// The callback to be invoked with the updated . /// /// A subscription token. It must be kept alive for as long as you want to receive change notifications. /// To stop receiving notifications, call . - /// - /// May be null in the case the provided collection is not managed. /// + /// public IDisposable RegisterForNotifications(Func> query, NotificationCallbackDelegate callback) where T : RealmObjectBase { From b0919722ac1c77fb484a7175e3f90cf325d03f9e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Jan 2022 18:24:25 +0900 Subject: [PATCH 609/996] Guard against potential exception while blocking realm --- .../Sections/DebugSettings/MemorySettings.cs | 48 +++++++++++-------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs b/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs index f058c274e4..c5854981e6 100644 --- a/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs +++ b/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs @@ -1,11 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Threading; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Localisation; +using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Database; using osu.Game.Localisation; @@ -52,30 +54,38 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings blockAction.Action = () => { - var blocking = realmFactory.BlockAllOperations(); - blockAction.Enabled.Value = false; - - // As a safety measure, unblock after 10 seconds. - // This is to handle the case where a dev may block, but then something on the update thread - // accesses realm and blocks for eternity. - Task.Factory.StartNew(() => + try { - Thread.Sleep(10000); - unblock(); - }); + var token = realmFactory.BlockAllOperations(); - unblockAction.Action = unblock; + blockAction.Enabled.Value = false; - void unblock() - { - blocking?.Dispose(); - blocking = null; - - Scheduler.Add(() => + // As a safety measure, unblock after 10 seconds. + // This is to handle the case where a dev may block, but then something on the update thread + // accesses realm and blocks for eternity. + Task.Factory.StartNew(() => { - blockAction.Enabled.Value = true; - unblockAction.Action = null; + Thread.Sleep(10000); + unblock(); }); + + unblockAction.Action = unblock; + + void unblock() + { + token?.Dispose(); + token = null; + + Scheduler.Add(() => + { + blockAction.Enabled.Value = true; + unblockAction.Action = null; + }); + } + } + catch (Exception e) + { + Logger.Error(e, "Blocking realm failed"); } }; } From 9afa034296e8f06642c907d44ab3061c076c9815 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Jan 2022 18:36:16 +0900 Subject: [PATCH 610/996] Fix attempt to revive update thread realm context from non-update thread --- osu.Game/Database/RealmContextFactory.cs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 3956738147..778092c543 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -594,7 +594,7 @@ namespace osu.Game.Database if (isDisposed) throw new ObjectDisposedException(nameof(RealmContextFactory)); - SynchronizationContext syncContext; + SynchronizationContext? syncContext = null; try { @@ -602,10 +602,20 @@ namespace osu.Game.Database lock (contextLock) { - if (!ThreadSafety.IsUpdateThread && context != null) - throw new InvalidOperationException(@$"{nameof(BlockAllOperations)} must be called from the update thread."); + if (context == null) + { + // null context means the update thread has not yet retrieved its context. + // we don't need to worry about reviving the update context in this case, so don't bother with the SynchronizationContext. + Debug.Assert(!ThreadSafety.IsUpdateThread); + } + else + { + if (!ThreadSafety.IsUpdateThread) + throw new InvalidOperationException(@$"{nameof(BlockAllOperations)} must be called from the update thread."); + + syncContext = SynchronizationContext.Current; + } - syncContext = SynchronizationContext.Current; unregisterAllSubscriptions(); Logger.Log(@"Blocking realm operations.", LoggingTarget.Database); From 66c5d77d6388686c733eb229e822d0ad4120dee1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Jan 2022 18:55:15 +0900 Subject: [PATCH 611/996] Allow realm migration to run again if interrupted halfway --- osu.Game/Database/EFToRealmMigrator.cs | 258 ++++++++++++------------- osu.Game/Screens/Loader.cs | 15 +- 2 files changed, 140 insertions(+), 133 deletions(-) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index bbbdac352e..edce99e302 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -27,7 +27,9 @@ namespace osu.Game.Database { internal class EFToRealmMigrator : CompositeDrawable { - public bool FinishedMigrating { get; private set; } + public Task MigrationCompleted => migrationCompleted.Task; + + private readonly TaskCompletionSource migrationCompleted = new TaskCompletionSource(); [Resolved] private DatabaseContextFactory efContextFactory { get; set; } = null!; @@ -99,6 +101,17 @@ namespace osu.Game.Database { using (var ef = efContextFactory.Get()) { + realmContextFactory.Write(realm => + { + // Before beginning, ensure realm is in an empty state. + // Migrations which are half-completed could lead to issues if the user tries a second time. + // Note that we only do this for beatmaps and scores since the other migrations are yonks old. + realm.RemoveAll(); + realm.RemoveAll(); + realm.RemoveAll(); + realm.RemoveAll(); + }); + migrateSettings(ef); migrateSkins(ef); migrateBeatmaps(ef); @@ -114,7 +127,7 @@ namespace osu.Game.Database Logger.Log("Your development database has been fully migrated to realm. If you switch back to a pre-realm branch and need your previous database, rename the backup file back to \"client.db\".\n\nNote that doing this can potentially leave your file store in a bad state.", level: LogLevel.Important); }, TaskCreationOptions.LongRunning).ContinueWith(t => { - FinishedMigrating = true; + migrationCompleted.SetResult(true); }); } @@ -149,87 +162,78 @@ namespace osu.Game.Database { log($"Found {count} beatmaps in EF"); - // only migrate data if the realm database is empty. - // note that this cannot be written as: `realm.All().All(s => s.Protected)`, because realm does not support `.All()`. - if (realm.All().Any(s => !s.Protected)) - { - log("Skipping migration as realm already has beatmaps loaded"); - } - else - { - var transaction = realm.BeginWrite(); - int written = 0; + var transaction = realm.BeginWrite(); + int written = 0; - try + try + { + foreach (var beatmapSet in existingBeatmapSets) { - foreach (var beatmapSet in existingBeatmapSets) + if (++written % 1000 == 0) { - if (++written % 1000 == 0) - { - transaction.Commit(); - transaction = realm.BeginWrite(); - log($"Migrated {written}/{count} beatmaps..."); - } + transaction.Commit(); + transaction = realm.BeginWrite(); + log($"Migrated {written}/{count} beatmaps..."); + } - var realmBeatmapSet = new BeatmapSetInfo + var realmBeatmapSet = new BeatmapSetInfo + { + OnlineID = beatmapSet.OnlineID ?? -1, + DateAdded = beatmapSet.DateAdded, + Status = beatmapSet.Status, + DeletePending = beatmapSet.DeletePending, + Hash = beatmapSet.Hash, + Protected = beatmapSet.Protected, + }; + + migrateFiles(beatmapSet, realm, realmBeatmapSet); + + foreach (var beatmap in beatmapSet.Beatmaps) + { + var ruleset = realm.Find(beatmap.RulesetInfo.ShortName); + var metadata = getBestMetadata(beatmap.Metadata, beatmapSet.Metadata); + + var realmBeatmap = new BeatmapInfo(ruleset, new BeatmapDifficulty(beatmap.BaseDifficulty), metadata) { - OnlineID = beatmapSet.OnlineID ?? -1, - DateAdded = beatmapSet.DateAdded, - Status = beatmapSet.Status, - DeletePending = beatmapSet.DeletePending, - Hash = beatmapSet.Hash, - Protected = beatmapSet.Protected, + DifficultyName = beatmap.DifficultyName, + Status = beatmap.Status, + OnlineID = beatmap.OnlineID ?? -1, + Length = beatmap.Length, + BPM = beatmap.BPM, + Hash = beatmap.Hash, + StarRating = beatmap.StarRating, + MD5Hash = beatmap.MD5Hash, + Hidden = beatmap.Hidden, + AudioLeadIn = beatmap.AudioLeadIn, + StackLeniency = beatmap.StackLeniency, + SpecialStyle = beatmap.SpecialStyle, + LetterboxInBreaks = beatmap.LetterboxInBreaks, + WidescreenStoryboard = beatmap.WidescreenStoryboard, + EpilepsyWarning = beatmap.EpilepsyWarning, + SamplesMatchPlaybackRate = beatmap.SamplesMatchPlaybackRate, + DistanceSpacing = beatmap.DistanceSpacing, + BeatDivisor = beatmap.BeatDivisor, + GridSize = beatmap.GridSize, + TimelineZoom = beatmap.TimelineZoom, + Countdown = beatmap.Countdown, + CountdownOffset = beatmap.CountdownOffset, + MaxCombo = beatmap.MaxCombo, + Bookmarks = beatmap.Bookmarks, + BeatmapSet = realmBeatmapSet, }; - migrateFiles(beatmapSet, realm, realmBeatmapSet); - - foreach (var beatmap in beatmapSet.Beatmaps) - { - var ruleset = realm.Find(beatmap.RulesetInfo.ShortName); - var metadata = getBestMetadata(beatmap.Metadata, beatmapSet.Metadata); - - var realmBeatmap = new BeatmapInfo(ruleset, new BeatmapDifficulty(beatmap.BaseDifficulty), metadata) - { - DifficultyName = beatmap.DifficultyName, - Status = beatmap.Status, - OnlineID = beatmap.OnlineID ?? -1, - Length = beatmap.Length, - BPM = beatmap.BPM, - Hash = beatmap.Hash, - StarRating = beatmap.StarRating, - MD5Hash = beatmap.MD5Hash, - Hidden = beatmap.Hidden, - AudioLeadIn = beatmap.AudioLeadIn, - StackLeniency = beatmap.StackLeniency, - SpecialStyle = beatmap.SpecialStyle, - LetterboxInBreaks = beatmap.LetterboxInBreaks, - WidescreenStoryboard = beatmap.WidescreenStoryboard, - EpilepsyWarning = beatmap.EpilepsyWarning, - SamplesMatchPlaybackRate = beatmap.SamplesMatchPlaybackRate, - DistanceSpacing = beatmap.DistanceSpacing, - BeatDivisor = beatmap.BeatDivisor, - GridSize = beatmap.GridSize, - TimelineZoom = beatmap.TimelineZoom, - Countdown = beatmap.Countdown, - CountdownOffset = beatmap.CountdownOffset, - MaxCombo = beatmap.MaxCombo, - Bookmarks = beatmap.Bookmarks, - BeatmapSet = realmBeatmapSet, - }; - - realmBeatmapSet.Beatmaps.Add(realmBeatmap); - } - - realm.Add(realmBeatmapSet); + realmBeatmapSet.Beatmaps.Add(realmBeatmap); } - } - finally - { - transaction.Commit(); - } - log($"Successfully migrated {count} beatmaps to realm"); + realm.Add(realmBeatmapSet); + } } + finally + { + transaction.Commit(); + } + + log($"Successfully migrated {count} beatmaps to realm"); }); } @@ -280,70 +284,62 @@ namespace osu.Game.Database { log($"Found {count} scores in EF"); - // only migrate data if the realm database is empty. - if (realm.All().Any()) - { - log("Skipping migration as realm already has scores loaded"); - } - else - { - var transaction = realm.BeginWrite(); - int written = 0; + var transaction = realm.BeginWrite(); + int written = 0; - try + try + { + foreach (var score in existingScores) { - foreach (var score in existingScores) + if (++written % 1000 == 0) { - if (++written % 1000 == 0) - { - transaction.Commit(); - transaction = realm.BeginWrite(); - log($"Migrated {written}/{count} scores..."); - } - - var beatmap = realm.All().First(b => b.Hash == score.BeatmapInfo.Hash); - var ruleset = realm.Find(score.Ruleset.ShortName); - var user = new RealmUser - { - OnlineID = score.User.OnlineID, - Username = score.User.Username - }; - - var realmScore = new ScoreInfo(beatmap, ruleset, user) - { - Hash = score.Hash, - DeletePending = score.DeletePending, - OnlineID = score.OnlineID ?? -1, - ModsJson = score.ModsJson, - StatisticsJson = score.StatisticsJson, - TotalScore = score.TotalScore, - MaxCombo = score.MaxCombo, - Accuracy = score.Accuracy, - HasReplay = ((IScoreInfo)score).HasReplay, - Date = score.Date, - PP = score.PP, - Rank = score.Rank, - HitEvents = score.HitEvents, - Passed = score.Passed, - Combo = score.Combo, - Position = score.Position, - Statistics = score.Statistics, - Mods = score.Mods, - APIMods = score.APIMods, - }; - - migrateFiles(score, realm, realmScore); - - realm.Add(realmScore); + transaction.Commit(); + transaction = realm.BeginWrite(); + log($"Migrated {written}/{count} scores..."); } - } - finally - { - transaction.Commit(); - } - log($"Successfully migrated {count} scores to realm"); + var beatmap = realm.All().First(b => b.Hash == score.BeatmapInfo.Hash); + var ruleset = realm.Find(score.Ruleset.ShortName); + var user = new RealmUser + { + OnlineID = score.User.OnlineID, + Username = score.User.Username + }; + + var realmScore = new ScoreInfo(beatmap, ruleset, user) + { + Hash = score.Hash, + DeletePending = score.DeletePending, + OnlineID = score.OnlineID ?? -1, + ModsJson = score.ModsJson, + StatisticsJson = score.StatisticsJson, + TotalScore = score.TotalScore, + MaxCombo = score.MaxCombo, + Accuracy = score.Accuracy, + HasReplay = ((IScoreInfo)score).HasReplay, + Date = score.Date, + PP = score.PP, + Rank = score.Rank, + HitEvents = score.HitEvents, + Passed = score.Passed, + Combo = score.Combo, + Position = score.Position, + Statistics = score.Statistics, + Mods = score.Mods, + APIMods = score.APIMods, + }; + + migrateFiles(score, realm, realmScore); + + realm.Add(realmScore); + } } + finally + { + transaction.Commit(); + } + + log($"Successfully migrated {count} scores to realm"); }); } diff --git a/osu.Game/Screens/Loader.cs b/osu.Game/Screens/Loader.cs index 8c4a13f2bd..a72ba89dfa 100644 --- a/osu.Game/Screens/Loader.cs +++ b/osu.Game/Screens/Loader.cs @@ -74,11 +74,22 @@ namespace osu.Game.Screens base.OnEntering(last); LoadComponentAsync(precompiler = CreateShaderPrecompiler(), AddInternal); - LoadComponentAsync(loadableScreen = CreateLoadableScreen()); // A non-null context factory means there's still content to migrate. if (efContextFactory != null) + { LoadComponentAsync(realmMigrator = new EFToRealmMigrator(), AddInternal); + realmMigrator.MigrationCompleted.ContinueWith(_ => Schedule(() => + { + // Delay initial screen loading to ensure that the migration is in a complete and sane state + // before the intro screen may import the game intro beatmap. + LoadComponentAsync(loadableScreen = CreateLoadableScreen()); + })); + } + else + { + LoadComponentAsync(loadableScreen = CreateLoadableScreen()); + } LoadComponentAsync(spinner = new LoadingSpinner(true, true) { @@ -96,7 +107,7 @@ namespace osu.Game.Screens private void checkIfLoaded() { - if (loadableScreen.LoadState != LoadState.Ready || !precompiler.FinishedCompiling || realmMigrator?.FinishedMigrating == false) + if (loadableScreen?.LoadState != LoadState.Ready || !precompiler.FinishedCompiling) { Schedule(checkIfLoaded); return; From 948867898cb36ed2a32ff2e58a6f7c9de64d66d5 Mon Sep 17 00:00:00 2001 From: mk-56 Date: Mon, 24 Jan 2022 11:38:52 +0100 Subject: [PATCH 612/996] ModeMultiplier rename --- osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs | 8 ++++---- osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs | 8 ++++---- osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs | 8 ++++---- osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs | 8 ++++---- osu.Game/Rulesets/Mods/ModFlashlight.cs | 14 ++++++-------- 5 files changed, 22 insertions(+), 24 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs index 4fbbf63abf..d48382a9ee 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs @@ -33,9 +33,9 @@ namespace osu.Game.Rulesets.Catch.Mods Precision = 0.1f }; - protected override float ModeMultiplier => 350; + protected virtual float DefaultFlashlightSize => 350; - public override Flashlight CreateFlashlight() => new CatchFlashlight(playfield, ChangeRadius.Value, InitialRadius.Value, ModeMultiplier); + public override Flashlight CreateFlashlight() => new CatchFlashlight(playfield, ChangeRadius.Value, InitialRadius.Value, DefaultFlashlightSize); private CatchPlayfield playfield; @@ -49,8 +49,8 @@ namespace osu.Game.Rulesets.Catch.Mods { private readonly CatchPlayfield playfield; - public CatchFlashlight(CatchPlayfield playfield, bool isRadiusBasedOnCombo, float initialRadius, float modeMultiplier) - : base(isRadiusBasedOnCombo, initialRadius, modeMultiplier) + public CatchFlashlight(CatchPlayfield playfield, bool isRadiusBasedOnCombo, float initialRadius, float defaultFlashlightSize) + : base(isRadiusBasedOnCombo, initialRadius, defaultFlashlightSize) { this.playfield = playfield; FlashlightSize = new Vector2(0, GetRadiusFor(0)); diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs index 61f73a1ee5..eb3f60edce 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs @@ -34,16 +34,16 @@ namespace osu.Game.Rulesets.Mania.Mods Precision = 0.1f }; - protected override float ModeMultiplier => 50; + protected virtual float DefaultFlashlightSize => 50; - public override Flashlight CreateFlashlight() => new ManiaFlashlight(ChangeRadius.Value, InitialRadius.Value, ModeMultiplier); + public override Flashlight CreateFlashlight() => new ManiaFlashlight(ChangeRadius.Value, InitialRadius.Value, DefaultFlashlightSize); private class ManiaFlashlight : Flashlight { private readonly LayoutValue flashlightProperties = new LayoutValue(Invalidation.DrawSize); - public ManiaFlashlight(bool isRadiusBasedOnCombo, float initialRadius, float modeMultiplier) - : base(isRadiusBasedOnCombo, initialRadius, modeMultiplier) + public ManiaFlashlight(bool isRadiusBasedOnCombo, float initialRadius, float defaultFlashlightSize) + : base(isRadiusBasedOnCombo, initialRadius, defaultFlashlightSize) { FlashlightSize = new Vector2(DrawWidth, GetRadiusFor(0)); diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs index 75299863a9..6a9d199c54 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs @@ -47,11 +47,11 @@ namespace osu.Game.Rulesets.Osu.Mods Precision = 0.1f }; - protected override float ModeMultiplier => 180; + protected virtual float DefaultFlashlightSize => 180; private OsuFlashlight flashlight; - public override Flashlight CreateFlashlight() => flashlight = new OsuFlashlight(ChangeRadius.Value, InitialRadius.Value, FollowDelay.Value, ModeMultiplier); + public override Flashlight CreateFlashlight() => flashlight = new OsuFlashlight(ChangeRadius.Value, InitialRadius.Value, FollowDelay.Value, DefaultFlashlightSize); public void ApplyToDrawableHitObject(DrawableHitObject drawable) { @@ -66,8 +66,8 @@ namespace osu.Game.Rulesets.Osu.Mods //public float InitialRadius { private get; set; } public bool ChangeRadius { private get; set; } - public OsuFlashlight(bool isRadiusBasedOnCombo, float initialRadius, double followDelay, float modeMultiplier) - : base(isRadiusBasedOnCombo, initialRadius, modeMultiplier) + public OsuFlashlight(bool isRadiusBasedOnCombo, float initialRadius, double followDelay, float defaultFlashlightSize) + : base(isRadiusBasedOnCombo, initialRadius, defaultFlashlightSize) { FollowDelay = followDelay; diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs index 65a173b491..8de7c859c4 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs @@ -34,9 +34,9 @@ namespace osu.Game.Rulesets.Taiko.Mods Precision = 0.1f }; - protected override float ModeMultiplier => 250; + protected virtual float DefaultFlashlightSize => 250; - public override Flashlight CreateFlashlight() => new TaikoFlashlight(playfield, ChangeRadius.Value, InitialRadius.Value, ModeMultiplier); + public override Flashlight CreateFlashlight() => new TaikoFlashlight(playfield, ChangeRadius.Value, InitialRadius.Value, DefaultFlashlightSize); private TaikoPlayfield playfield; @@ -51,8 +51,8 @@ namespace osu.Game.Rulesets.Taiko.Mods private readonly LayoutValue flashlightProperties = new LayoutValue(Invalidation.DrawSize); private readonly TaikoPlayfield taikoPlayfield; - public TaikoFlashlight(TaikoPlayfield taikoPlayfield, bool isRadiusBasedOnCombo, float initialRadius, float modeMultiplier) - : base(isRadiusBasedOnCombo, initialRadius, modeMultiplier) + public TaikoFlashlight(TaikoPlayfield taikoPlayfield, bool isRadiusBasedOnCombo, float initialRadius, float defaultFlashlightSize) + : base(isRadiusBasedOnCombo, initialRadius, defaultFlashlightSize) { this.taikoPlayfield = taikoPlayfield; FlashlightSize = getSizeFor(0); diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index 7b980e8097..531ee92b7a 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -38,8 +38,6 @@ namespace osu.Game.Rulesets.Mods [SettingSource("Initial radius", "Initial radius of the flashlight area.")] public abstract BindableNumber InitialRadius { get; } - - protected abstract float ModeMultiplier { get; } } public abstract class ModFlashlight : ModFlashlight, IApplicableToDrawableRuleset, IApplicableToScoreProcessor @@ -102,13 +100,13 @@ namespace osu.Game.Rulesets.Mods public readonly float InitialRadius; - public readonly float ModeMultiplier; + public readonly float DefaultFlashlightSize; - protected Flashlight(bool isRadiusBasedOnCombo, float initialRadius, float modeMultiplier) + protected Flashlight(bool isRadiusBasedOnCombo, float initialRadius, float defaultFlashlightSize) { IsRadiusBasedOnCombo = isRadiusBasedOnCombo; InitialRadius = initialRadius; - ModeMultiplier = modeMultiplier; + DefaultFlashlightSize = defaultFlashlightSize; } [BackgroundDependencyLoader] @@ -147,12 +145,12 @@ namespace osu.Game.Rulesets.Mods if (IsRadiusBasedOnCombo) { if (combo > 200) - return InitialRadius * 0.8f * ModeMultiplier; + return InitialRadius * 0.8f * DefaultFlashlightSize; else if (combo > 100) - return InitialRadius * 0.9f * ModeMultiplier; + return InitialRadius * 0.9f * DefaultFlashlightSize; } - return InitialRadius * ModeMultiplier; + return InitialRadius * DefaultFlashlightSize; } private Vector2 flashlightPosition; From deaff340d2733c544b8e87d78a51d5d69063ee27 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Jan 2022 17:58:06 +0900 Subject: [PATCH 613/996] Add test coverage of saving velocity --- .../Editor/TestSceneSliderVelocityAdjust.cs | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderVelocityAdjust.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderVelocityAdjust.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderVelocityAdjust.cs new file mode 100644 index 0000000000..4750c97566 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderVelocityAdjust.cs @@ -0,0 +1,98 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Diagnostics; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Input; +using osu.Framework.Testing; +using osu.Framework.Utils; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.UI; +using osu.Game.Screens.Edit; +using osu.Game.Screens.Edit.Compose.Components.Timeline; +using osu.Game.Screens.Edit.Timing; +using osu.Game.Tests.Beatmaps; +using osu.Game.Tests.Visual; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Rulesets.Osu.Tests.Editor +{ + public class TestSceneSliderVelocityAdjust : OsuGameTestScene + { + private Screens.Edit.Editor editor => Game.ScreenStack.CurrentScreen as Screens.Edit.Editor; + + private EditorBeatmap editorBeatmap => editor.ChildrenOfType().FirstOrDefault(); + + private EditorClock editorClock => editor.ChildrenOfType().FirstOrDefault(); + + private Slider slider => editorBeatmap.HitObjects.OfType().FirstOrDefault(); + + private TimelineHitObjectBlueprint blueprint => editor.ChildrenOfType().FirstOrDefault(); + + private DifficultyPointPiece difficultyPointPiece => blueprint.ChildrenOfType().First(); + + private IndeterminateSliderWithTextBoxInput velocityTextBox => Game.ChildrenOfType().First().ChildrenOfType>().First(); + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false); + + private bool editorComponentsReady => editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true + && editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true + && editor?.ChildrenOfType().FirstOrDefault()?.IsLoaded == true; + + [TestCase(true)] + [TestCase(false)] + public void TestVelocityChangeSavesCorrectly(bool adjustVelocity) + { + double? velocity = null; + + AddStep("enter editor", () => Game.ScreenStack.Push(new EditorLoader())); + AddUntilStep("wait for editor load", () => editorComponentsReady); + + AddStep("seek to first control point", () => editorClock.Seek(editorBeatmap.ControlPointInfo.TimingPoints.First().Time)); + AddStep("enter slider placement mode", () => InputManager.Key(Key.Number3)); + + AddStep("move mouse to centre", () => InputManager.MoveMouseTo(editor.ChildrenOfType().First().ScreenSpaceDrawQuad.Centre)); + AddStep("start placement", () => InputManager.Click(MouseButton.Left)); + + AddStep("move mouse to bottom right", () => InputManager.MoveMouseTo(editor.ChildrenOfType().First().ScreenSpaceDrawQuad.BottomRight - new Vector2(10))); + AddStep("end placement", () => InputManager.Click(MouseButton.Right)); + + AddStep("exit placement mode", () => InputManager.Key(Key.Number1)); + + AddAssert("slider placed", () => slider != null); + + AddStep("select slider", () => editorBeatmap.SelectedHitObjects.Add(slider)); + + AddAssert("ensure one slider placed", () => slider != null); + + AddStep("store velocity", () => velocity = slider.Velocity); + + if (adjustVelocity) + { + AddStep("open velocity adjust panel", () => difficultyPointPiece.TriggerClick()); + AddStep("change velocity", () => velocityTextBox.Current.Value = 2); + + AddAssert("velocity adjusted", () => + { + Debug.Assert(velocity != null); + return Precision.AlmostEquals(velocity.Value * 2, slider.Velocity); + }); + + AddStep("store velocity", () => velocity = slider.Velocity); + } + + AddStep("save", () => InputManager.Keys(PlatformAction.Save)); + AddStep("exit", () => InputManager.Key(Key.Escape)); + + AddStep("enter editor (again)", () => Game.ScreenStack.Push(new EditorLoader())); + AddUntilStep("wait for editor load", () => editorComponentsReady); + + AddStep("seek to slider", () => editorClock.Seek(slider.StartTime)); + AddAssert("slider has correct velocity", () => slider.Velocity == velocity); + } + } +} From c3758047fd9e2113173ea9bfcba50a575f5b7035 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Jan 2022 19:54:07 +0900 Subject: [PATCH 614/996] Don't include nested hit objects' `DifficultyControLPoints` in legacy encoder logic The editor doesn't currently propagate velocity to nested objects. We're not yet sure whether it should or not. For now, let's just ignore nested objects' `DifficultyControlPoints` for simplicity. Note that this only affects osu! ruleset due to the pre-check on `isOsuRuleset`. --- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 4cf6d3335f..9d848fd8a4 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -242,12 +242,7 @@ namespace osu.Game.Beatmaps.Formats yield break; foreach (var hitObject in hitObjects) - { yield return hitObject.DifficultyControlPoint; - - foreach (var nested in collectDifficultyControlPoints(hitObject.NestedHitObjects)) - yield return nested; - } } void extractDifficultyControlPoints(IEnumerable hitObjects) From 6eb2c28e41369be24fbefb14478b62935207992d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Jan 2022 19:59:58 +0900 Subject: [PATCH 615/996] Rename `RealmContextFactory` to `RealmAccess` --- osu.Game.Benchmarks/BenchmarkRealmReads.cs | 20 +- .../Beatmaps/IO/BeatmapImportHelper.cs | 4 +- .../Database/BeatmapImporterTests.cs | 258 +++++++++--------- osu.Game.Tests/Database/FileStoreTests.cs | 24 +- osu.Game.Tests/Database/GeneralUsageTests.cs | 22 +- osu.Game.Tests/Database/RealmLiveTests.cs | 60 ++-- .../RealmSubscriptionRegistrationTests.cs | 42 +-- osu.Game.Tests/Database/RealmTest.cs | 34 +-- osu.Game.Tests/Database/RulesetStoreTests.cs | 18 +- .../Database/TestRealmKeyBindingStore.cs | 16 +- .../Gameplay/TestSceneStoryboardSamples.cs | 2 +- ...eneOnlinePlayBeatmapAvailabilityTracker.cs | 20 +- .../Background/TestSceneUserDimBackgrounds.cs | 6 +- .../TestSceneManageCollectionsDialog.cs | 6 +- .../Visual/Multiplayer/QueueModeTestScene.cs | 6 +- .../TestSceneDrawableRoomPlaylist.cs | 6 +- .../Multiplayer/TestSceneMultiplayer.cs | 6 +- .../TestSceneMultiplayerMatchSongSelect.cs | 6 +- .../TestSceneMultiplayerMatchSubScreen.cs | 6 +- .../TestSceneMultiplayerPlaylist.cs | 6 +- .../TestSceneMultiplayerQueueList.cs | 6 +- .../TestSceneMultiplayerReadyButton.cs | 6 +- .../TestSceneMultiplayerSpectateButton.cs | 6 +- .../TestScenePlaylistsSongSelect.cs | 6 +- .../Visual/Multiplayer/TestSceneTeamVersus.cs | 6 +- .../TestSceneChangeAndUseGameplayBindings.cs | 2 +- .../TestScenePlaylistsRoomCreation.cs | 6 +- .../Visual/Ranking/TestSceneResultsScreen.cs | 4 +- .../SongSelect/TestSceneBeatmapLeaderboard.cs | 8 +- .../SongSelect/TestSceneFilterControl.cs | 6 +- .../SongSelect/TestScenePlaySongSelect.cs | 6 +- .../SongSelect/TestSceneTopLocalRank.cs | 8 +- .../TestSceneDeleteLocalScore.cs | 12 +- osu.Game/Beatmaps/BeatmapManager.cs | 30 +- osu.Game/Beatmaps/BeatmapModelManager.cs | 8 +- osu.Game/Beatmaps/WorkingBeatmapCache.cs | 2 +- osu.Game/Configuration/SettingsStore.cs | 6 +- osu.Game/Database/EFToRealmMigrator.cs | 12 +- ...{RealmContextFactory.cs => RealmAccess.cs} | 10 +- osu.Game/Database/RealmLive.cs | 14 +- osu.Game/Database/RealmObjectExtensions.cs | 12 +- osu.Game/IO/IStorageResourceProvider.cs | 2 +- .../Bindings/DatabasedKeyBindingContainer.cs | 8 +- osu.Game/Input/RealmKeyBindingStore.cs | 10 +- osu.Game/Online/BeatmapDownloadTracker.cs | 4 +- .../OnlinePlayBeatmapAvailabilityTracker.cs | 10 +- osu.Game/Online/ScoreDownloadTracker.cs | 4 +- osu.Game/OsuGameBase.cs | 22 +- osu.Game/Overlays/MusicController.cs | 10 +- .../Sections/DebugSettings/MemorySettings.cs | 6 +- .../Settings/Sections/Input/KeyBindingRow.cs | 4 +- .../Sections/Input/KeyBindingsSubsection.cs | 8 +- .../Overlays/Settings/Sections/SkinSection.cs | 14 +- osu.Game/Overlays/Toolbar/ToolbarButton.cs | 4 +- .../Configuration/RulesetConfigManager.cs | 12 +- osu.Game/Rulesets/RulesetConfigCache.cs | 8 +- osu.Game/Rulesets/RulesetStore.cs | 8 +- osu.Game/Scoring/ScoreManager.cs | 12 +- osu.Game/Scoring/ScoreModelManager.cs | 6 +- osu.Game/Screens/Menu/IntroScreen.cs | 6 +- osu.Game/Screens/Select/BeatmapCarousel.cs | 14 +- .../Screens/Select/Carousel/TopLocalRank.cs | 4 +- .../Select/Leaderboards/BeatmapLeaderboard.cs | 6 +- osu.Game/Screens/Spectate/SpectatorScreen.cs | 4 +- osu.Game/Skinning/Skin.cs | 4 +- osu.Game/Skinning/SkinManager.cs | 22 +- osu.Game/Skinning/SkinModelManager.cs | 6 +- osu.Game/Stores/BeatmapImporter.cs | 6 +- osu.Game/Stores/RealmArchiveModelImporter.cs | 16 +- osu.Game/Stores/RealmArchiveModelManager.cs | 12 +- osu.Game/Stores/RealmFileStore.cs | 8 +- .../Drawables/DrawableStoryboard.cs | 4 +- .../Tests/Beatmaps/HitObjectSampleTest.cs | 2 +- osu.Game/Tests/Visual/EditorTestScene.cs | 14 +- osu.Game/Tests/Visual/OsuTestScene.cs | 6 +- osu.Game/Tests/Visual/SkinnableTestScene.cs | 2 +- 76 files changed, 516 insertions(+), 516 deletions(-) rename osu.Game/Database/{RealmContextFactory.cs => RealmAccess.cs} (98%) diff --git a/osu.Game.Benchmarks/BenchmarkRealmReads.cs b/osu.Game.Benchmarks/BenchmarkRealmReads.cs index bb22fab51c..412f86bd1c 100644 --- a/osu.Game.Benchmarks/BenchmarkRealmReads.cs +++ b/osu.Game.Benchmarks/BenchmarkRealmReads.cs @@ -16,7 +16,7 @@ namespace osu.Game.Benchmarks public class BenchmarkRealmReads : BenchmarkTest { private TemporaryNativeStorage storage; - private RealmContextFactory realmFactory; + private RealmAccess realm; private UpdateThread updateThread; [Params(1, 100, 1000)] @@ -27,9 +27,9 @@ namespace osu.Game.Benchmarks storage = new TemporaryNativeStorage("realm-benchmark"); storage.DeleteDirectory(string.Empty); - realmFactory = new RealmContextFactory(storage, "client"); + realm = new RealmAccess(storage, "client"); - realmFactory.Run(realm => + realm.Run(realm => { realm.Write(c => c.Add(TestResources.CreateTestBeatmapSetInfo(rulesets: new[] { new OsuRuleset().RulesetInfo }))); }); @@ -41,7 +41,7 @@ namespace osu.Game.Benchmarks [Benchmark] public void BenchmarkDirectPropertyRead() { - realmFactory.Run(realm => + realm.Run(realm => { var beatmapSet = realm.All().First(); @@ -61,7 +61,7 @@ namespace osu.Game.Benchmarks { try { - var beatmapSet = realmFactory.Context.All().First(); + var beatmapSet = realm.Realm.All().First(); for (int i = 0; i < ReadsPerFetch; i++) { @@ -80,9 +80,9 @@ namespace osu.Game.Benchmarks [Benchmark] public void BenchmarkRealmLivePropertyRead() { - realmFactory.Run(realm => + realm.Run(r => { - var beatmapSet = realm.All().First().ToLive(realmFactory); + var beatmapSet = r.All().First().ToLive(realm); for (int i = 0; i < ReadsPerFetch; i++) { @@ -100,7 +100,7 @@ namespace osu.Game.Benchmarks { try { - var beatmapSet = realmFactory.Context.All().First().ToLive(realmFactory); + var beatmapSet = realm.Realm.All().First().ToLive(realm); for (int i = 0; i < ReadsPerFetch; i++) { @@ -119,7 +119,7 @@ namespace osu.Game.Benchmarks [Benchmark] public void BenchmarkDetachedPropertyRead() { - realmFactory.Run(realm => + realm.Run(realm => { var beatmapSet = realm.All().First().Detach(); @@ -133,7 +133,7 @@ namespace osu.Game.Benchmarks [GlobalCleanup] public void Cleanup() { - realmFactory?.Dispose(); + realm?.Dispose(); storage?.Dispose(); updateThread?.Exit(); } diff --git a/osu.Game.Tests/Beatmaps/IO/BeatmapImportHelper.cs b/osu.Game.Tests/Beatmaps/IO/BeatmapImportHelper.cs index 7aa2dc7093..9e440c6bce 100644 --- a/osu.Game.Tests/Beatmaps/IO/BeatmapImportHelper.cs +++ b/osu.Game.Tests/Beatmaps/IO/BeatmapImportHelper.cs @@ -53,9 +53,9 @@ namespace osu.Game.Tests.Beatmaps.IO private static void ensureLoaded(OsuGameBase osu, int timeout = 60000) { - var realmContextFactory = osu.Dependencies.Get(); + var realm = osu.Dependencies.Get(); - realmContextFactory.Run(realm => BeatmapImporterTests.EnsureLoaded(realm, timeout)); + realm.Run(r => BeatmapImporterTests.EnsureLoaded(r, timeout)); // TODO: add back some extra checks outside of the realm ones? // var set = queryBeatmapSets().First(); diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index 227314cffd..a52b21244c 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -38,10 +38,10 @@ namespace osu.Game.Tests.Database [Test] public void TestDetachBeatmapSet() { - RunTestWithRealmAsync(async (realmFactory, storage) => + RunTestWithRealmAsync(async (realm, storage) => { - using (var importer = new BeatmapModelManager(realmFactory, storage)) - using (new RulesetStore(realmFactory, storage)) + using (var importer = new BeatmapModelManager(realm, storage)) + using (new RulesetStore(realm, storage)) { ILive? beatmapSet; @@ -82,10 +82,10 @@ namespace osu.Game.Tests.Database [Test] public void TestUpdateDetachedBeatmapSet() { - RunTestWithRealmAsync(async (realmFactory, storage) => + RunTestWithRealmAsync(async (realm, storage) => { - using (var importer = new BeatmapModelManager(realmFactory, storage)) - using (new RulesetStore(realmFactory, storage)) + using (var importer = new BeatmapModelManager(realm, storage)) + using (new RulesetStore(realm, storage)) { ILive? beatmapSet; @@ -139,53 +139,53 @@ namespace osu.Game.Tests.Database [Test] public void TestImportBeatmapThenCleanup() { - RunTestWithRealmAsync(async (realmFactory, storage) => + RunTestWithRealmAsync(async (realm, storage) => { - using (var importer = new BeatmapModelManager(realmFactory, storage)) - using (new RulesetStore(realmFactory, storage)) + using (var importer = new BeatmapModelManager(realm, storage)) + using (new RulesetStore(realm, storage)) { ILive? imported; using (var reader = new ZipArchiveReader(TestResources.GetTestBeatmapStream())) imported = await importer.Import(reader); - Assert.AreEqual(1, realmFactory.Context.All().Count()); + Assert.AreEqual(1, realm.Realm.All().Count()); Assert.NotNull(imported); Debug.Assert(imported != null); imported.PerformWrite(s => s.DeletePending = true); - Assert.AreEqual(1, realmFactory.Context.All().Count(s => s.DeletePending)); + Assert.AreEqual(1, realm.Realm.All().Count(s => s.DeletePending)); } }); Logger.Log("Running with no work to purge pending deletions"); - RunTestWithRealm((realmFactory, _) => { Assert.AreEqual(0, realmFactory.Context.All().Count()); }); + RunTestWithRealm((realm, _) => { Assert.AreEqual(0, realm.Realm.All().Count()); }); } [Test] public void TestImportWhenClosed() { - RunTestWithRealmAsync(async (realmFactory, storage) => + RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapModelManager(realmFactory, storage); - using var store = new RulesetStore(realmFactory, storage); + using var importer = new BeatmapModelManager(realm, storage); + using var store = new RulesetStore(realm, storage); - await LoadOszIntoStore(importer, realmFactory.Context); + await LoadOszIntoStore(importer, realm.Realm); }); } [Test] public void TestAccessFileAfterImport() { - RunTestWithRealmAsync(async (realmFactory, storage) => + RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapModelManager(realmFactory, storage); - using var store = new RulesetStore(realmFactory, storage); + using var importer = new BeatmapModelManager(realm, storage); + using var store = new RulesetStore(realm, storage); - var imported = await LoadOszIntoStore(importer, realmFactory.Context); + var imported = await LoadOszIntoStore(importer, realm.Realm); var beatmap = imported.Beatmaps.First(); var file = beatmap.File; @@ -198,24 +198,24 @@ namespace osu.Game.Tests.Database [Test] public void TestImportThenDelete() { - RunTestWithRealmAsync(async (realmFactory, storage) => + RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapModelManager(realmFactory, storage); - using var store = new RulesetStore(realmFactory, storage); + using var importer = new BeatmapModelManager(realm, storage); + using var store = new RulesetStore(realm, storage); - var imported = await LoadOszIntoStore(importer, realmFactory.Context); + var imported = await LoadOszIntoStore(importer, realm.Realm); - deleteBeatmapSet(imported, realmFactory.Context); + deleteBeatmapSet(imported, realm.Realm); }); } [Test] public void TestImportThenDeleteFromStream() { - RunTestWithRealmAsync(async (realmFactory, storage) => + RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapModelManager(realmFactory, storage); - using var store = new RulesetStore(realmFactory, storage); + using var importer = new BeatmapModelManager(realm, storage); + using var store = new RulesetStore(realm, storage); string? tempPath = TestResources.GetTestBeatmapForImport(); @@ -224,7 +224,7 @@ namespace osu.Game.Tests.Database using (var stream = File.OpenRead(tempPath)) { importedSet = await importer.Import(new ImportTask(stream, Path.GetFileName(tempPath))); - EnsureLoaded(realmFactory.Context); + EnsureLoaded(realm.Realm); } Assert.NotNull(importedSet); @@ -233,39 +233,39 @@ namespace osu.Game.Tests.Database Assert.IsTrue(File.Exists(tempPath), "Stream source file somehow went missing"); File.Delete(tempPath); - var imported = realmFactory.Context.All().First(beatmapSet => beatmapSet.ID == importedSet.ID); + var imported = realm.Realm.All().First(beatmapSet => beatmapSet.ID == importedSet.ID); - deleteBeatmapSet(imported, realmFactory.Context); + deleteBeatmapSet(imported, realm.Realm); }); } [Test] public void TestImportThenImport() { - RunTestWithRealmAsync(async (realmFactory, storage) => + RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapModelManager(realmFactory, storage); - using var store = new RulesetStore(realmFactory, storage); + using var importer = new BeatmapModelManager(realm, storage); + using var store = new RulesetStore(realm, storage); - var imported = await LoadOszIntoStore(importer, realmFactory.Context); - var importedSecondTime = await LoadOszIntoStore(importer, realmFactory.Context); + var imported = await LoadOszIntoStore(importer, realm.Realm); + var importedSecondTime = await LoadOszIntoStore(importer, realm.Realm); // check the newly "imported" beatmap is actually just the restored previous import. since it matches hash. Assert.IsTrue(imported.ID == importedSecondTime.ID); Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID); - checkBeatmapSetCount(realmFactory.Context, 1); - checkSingleReferencedFileCount(realmFactory.Context, 18); + checkBeatmapSetCount(realm.Realm, 1); + checkSingleReferencedFileCount(realm.Realm, 18); }); } [Test] public void TestImportThenImportWithReZip() { - RunTestWithRealmAsync(async (realmFactory, storage) => + RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapModelManager(realmFactory, storage); - using var store = new RulesetStore(realmFactory, storage); + using var importer = new BeatmapModelManager(realm, storage); + using var store = new RulesetStore(realm, storage); string? temp = TestResources.GetTestBeatmapForImport(); @@ -274,7 +274,7 @@ namespace osu.Game.Tests.Database try { - var imported = await LoadOszIntoStore(importer, realmFactory.Context); + var imported = await LoadOszIntoStore(importer, realm.Realm); string hashBefore = hashFile(temp); @@ -292,7 +292,7 @@ namespace osu.Game.Tests.Database var importedSecondTime = await importer.Import(new ImportTask(temp)); - EnsureLoaded(realmFactory.Context); + EnsureLoaded(realm.Realm); Assert.NotNull(importedSecondTime); Debug.Assert(importedSecondTime != null); @@ -311,10 +311,10 @@ namespace osu.Game.Tests.Database [Test] public void TestImportThenImportWithChangedHashedFile() { - RunTestWithRealmAsync(async (realmFactory, storage) => + RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapModelManager(realmFactory, storage); - using var store = new RulesetStore(realmFactory, storage); + using var importer = new BeatmapModelManager(realm, storage); + using var store = new RulesetStore(realm, storage); string? temp = TestResources.GetTestBeatmapForImport(); @@ -323,9 +323,9 @@ namespace osu.Game.Tests.Database try { - var imported = await LoadOszIntoStore(importer, realmFactory.Context); + var imported = await LoadOszIntoStore(importer, realm.Realm); - await createScoreForBeatmap(realmFactory.Context, imported.Beatmaps.First()); + await createScoreForBeatmap(realm.Realm, imported.Beatmaps.First()); using (var zip = ZipArchive.Open(temp)) zip.WriteToDirectory(extractedFolder); @@ -343,7 +343,7 @@ namespace osu.Game.Tests.Database var importedSecondTime = await importer.Import(new ImportTask(temp)); - EnsureLoaded(realmFactory.Context); + EnsureLoaded(realm.Realm); // check the newly "imported" beatmap is not the original. Assert.NotNull(importedSecondTime); @@ -363,10 +363,10 @@ namespace osu.Game.Tests.Database [Ignore("intentionally broken by import optimisations")] public void TestImportThenImportWithChangedFile() { - RunTestWithRealmAsync(async (realmFactory, storage) => + RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapModelManager(realmFactory, storage); - using var store = new RulesetStore(realmFactory, storage); + using var importer = new BeatmapModelManager(realm, storage); + using var store = new RulesetStore(realm, storage); string? temp = TestResources.GetTestBeatmapForImport(); @@ -375,7 +375,7 @@ namespace osu.Game.Tests.Database try { - var imported = await LoadOszIntoStore(importer, realmFactory.Context); + var imported = await LoadOszIntoStore(importer, realm.Realm); using (var zip = ZipArchive.Open(temp)) zip.WriteToDirectory(extractedFolder); @@ -392,7 +392,7 @@ namespace osu.Game.Tests.Database var importedSecondTime = await importer.Import(new ImportTask(temp)); - EnsureLoaded(realmFactory.Context); + EnsureLoaded(realm.Realm); Assert.NotNull(importedSecondTime); Debug.Assert(importedSecondTime != null); @@ -411,10 +411,10 @@ namespace osu.Game.Tests.Database [Test] public void TestImportThenImportWithDifferentFilename() { - RunTestWithRealmAsync(async (realmFactory, storage) => + RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapModelManager(realmFactory, storage); - using var store = new RulesetStore(realmFactory, storage); + using var importer = new BeatmapModelManager(realm, storage); + using var store = new RulesetStore(realm, storage); string? temp = TestResources.GetTestBeatmapForImport(); @@ -423,7 +423,7 @@ namespace osu.Game.Tests.Database try { - var imported = await LoadOszIntoStore(importer, realmFactory.Context); + var imported = await LoadOszIntoStore(importer, realm.Realm); using (var zip = ZipArchive.Open(temp)) zip.WriteToDirectory(extractedFolder); @@ -440,7 +440,7 @@ namespace osu.Game.Tests.Database var importedSecondTime = await importer.Import(new ImportTask(temp)); - EnsureLoaded(realmFactory.Context); + EnsureLoaded(realm.Realm); Assert.NotNull(importedSecondTime); Debug.Assert(importedSecondTime != null); @@ -460,12 +460,12 @@ namespace osu.Game.Tests.Database [Ignore("intentionally broken by import optimisations")] public void TestImportCorruptThenImport() { - RunTestWithRealmAsync(async (realmFactory, storage) => + RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapModelManager(realmFactory, storage); - using var store = new RulesetStore(realmFactory, storage); + using var importer = new BeatmapModelManager(realm, storage); + using var store = new RulesetStore(realm, storage); - var imported = await LoadOszIntoStore(importer, realmFactory.Context); + var imported = await LoadOszIntoStore(importer, realm.Realm); var firstFile = imported.Files.First(); @@ -476,7 +476,7 @@ namespace osu.Game.Tests.Database using (var stream = storage.GetStream(firstFile.File.GetStoragePath(), FileAccess.Write, FileMode.Create)) stream.WriteByte(0); - var importedSecondTime = await LoadOszIntoStore(importer, realmFactory.Context); + var importedSecondTime = await LoadOszIntoStore(importer, realm.Realm); using (var stream = storage.GetStream(firstFile.File.GetStoragePath())) Assert.AreEqual(stream.Length, originalLength, "Corruption was not fixed on second import"); @@ -485,18 +485,18 @@ namespace osu.Game.Tests.Database Assert.IsTrue(imported.ID == importedSecondTime.ID); Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID); - checkBeatmapSetCount(realmFactory.Context, 1); - checkSingleReferencedFileCount(realmFactory.Context, 18); + checkBeatmapSetCount(realm.Realm, 1); + checkSingleReferencedFileCount(realm.Realm, 18); }); } [Test] public void TestModelCreationFailureDoesntReturn() { - RunTestWithRealmAsync(async (realmFactory, storage) => + RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapModelManager(realmFactory, storage); - using var store = new RulesetStore(realmFactory, storage); + using var importer = new BeatmapModelManager(realm, storage); + using var store = new RulesetStore(realm, storage); var progressNotification = new ImportProgressNotification(); @@ -510,8 +510,8 @@ namespace osu.Game.Tests.Database new ImportTask(zipStream, string.Empty) ); - checkBeatmapSetCount(realmFactory.Context, 0); - checkBeatmapCount(realmFactory.Context, 0); + checkBeatmapSetCount(realm.Realm, 0); + checkBeatmapCount(realm.Realm, 0); Assert.IsEmpty(imported); Assert.AreEqual(ProgressNotificationState.Cancelled, progressNotification.State); @@ -521,7 +521,7 @@ namespace osu.Game.Tests.Database [Test] public void TestRollbackOnFailure() { - RunTestWithRealmAsync(async (realmFactory, storage) => + RunTestWithRealmAsync(async (realm, storage) => { int loggedExceptionCount = 0; @@ -531,16 +531,16 @@ namespace osu.Game.Tests.Database Interlocked.Increment(ref loggedExceptionCount); }; - using var importer = new BeatmapModelManager(realmFactory, storage); - using var store = new RulesetStore(realmFactory, storage); + using var importer = new BeatmapModelManager(realm, storage); + using var store = new RulesetStore(realm, storage); - var imported = await LoadOszIntoStore(importer, realmFactory.Context); + var imported = await LoadOszIntoStore(importer, realm.Realm); - realmFactory.Context.Write(() => imported.Hash += "-changed"); + realm.Realm.Write(() => imported.Hash += "-changed"); - checkBeatmapSetCount(realmFactory.Context, 1); - checkBeatmapCount(realmFactory.Context, 12); - checkSingleReferencedFileCount(realmFactory.Context, 18); + checkBeatmapSetCount(realm.Realm, 1); + checkBeatmapCount(realm.Realm, 12); + checkSingleReferencedFileCount(realm.Realm, 18); string? brokenTempFilename = TestResources.GetTestBeatmapForImport(); @@ -565,10 +565,10 @@ namespace osu.Game.Tests.Database { } - checkBeatmapSetCount(realmFactory.Context, 1); - checkBeatmapCount(realmFactory.Context, 12); + checkBeatmapSetCount(realm.Realm, 1); + checkBeatmapCount(realm.Realm, 12); - checkSingleReferencedFileCount(realmFactory.Context, 18); + checkSingleReferencedFileCount(realm.Realm, 18); Assert.AreEqual(1, loggedExceptionCount); @@ -579,18 +579,18 @@ namespace osu.Game.Tests.Database [Test] public void TestImportThenDeleteThenImportOptimisedPath() { - RunTestWithRealmAsync(async (realmFactory, storage) => + RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapModelManager(realmFactory, storage); - using var store = new RulesetStore(realmFactory, storage); + using var importer = new BeatmapModelManager(realm, storage); + using var store = new RulesetStore(realm, storage); - var imported = await LoadOszIntoStore(importer, realmFactory.Context); + var imported = await LoadOszIntoStore(importer, realm.Realm); - deleteBeatmapSet(imported, realmFactory.Context); + deleteBeatmapSet(imported, realm.Realm); Assert.IsTrue(imported.DeletePending); - var importedSecondTime = await LoadOszIntoStore(importer, realmFactory.Context); + var importedSecondTime = await LoadOszIntoStore(importer, realm.Realm); // check the newly "imported" beatmap is actually just the restored previous import. since it matches hash. Assert.IsTrue(imported.ID == importedSecondTime.ID); @@ -603,18 +603,18 @@ namespace osu.Game.Tests.Database [Test] public void TestImportThenDeleteThenImportNonOptimisedPath() { - RunTestWithRealmAsync(async (realmFactory, storage) => + RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new NonOptimisedBeatmapImporter(realmFactory, storage); - using var store = new RulesetStore(realmFactory, storage); + using var importer = new NonOptimisedBeatmapImporter(realm, storage); + using var store = new RulesetStore(realm, storage); - var imported = await LoadOszIntoStore(importer, realmFactory.Context); + var imported = await LoadOszIntoStore(importer, realm.Realm); - deleteBeatmapSet(imported, realmFactory.Context); + deleteBeatmapSet(imported, realm.Realm); Assert.IsTrue(imported.DeletePending); - var importedSecondTime = await LoadOszIntoStore(importer, realmFactory.Context); + var importedSecondTime = await LoadOszIntoStore(importer, realm.Realm); // check the newly "imported" beatmap is actually just the restored previous import. since it matches hash. Assert.IsTrue(imported.ID == importedSecondTime.ID); @@ -627,22 +627,22 @@ namespace osu.Game.Tests.Database [Test] public void TestImportThenDeleteThenImportWithOnlineIDsMissing() { - RunTestWithRealmAsync(async (realmFactory, storage) => + RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapModelManager(realmFactory, storage); - using var store = new RulesetStore(realmFactory, storage); + using var importer = new BeatmapModelManager(realm, storage); + using var store = new RulesetStore(realm, storage); - var imported = await LoadOszIntoStore(importer, realmFactory.Context); + var imported = await LoadOszIntoStore(importer, realm.Realm); - realmFactory.Context.Write(() => + realm.Realm.Write(() => { foreach (var b in imported.Beatmaps) b.OnlineID = -1; }); - deleteBeatmapSet(imported, realmFactory.Context); + deleteBeatmapSet(imported, realm.Realm); - var importedSecondTime = await LoadOszIntoStore(importer, realmFactory.Context); + var importedSecondTime = await LoadOszIntoStore(importer, realm.Realm); // check the newly "imported" beatmap has been reimported due to mismatch (even though hashes matched) Assert.IsTrue(imported.ID != importedSecondTime.ID); @@ -653,10 +653,10 @@ namespace osu.Game.Tests.Database [Test] public void TestImportWithDuplicateBeatmapIDs() { - RunTestWithRealmAsync(async (realmFactory, storage) => + RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapModelManager(realmFactory, storage); - using var store = new RulesetStore(realmFactory, storage); + using var importer = new BeatmapModelManager(realm, storage); + using var store = new RulesetStore(realm, storage); var metadata = new BeatmapMetadata { @@ -667,7 +667,7 @@ namespace osu.Game.Tests.Database } }; - var ruleset = realmFactory.Context.All().First(); + var ruleset = realm.Realm.All().First(); var toImport = new BeatmapSetInfo { @@ -699,15 +699,15 @@ namespace osu.Game.Tests.Database [Test] public void TestImportWhenFileOpen() { - RunTestWithRealmAsync(async (realmFactory, storage) => + RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapModelManager(realmFactory, storage); - using var store = new RulesetStore(realmFactory, storage); + using var importer = new BeatmapModelManager(realm, storage); + using var store = new RulesetStore(realm, storage); string? temp = TestResources.GetTestBeatmapForImport(); using (File.OpenRead(temp)) await importer.Import(temp); - EnsureLoaded(realmFactory.Context); + EnsureLoaded(realm.Realm); File.Delete(temp); Assert.IsFalse(File.Exists(temp), "We likely held a read lock on the file when we shouldn't"); }); @@ -716,10 +716,10 @@ namespace osu.Game.Tests.Database [Test] public void TestImportWithDuplicateHashes() { - RunTestWithRealmAsync(async (realmFactory, storage) => + RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapModelManager(realmFactory, storage); - using var store = new RulesetStore(realmFactory, storage); + using var importer = new BeatmapModelManager(realm, storage); + using var store = new RulesetStore(realm, storage); string? temp = TestResources.GetTestBeatmapForImport(); @@ -740,7 +740,7 @@ namespace osu.Game.Tests.Database await importer.Import(temp); - EnsureLoaded(realmFactory.Context); + EnsureLoaded(realm.Realm); } finally { @@ -752,10 +752,10 @@ namespace osu.Game.Tests.Database [Test] public void TestImportNestedStructure() { - RunTestWithRealmAsync(async (realmFactory, storage) => + RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapModelManager(realmFactory, storage); - using var store = new RulesetStore(realmFactory, storage); + using var importer = new BeatmapModelManager(realm, storage); + using var store = new RulesetStore(realm, storage); string? temp = TestResources.GetTestBeatmapForImport(); @@ -780,7 +780,7 @@ namespace osu.Game.Tests.Database Assert.NotNull(imported); Debug.Assert(imported != null); - EnsureLoaded(realmFactory.Context); + EnsureLoaded(realm.Realm); Assert.IsFalse(imported.PerformRead(s => s.Files.Any(f => f.Filename.Contains("subfolder"))), "Files contain common subfolder"); } @@ -794,10 +794,10 @@ namespace osu.Game.Tests.Database [Test] public void TestImportWithIgnoredDirectoryInArchive() { - RunTestWithRealmAsync(async (realmFactory, storage) => + RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapModelManager(realmFactory, storage); - using var store = new RulesetStore(realmFactory, storage); + using var importer = new BeatmapModelManager(realm, storage); + using var store = new RulesetStore(realm, storage); string? temp = TestResources.GetTestBeatmapForImport(); @@ -830,7 +830,7 @@ namespace osu.Game.Tests.Database Assert.NotNull(imported); Debug.Assert(imported != null); - EnsureLoaded(realmFactory.Context); + EnsureLoaded(realm.Realm); Assert.IsFalse(imported.PerformRead(s => s.Files.Any(f => f.Filename.Contains("__MACOSX"))), "Files contain resource fork folder, which should be ignored"); Assert.IsFalse(imported.PerformRead(s => s.Files.Any(f => f.Filename.Contains("actual_data"))), "Files contain common subfolder"); @@ -845,22 +845,22 @@ namespace osu.Game.Tests.Database [Test] public void TestUpdateBeatmapInfo() { - RunTestWithRealmAsync(async (realmFactory, storage) => + RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapModelManager(realmFactory, storage); - using var store = new RulesetStore(realmFactory, storage); + using var importer = new BeatmapModelManager(realm, storage); + using var store = new RulesetStore(realm, storage); string? temp = TestResources.GetTestBeatmapForImport(); await importer.Import(temp); // Update via the beatmap, not the beatmap info, to ensure correct linking - BeatmapSetInfo setToUpdate = realmFactory.Context.All().First(); + BeatmapSetInfo setToUpdate = realm.Realm.All().First(); var beatmapToUpdate = setToUpdate.Beatmaps.First(); - realmFactory.Context.Write(() => beatmapToUpdate.DifficultyName = "updated"); + realm.Realm.Write(() => beatmapToUpdate.DifficultyName = "updated"); - BeatmapInfo updatedInfo = realmFactory.Context.All().First(b => b.ID == beatmapToUpdate.ID); + BeatmapInfo updatedInfo = realm.Realm.All().First(b => b.ID == beatmapToUpdate.ID); Assert.That(updatedInfo.DifficultyName, Is.EqualTo("updated")); }); } @@ -1004,8 +1004,8 @@ namespace osu.Game.Tests.Database public class NonOptimisedBeatmapImporter : BeatmapImporter { - public NonOptimisedBeatmapImporter(RealmContextFactory realmFactory, Storage storage) - : base(realmFactory, storage) + public NonOptimisedBeatmapImporter(RealmAccess realm, Storage storage) + : base(realm, storage) { } diff --git a/osu.Game.Tests/Database/FileStoreTests.cs b/osu.Game.Tests/Database/FileStoreTests.cs index 3cb4705381..008aa3d833 100644 --- a/osu.Game.Tests/Database/FileStoreTests.cs +++ b/osu.Game.Tests/Database/FileStoreTests.cs @@ -19,10 +19,10 @@ namespace osu.Game.Tests.Database [Test] public void TestImportFile() { - RunTestWithRealm((realmFactory, storage) => + RunTestWithRealm((realmAccess, storage) => { - var realm = realmFactory.Context; - var files = new RealmFileStore(realmFactory, storage); + var realm = realmAccess.Context; + var files = new RealmFileStore(realmAccess, storage); var testData = new MemoryStream(new byte[] { 0, 1, 2, 3 }); @@ -36,10 +36,10 @@ namespace osu.Game.Tests.Database [Test] public void TestImportSameFileTwice() { - RunTestWithRealm((realmFactory, storage) => + RunTestWithRealm((realmAccess, storage) => { - var realm = realmFactory.Context; - var files = new RealmFileStore(realmFactory, storage); + var realm = realmAccess.Context; + var files = new RealmFileStore(realmAccess, storage); var testData = new MemoryStream(new byte[] { 0, 1, 2, 3 }); @@ -53,10 +53,10 @@ namespace osu.Game.Tests.Database [Test] public void TestDontPurgeReferenced() { - RunTestWithRealm((realmFactory, storage) => + RunTestWithRealm((realmAccess, storage) => { - var realm = realmFactory.Context; - var files = new RealmFileStore(realmFactory, storage); + var realm = realmAccess.Context; + var files = new RealmFileStore(realmAccess, storage); var file = realm.Write(() => files.Add(new MemoryStream(new byte[] { 0, 1, 2, 3 }), realm)); @@ -92,10 +92,10 @@ namespace osu.Game.Tests.Database [Test] public void TestPurgeUnreferenced() { - RunTestWithRealm((realmFactory, storage) => + RunTestWithRealm((realmAccess, storage) => { - var realm = realmFactory.Context; - var files = new RealmFileStore(realmFactory, storage); + var realm = realmAccess.Context; + var files = new RealmFileStore(realmAccess, storage); var file = realm.Write(() => files.Add(new MemoryStream(new byte[] { 0, 1, 2, 3 }), realm)); diff --git a/osu.Game.Tests/Database/GeneralUsageTests.cs b/osu.Game.Tests/Database/GeneralUsageTests.cs index 3c62153d9e..2533c832e6 100644 --- a/osu.Game.Tests/Database/GeneralUsageTests.cs +++ b/osu.Game.Tests/Database/GeneralUsageTests.cs @@ -21,15 +21,15 @@ namespace osu.Game.Tests.Database [Test] public void TestConstructRealm() { - RunTestWithRealm((realmFactory, _) => { realmFactory.Run(realm => realm.Refresh()); }); + RunTestWithRealm((realm, _) => { realm.Run(r => r.Refresh()); }); } [Test] public void TestBlockOperations() { - RunTestWithRealm((realmFactory, _) => + RunTestWithRealm((realm, _) => { - using (realmFactory.BlockAllOperations()) + using (realm.BlockAllOperations()) { } }); @@ -42,22 +42,22 @@ namespace osu.Game.Tests.Database [Test] public void TestNestedContextCreationWithSubscription() { - RunTestWithRealm((realmFactory, _) => + RunTestWithRealm((realm, _) => { bool callbackRan = false; - realmFactory.RegisterCustomSubscription(realm => + realm.RegisterCustomSubscription(r => { - var subscription = realm.All().QueryAsyncWithNotifications((sender, changes, error) => + var subscription = r.All().QueryAsyncWithNotifications((sender, changes, error) => { - realmFactory.Run(_ => + realm.Run(_ => { callbackRan = true; }); }); // Force the callback above to run. - realmFactory.Run(r => r.Refresh()); + realm.Run(rr => rr.Refresh()); subscription?.Dispose(); return null; @@ -70,14 +70,14 @@ namespace osu.Game.Tests.Database [Test] public void TestBlockOperationsWithContention() { - RunTestWithRealm((realmFactory, _) => + RunTestWithRealm((realm, _) => { ManualResetEventSlim stopThreadedUsage = new ManualResetEventSlim(); ManualResetEventSlim hasThreadedUsage = new ManualResetEventSlim(); Task.Factory.StartNew(() => { - realmFactory.Run(_ => + realm.Run(_ => { hasThreadedUsage.Set(); @@ -89,7 +89,7 @@ namespace osu.Game.Tests.Database Assert.Throws(() => { - using (realmFactory.BlockAllOperations()) + using (realm.BlockAllOperations()) { } }); diff --git a/osu.Game.Tests/Database/RealmLiveTests.cs b/osu.Game.Tests/Database/RealmLiveTests.cs index d53fcb9ac7..2e3f708f79 100644 --- a/osu.Game.Tests/Database/RealmLiveTests.cs +++ b/osu.Game.Tests/Database/RealmLiveTests.cs @@ -21,11 +21,11 @@ namespace osu.Game.Tests.Database [Test] public void TestLiveEquality() { - RunTestWithRealm((realmFactory, _) => + RunTestWithRealm((realm, _) => { - ILive beatmap = realmFactory.Run(realm => realm.Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()))).ToLive(realmFactory)); + ILive beatmap = realm.Run(r => r.Write(_ => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()))).ToLive(realm)); - ILive beatmap2 = realmFactory.Run(realm => realm.All().First().ToLive(realmFactory)); + ILive beatmap2 = realm.Run(r => r.All().First().ToLive(realm)); Assert.AreEqual(beatmap, beatmap2); }); @@ -34,20 +34,20 @@ namespace osu.Game.Tests.Database [Test] public void TestAccessAfterStorageMigrate() { - RunTestWithRealm((realmFactory, storage) => + RunTestWithRealm((realm, storage) => { var beatmap = new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()); ILive? liveBeatmap = null; - realmFactory.Run(realm => + realm.Run(r => { - realm.Write(r => r.Add(beatmap)); + r.Write(_ => r.Add(beatmap)); - liveBeatmap = beatmap.ToLive(realmFactory); + liveBeatmap = beatmap.ToLive(realm); }); - using (realmFactory.BlockAllOperations()) + using (realm.BlockAllOperations()) { // recycle realm before migrating } @@ -66,13 +66,13 @@ namespace osu.Game.Tests.Database [Test] public void TestAccessAfterAttach() { - RunTestWithRealm((realmFactory, _) => + RunTestWithRealm((realm, _) => { var beatmap = new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()); - var liveBeatmap = beatmap.ToLive(realmFactory); + var liveBeatmap = beatmap.ToLive(realm); - realmFactory.Run(realm => realm.Write(r => r.Add(beatmap))); + realm.Run(r => r.Write(_ => r.Add(beatmap))); Assert.IsFalse(liveBeatmap.PerformRead(l => l.Hidden)); }); @@ -98,16 +98,16 @@ namespace osu.Game.Tests.Database [Test] public void TestScopedReadWithoutContext() { - RunTestWithRealm((realmFactory, _) => + RunTestWithRealm((realm, _) => { ILive? liveBeatmap = null; Task.Factory.StartNew(() => { - realmFactory.Run(threadContext => + realm.Run(threadContext => { var beatmap = threadContext.Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()))); - liveBeatmap = beatmap.ToLive(realmFactory); + liveBeatmap = beatmap.ToLive(realm); }); }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely(); @@ -127,16 +127,16 @@ namespace osu.Game.Tests.Database [Test] public void TestScopedWriteWithoutContext() { - RunTestWithRealm((realmFactory, _) => + RunTestWithRealm((realm, _) => { ILive? liveBeatmap = null; Task.Factory.StartNew(() => { - realmFactory.Run(threadContext => + realm.Run(threadContext => { var beatmap = threadContext.Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()))); - liveBeatmap = beatmap.ToLive(realmFactory); + liveBeatmap = beatmap.ToLive(realm); }); }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely(); @@ -153,10 +153,10 @@ namespace osu.Game.Tests.Database [Test] public void TestValueAccessNonManaged() { - RunTestWithRealm((realmFactory, _) => + RunTestWithRealm((realm, _) => { var beatmap = new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()); - var liveBeatmap = beatmap.ToLive(realmFactory); + var liveBeatmap = beatmap.ToLive(realm); Assert.DoesNotThrow(() => { @@ -168,17 +168,17 @@ namespace osu.Game.Tests.Database [Test] public void TestValueAccessWithOpenContextFails() { - RunTestWithRealm((realmFactory, _) => + RunTestWithRealm((realm, _) => { ILive? liveBeatmap = null; Task.Factory.StartNew(() => { - realmFactory.Run(threadContext => + realm.Run(threadContext => { var beatmap = threadContext.Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()))); - liveBeatmap = beatmap.ToLive(realmFactory); + liveBeatmap = beatmap.ToLive(realm); }); }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely(); @@ -193,7 +193,7 @@ namespace osu.Game.Tests.Database }); // Can't be used, even from within a valid context. - realmFactory.Run(threadContext => + realm.Run(threadContext => { Assert.Throws(() => { @@ -207,16 +207,16 @@ namespace osu.Game.Tests.Database [Test] public void TestValueAccessWithoutOpenContextFails() { - RunTestWithRealm((realmFactory, _) => + RunTestWithRealm((realm, _) => { ILive? liveBeatmap = null; Task.Factory.StartNew(() => { - realmFactory.Run(threadContext => + realm.Run(threadContext => { var beatmap = threadContext.Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()))); - liveBeatmap = beatmap.ToLive(realmFactory); + liveBeatmap = beatmap.ToLive(realm); }); }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely(); @@ -235,18 +235,18 @@ namespace osu.Game.Tests.Database [Test] public void TestLiveAssumptions() { - RunTestWithRealm((realmFactory, _) => + RunTestWithRealm((realm, _) => { int changesTriggered = 0; - realmFactory.RegisterCustomSubscription(outerRealm => + realm.RegisterCustomSubscription(outerRealm => { outerRealm.All().QueryAsyncWithNotifications(gotChange); ILive? liveBeatmap = null; Task.Factory.StartNew(() => { - realmFactory.Run(innerRealm => + realm.Run(innerRealm => { var ruleset = CreateRuleset(); var beatmap = innerRealm.Write(r => r.Add(new BeatmapInfo(ruleset, new BeatmapDifficulty(), new BeatmapMetadata()))); @@ -255,7 +255,7 @@ namespace osu.Game.Tests.Database // not just a refresh from the resolved Live. innerRealm.Write(r => r.Add(new BeatmapInfo(ruleset, new BeatmapDifficulty(), new BeatmapMetadata()))); - liveBeatmap = beatmap.ToLive(realmFactory); + liveBeatmap = beatmap.ToLive(realm); }); }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely(); diff --git a/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs b/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs index 1799b95905..d62ce3b585 100644 --- a/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs +++ b/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs @@ -24,11 +24,11 @@ namespace osu.Game.Tests.Database IEnumerable? resolvedItems = null; ChangeSet? lastChanges = null; - RunTestWithRealm((realmFactory, _) => + RunTestWithRealm((realm, _) => { - realmFactory.Write(realm => realm.Add(TestResources.CreateTestBeatmapSetInfo())); + realm.Write(r => r.Add(TestResources.CreateTestBeatmapSetInfo())); - var registration = realmFactory.RegisterForNotifications(realm => realm.All(), onChanged); + var registration = realm.RegisterForNotifications(r => r.All(), onChanged); testEventsArriving(true); @@ -37,10 +37,10 @@ namespace osu.Game.Tests.Database resolvedItems = null; lastChanges = null; - using (realmFactory.BlockAllOperations()) + using (realm.BlockAllOperations()) Assert.That(resolvedItems, Is.Empty); - realmFactory.Write(realm => realm.Add(TestResources.CreateTestBeatmapSetInfo())); + realm.Write(r => r.Add(TestResources.CreateTestBeatmapSetInfo())); testEventsArriving(true); @@ -50,34 +50,34 @@ namespace osu.Game.Tests.Database registration.Dispose(); - realmFactory.Write(realm => realm.Add(TestResources.CreateTestBeatmapSetInfo())); + realm.Write(r => r.Add(TestResources.CreateTestBeatmapSetInfo())); testEventsArriving(false); // And make sure even after another context loss we don't get firings. - using (realmFactory.BlockAllOperations()) + using (realm.BlockAllOperations()) Assert.That(resolvedItems, Is.Null); - realmFactory.Write(realm => realm.Add(TestResources.CreateTestBeatmapSetInfo())); + realm.Write(r => r.Add(TestResources.CreateTestBeatmapSetInfo())); testEventsArriving(false); void testEventsArriving(bool shouldArrive) { - realmFactory.Run(realm => realm.Refresh()); + realm.Run(r => r.Refresh()); if (shouldArrive) Assert.That(resolvedItems, Has.One.Items); else Assert.That(resolvedItems, Is.Null); - realmFactory.Write(realm => + realm.Write(r => { - realm.RemoveAll(); - realm.RemoveAll(); + r.RemoveAll(); + r.RemoveAll(); }); - realmFactory.Run(realm => realm.Refresh()); + realm.Run(r => r.Refresh()); if (shouldArrive) Assert.That(lastChanges?.DeletedIndices, Has.One.Items); @@ -98,39 +98,39 @@ namespace osu.Game.Tests.Database [Test] public void TestCustomRegisterWithContextLoss() { - RunTestWithRealm((realmFactory, _) => + RunTestWithRealm((realm, _) => { BeatmapSetInfo? beatmapSetInfo = null; - realmFactory.Write(realm => realm.Add(TestResources.CreateTestBeatmapSetInfo())); + realm.Write(r => r.Add(TestResources.CreateTestBeatmapSetInfo())); - var subscription = realmFactory.RegisterCustomSubscription(realm => + var subscription = realm.RegisterCustomSubscription(r => { - beatmapSetInfo = realm.All().First(); + beatmapSetInfo = r.All().First(); return new InvokeOnDisposal(() => beatmapSetInfo = null); }); Assert.That(beatmapSetInfo, Is.Not.Null); - using (realmFactory.BlockAllOperations()) + using (realm.BlockAllOperations()) { // custom disposal action fired when context lost. Assert.That(beatmapSetInfo, Is.Null); } // re-registration after context restore. - realmFactory.Run(realm => realm.Refresh()); + realm.Run(r => r.Refresh()); Assert.That(beatmapSetInfo, Is.Not.Null); subscription.Dispose(); Assert.That(beatmapSetInfo, Is.Null); - using (realmFactory.BlockAllOperations()) + using (realm.BlockAllOperations()) Assert.That(beatmapSetInfo, Is.Null); - realmFactory.Run(realm => realm.Refresh()); + realm.Run(r => r.Refresh()); Assert.That(beatmapSetInfo, Is.Null); }); } diff --git a/osu.Game.Tests/Database/RealmTest.cs b/osu.Game.Tests/Database/RealmTest.cs index 0cee165f75..c2339dd9ad 100644 --- a/osu.Game.Tests/Database/RealmTest.cs +++ b/osu.Game.Tests/Database/RealmTest.cs @@ -30,7 +30,7 @@ namespace osu.Game.Tests.Database storage.DeleteDirectory(string.Empty); } - protected void RunTestWithRealm(Action testAction, [CallerMemberName] string caller = "") + protected void RunTestWithRealm(Action testAction, [CallerMemberName] string caller = "") { using (HeadlessGameHost host = new CleanRunHeadlessGameHost(callingMethodName: caller)) { @@ -39,22 +39,22 @@ namespace osu.Game.Tests.Database // ReSharper disable once AccessToDisposedClosure var testStorage = new OsuStorage(host, storage.GetStorageForDirectory(caller)); - using (var realmFactory = new RealmContextFactory(testStorage, "client")) + using (var realm = new RealmAccess(testStorage, "client")) { - Logger.Log($"Running test using realm file {testStorage.GetFullPath(realmFactory.Filename)}"); - testAction(realmFactory, testStorage); + Logger.Log($"Running test using realm file {testStorage.GetFullPath(realm.Filename)}"); + testAction(realm, testStorage); - realmFactory.Dispose(); + realm.Dispose(); - Logger.Log($"Final database size: {getFileSize(testStorage, realmFactory)}"); - realmFactory.Compact(); - Logger.Log($"Final database size after compact: {getFileSize(testStorage, realmFactory)}"); + Logger.Log($"Final database size: {getFileSize(testStorage, realm)}"); + realm.Compact(); + Logger.Log($"Final database size after compact: {getFileSize(testStorage, realm)}"); } })); } } - protected void RunTestWithRealmAsync(Func testAction, [CallerMemberName] string caller = "") + protected void RunTestWithRealmAsync(Func testAction, [CallerMemberName] string caller = "") { using (HeadlessGameHost host = new CleanRunHeadlessGameHost(callingMethodName: caller)) { @@ -62,15 +62,15 @@ namespace osu.Game.Tests.Database { var testStorage = storage.GetStorageForDirectory(caller); - using (var realmFactory = new RealmContextFactory(testStorage, "client")) + using (var realm = new RealmAccess(testStorage, "client")) { - Logger.Log($"Running test using realm file {testStorage.GetFullPath(realmFactory.Filename)}"); - await testAction(realmFactory, testStorage); + Logger.Log($"Running test using realm file {testStorage.GetFullPath(realm.Filename)}"); + await testAction(realm, testStorage); - realmFactory.Dispose(); + realm.Dispose(); - Logger.Log($"Final database size: {getFileSize(testStorage, realmFactory)}"); - realmFactory.Compact(); + Logger.Log($"Final database size: {getFileSize(testStorage, realm)}"); + realm.Compact(); } })); } @@ -138,11 +138,11 @@ namespace osu.Game.Tests.Database } } - private static long getFileSize(Storage testStorage, RealmContextFactory realmFactory) + private static long getFileSize(Storage testStorage, RealmAccess realm) { try { - using (var stream = testStorage.GetStream(realmFactory.Filename)) + using (var stream = testStorage.GetStream(realm.Filename)) return stream?.Length ?? 0; } catch diff --git a/osu.Game.Tests/Database/RulesetStoreTests.cs b/osu.Game.Tests/Database/RulesetStoreTests.cs index 4416da6f92..7544142b70 100644 --- a/osu.Game.Tests/Database/RulesetStoreTests.cs +++ b/osu.Game.Tests/Database/RulesetStoreTests.cs @@ -12,37 +12,37 @@ namespace osu.Game.Tests.Database [Test] public void TestCreateStore() { - RunTestWithRealm((realmFactory, storage) => + RunTestWithRealm((realm, storage) => { - var rulesets = new RulesetStore(realmFactory, storage); + var rulesets = new RulesetStore(realm, storage); Assert.AreEqual(4, rulesets.AvailableRulesets.Count()); - Assert.AreEqual(4, realmFactory.Context.All().Count()); + Assert.AreEqual(4, realm.Realm.All().Count()); }); } [Test] public void TestCreateStoreTwiceDoesntAddRulesetsAgain() { - RunTestWithRealm((realmFactory, storage) => + RunTestWithRealm((realm, storage) => { - var rulesets = new RulesetStore(realmFactory, storage); - var rulesets2 = new RulesetStore(realmFactory, storage); + var rulesets = new RulesetStore(realm, storage); + var rulesets2 = new RulesetStore(realm, storage); Assert.AreEqual(4, rulesets.AvailableRulesets.Count()); Assert.AreEqual(4, rulesets2.AvailableRulesets.Count()); Assert.AreEqual(rulesets.AvailableRulesets.First(), rulesets2.AvailableRulesets.First()); - Assert.AreEqual(4, realmFactory.Context.All().Count()); + Assert.AreEqual(4, realm.Realm.All().Count()); }); } [Test] public void TestRetrievedRulesetsAreDetached() { - RunTestWithRealm((realmFactory, storage) => + RunTestWithRealm((realm, storage) => { - var rulesets = new RulesetStore(realmFactory, storage); + var rulesets = new RulesetStore(realm, storage); Assert.IsFalse(rulesets.AvailableRulesets.First().IsManaged); Assert.IsFalse(rulesets.GetRuleset(0)?.IsManaged); diff --git a/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs index c1041e9fd6..4b8816f142 100644 --- a/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs +++ b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs @@ -24,7 +24,7 @@ namespace osu.Game.Tests.Database private RealmKeyBindingStore keyBindingStore; - private RealmContextFactory realmContextFactory; + private RealmAccess realm; [SetUp] public void SetUp() @@ -33,8 +33,8 @@ namespace osu.Game.Tests.Database storage = new NativeStorage(directory.FullName); - realmContextFactory = new RealmContextFactory(storage, "test"); - keyBindingStore = new RealmKeyBindingStore(realmContextFactory, new ReadableKeyCombinationProvider()); + realm = new RealmAccess(storage, "test"); + keyBindingStore = new RealmKeyBindingStore(realm, new ReadableKeyCombinationProvider()); } [Test] @@ -60,7 +60,7 @@ namespace osu.Game.Tests.Database KeyBindingContainer testContainer = new TestKeyBindingContainer(); // Add some excess bindings for an action which only supports 1. - realmContextFactory.Write(realm => + realm.Write(realm => { realm.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.A))); realm.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.S))); @@ -76,7 +76,7 @@ namespace osu.Game.Tests.Database private int queryCount(GlobalAction? match = null) { - return realmContextFactory.Run(realm => + return realm.Run(realm => { var results = realm.All(); if (match.HasValue) @@ -92,7 +92,7 @@ namespace osu.Game.Tests.Database keyBindingStore.Register(testContainer, Enumerable.Empty()); - realmContextFactory.Run(outerRealm => + realm.Run(outerRealm => { var backBinding = outerRealm.All().Single(k => k.ActionInt == (int)GlobalAction.Back); @@ -100,7 +100,7 @@ namespace osu.Game.Tests.Database var tsr = ThreadSafeReference.Create(backBinding); - realmContextFactory.Run(innerRealm => + realm.Run(innerRealm => { var binding = innerRealm.ResolveReference(tsr); innerRealm.Write(() => binding.KeyCombination = new KeyCombination(InputKey.BackSpace)); @@ -117,7 +117,7 @@ namespace osu.Game.Tests.Database [TearDown] public void TearDown() { - realmContextFactory.Dispose(); + realm.Dispose(); storage.DeleteDirectory(string.Empty); } diff --git a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs index f0ebd7a8cc..88862ea28b 100644 --- a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs @@ -261,7 +261,7 @@ namespace osu.Game.Tests.Gameplay public AudioManager AudioManager => Audio; public IResourceStore Files => null; public new IResourceStore Resources => base.Resources; - public RealmContextFactory RealmContextFactory => null; + public RealmAccess RealmAccess => null; public IResourceStore CreateTextureLoaderStore(IResourceStore underlyingStore) => null; #endregion diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index 1d639c6418..e35e4d9b15 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -45,8 +45,8 @@ namespace osu.Game.Tests.Online [BackgroundDependencyLoader] private void load(AudioManager audio, GameHost host) { - Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); - Dependencies.CacheAs(beatmaps = new TestBeatmapManager(LocalStorage, ContextFactory, rulesets, API, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(rulesets = new RulesetStore(Access)); + Dependencies.CacheAs(beatmaps = new TestBeatmapManager(LocalStorage, Access, rulesets, API, audio, Resources, host, Beatmap.Default)); Dependencies.CacheAs(beatmapDownloader = new TestBeatmapModelDownloader(beatmaps, API, host)); } @@ -60,8 +60,8 @@ namespace osu.Game.Tests.Online testBeatmapInfo = getTestBeatmapInfo(testBeatmapFile); testBeatmapSet = testBeatmapInfo.BeatmapSet; - ContextFactory.Write(r => r.RemoveAll()); - ContextFactory.Write(r => r.RemoveAll()); + Access.Write(r => r.RemoveAll()); + Access.Write(r => r.RemoveAll()); selectedItem.Value = new PlaylistItem { @@ -166,22 +166,22 @@ namespace osu.Game.Tests.Online public Task> CurrentImportTask { get; private set; } - public TestBeatmapManager(Storage storage, RealmContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore resources, GameHost host = null, WorkingBeatmap defaultBeatmap = null) - : base(storage, contextFactory, rulesets, api, audioManager, resources, host, defaultBeatmap) + public TestBeatmapManager(Storage storage, RealmAccess realm, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore resources, GameHost host = null, WorkingBeatmap defaultBeatmap = null) + : base(storage, realm, rulesets, api, audioManager, resources, host, defaultBeatmap) { } - protected override BeatmapModelManager CreateBeatmapModelManager(Storage storage, RealmContextFactory contextFactory, RulesetStore rulesets, BeatmapOnlineLookupQueue onlineLookupQueue) + protected override BeatmapModelManager CreateBeatmapModelManager(Storage storage, RealmAccess realm, RulesetStore rulesets, BeatmapOnlineLookupQueue onlineLookupQueue) { - return new TestBeatmapModelManager(this, storage, contextFactory, rulesets, onlineLookupQueue); + return new TestBeatmapModelManager(this, storage, realm, rulesets, onlineLookupQueue); } internal class TestBeatmapModelManager : BeatmapModelManager { private readonly TestBeatmapManager testBeatmapManager; - public TestBeatmapModelManager(TestBeatmapManager testBeatmapManager, Storage storage, RealmContextFactory databaseContextFactory, RulesetStore rulesetStore, BeatmapOnlineLookupQueue beatmapOnlineLookupQueue) - : base(databaseContextFactory, storage, beatmapOnlineLookupQueue) + public TestBeatmapModelManager(TestBeatmapManager testBeatmapManager, Storage storage, RealmAccess databaseAccess, RulesetStore rulesetStore, BeatmapOnlineLookupQueue beatmapOnlineLookupQueue) + : base(databaseAccess, storage, beatmapOnlineLookupQueue) { this.testBeatmapManager = testBeatmapManager; } diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs index 4ab4c08353..fbfa7eda6a 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs @@ -47,10 +47,10 @@ namespace osu.Game.Tests.Visual.Background [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); - Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(rulesets = new RulesetStore(Access)); + Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(new OsuConfigManager(LocalStorage)); - Dependencies.Cache(ContextFactory); + Dependencies.Cache(Access); manager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); diff --git a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs index 18572ac211..fd0645a1e9 100644 --- a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs +++ b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs @@ -36,9 +36,9 @@ namespace osu.Game.Tests.Visual.Collections [BackgroundDependencyLoader] private void load(GameHost host) { - Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); - Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, Audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(ContextFactory); + Dependencies.Cache(rulesets = new RulesetStore(Access)); + Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Access, rulesets, null, Audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Access); beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs index d4282ff21e..07b2bdcba3 100644 --- a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs +++ b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs @@ -47,9 +47,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(ContextFactory); + Dependencies.Cache(rulesets = new RulesetStore(Access)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Access); } public override void SetUpSteps() diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index 99c867b014..0bf1d60ac5 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -43,9 +43,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); - Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(ContextFactory); + Dependencies.Cache(rulesets = new RulesetStore(Access)); + Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Access); } [Test] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 373b165acc..063d886729 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -61,9 +61,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, API, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(ContextFactory); + Dependencies.Cache(rulesets = new RulesetStore(Access)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Access, rulesets, API, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Access); } public override void SetUpSteps() diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index 15ebe0ee00..8c79c468d7 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -42,9 +42,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); - Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(ContextFactory); + Dependencies.Cache(rulesets = new RulesetStore(Access)); + Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Access); beatmaps = new List(); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index 012a2fd960..77e2c9c714 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -38,9 +38,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(ContextFactory); + Dependencies.Cache(rulesets = new RulesetStore(Access)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Access); beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs index d547b42891..4270818b1a 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs @@ -33,9 +33,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(ContextFactory); + Dependencies.Cache(rulesets = new RulesetStore(Access)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Access); } [SetUp] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs index 965b142ed7..56e64292c6 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs @@ -38,9 +38,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, API, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(ContextFactory); + Dependencies.Cache(rulesets = new RulesetStore(Access)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Access, rulesets, API, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Access); } public override void SetUpSteps() diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs index 1c346e09d5..afb60b62aa 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs @@ -40,9 +40,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(ContextFactory); + Dependencies.Cache(rulesets = new RulesetStore(Access)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Access); beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs index 221732910b..79b29f0eca 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs @@ -41,9 +41,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(ContextFactory); + Dependencies.Cache(rulesets = new RulesetStore(Access)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Access); beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs index 0b0006e437..6ed57e9899 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs @@ -34,9 +34,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); - Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(ContextFactory); + Dependencies.Cache(rulesets = new RulesetStore(Access)); + Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Access); var beatmapSet = TestResources.CreateTestBeatmapSetInfo(); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs index 39cde0ad87..bd95b297d4 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs @@ -42,9 +42,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(ContextFactory); + Dependencies.Cache(rulesets = new RulesetStore(Access)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Access); } public override void SetUpSteps() diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneChangeAndUseGameplayBindings.cs b/osu.Game.Tests/Visual/Navigation/TestSceneChangeAndUseGameplayBindings.cs index 0f314242b4..7cb29895eb 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneChangeAndUseGameplayBindings.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneChangeAndUseGameplayBindings.cs @@ -76,7 +76,7 @@ namespace osu.Game.Tests.Visual.Navigation .ChildrenOfType().SingleOrDefault(); private RealmKeyBinding firstOsuRulesetKeyBindings => Game.Dependencies - .Get().Context + .Get().Context .All() .AsEnumerable() .First(k => k.RulesetName == "osu" && k.ActionInt == 0); diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs index bc9f759bdd..716e3a535d 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs @@ -40,9 +40,9 @@ namespace osu.Game.Tests.Visual.Playlists [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); - Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, API, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(ContextFactory); + Dependencies.Cache(rulesets = new RulesetStore(Access)); + Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Access, rulesets, API, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Access); } [SetUpSteps] diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index a77480ee54..13b4af5223 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs @@ -36,13 +36,13 @@ namespace osu.Game.Tests.Visual.Ranking private BeatmapManager beatmaps { get; set; } [Resolved] - private RealmContextFactory realmContextFactory { get; set; } + private RealmAccess realm { get; set; } protected override void LoadComplete() { base.LoadComplete(); - realmContextFactory.Run(realm => + realm.Run(realm => { var beatmapInfo = realm.All() .Filter($"{nameof(BeatmapInfo.Ruleset)}.{nameof(RulesetInfo.OnlineID)} = $0", 0) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 2e1a66be5f..2f5594379b 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -42,10 +42,10 @@ namespace osu.Game.Tests.Visual.SongSelect { var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - dependencies.Cache(rulesetStore = new RulesetStore(ContextFactory)); - dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesetStore, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default)); - dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, ContextFactory, Scheduler)); - Dependencies.Cache(ContextFactory); + dependencies.Cache(rulesetStore = new RulesetStore(Access)); + dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Access, rulesetStore, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default)); + dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, Access, Scheduler)); + Dependencies.Cache(Access); return dependencies; } diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs index ca8e9d2eff..4868a4a075 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs @@ -36,9 +36,9 @@ namespace osu.Game.Tests.Visual.SongSelect [BackgroundDependencyLoader] private void load(GameHost host) { - Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); - Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, Audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(ContextFactory); + Dependencies.Cache(rulesets = new RulesetStore(Access)); + Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Access, rulesets, null, Audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Access); beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 6295a52bdd..d8a39dda01 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -47,9 +47,9 @@ namespace osu.Game.Tests.Visual.SongSelect { // These DI caches are required to ensure for interactive runs this test scene doesn't nuke all user beatmaps in the local install. // At a point we have isolated interactive test runs enough, this can likely be removed. - Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); - Dependencies.Cache(ContextFactory); - Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, defaultBeatmap = Beatmap.Default)); + Dependencies.Cache(rulesets = new RulesetStore(Access)); + Dependencies.Cache(Access); + Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, defaultBeatmap = Beatmap.Default)); Dependencies.Cache(music = new MusicController()); diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs index 3aa5a759e6..a0657ffdf6 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs @@ -28,10 +28,10 @@ namespace osu.Game.Tests.Visual.SongSelect [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); - Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(scoreManager = new ScoreManager(rulesets, () => beatmapManager, LocalStorage, ContextFactory, Scheduler)); - Dependencies.Cache(ContextFactory); + Dependencies.Cache(rulesets = new RulesetStore(Access)); + Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(scoreManager = new ScoreManager(rulesets, () => beatmapManager, LocalStorage, Access, Scheduler)); + Dependencies.Cache(Access); beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index f43354514b..2278d3f8bf 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -45,7 +45,7 @@ namespace osu.Game.Tests.Visual.UserInterface private BeatmapInfo beatmapInfo; [Resolved] - private RealmContextFactory realmFactory { get; set; } + private RealmAccess realm { get; set; } [Cached] private readonly DialogOverlay dialogOverlay; @@ -87,10 +87,10 @@ namespace osu.Game.Tests.Visual.UserInterface { var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - dependencies.Cache(rulesetStore = new RulesetStore(ContextFactory)); - dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesetStore, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default)); - dependencies.Cache(scoreManager = new ScoreManager(dependencies.Get(), () => beatmapManager, LocalStorage, ContextFactory, Scheduler)); - Dependencies.Cache(ContextFactory); + dependencies.Cache(rulesetStore = new RulesetStore(Access)); + dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Access, rulesetStore, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default)); + dependencies.Cache(scoreManager = new ScoreManager(dependencies.Get(), () => beatmapManager, LocalStorage, Access, Scheduler)); + Dependencies.Cache(Access); var imported = beatmapManager.Import(new ImportTask(TestResources.GetQuickTestBeatmapForImport())).GetResultSafely(); @@ -122,7 +122,7 @@ namespace osu.Game.Tests.Visual.UserInterface [SetUp] public void Setup() => Schedule(() => { - realmFactory.Run(realm => + realm.Run(realm => { // Due to soft deletions, we can re-use deleted scores between test runs scoreManager.Undelete(realm.All().Where(s => s.DeletePending).ToList()); diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 43e4b482bd..ddc1d054cc 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -41,11 +41,11 @@ namespace osu.Game.Beatmaps private readonly WorkingBeatmapCache workingBeatmapCache; private readonly BeatmapOnlineLookupQueue? onlineBeatmapLookupQueue; - private readonly RealmContextFactory contextFactory; + private readonly RealmAccess realm; - public BeatmapManager(Storage storage, RealmContextFactory contextFactory, RulesetStore rulesets, IAPIProvider? api, AudioManager audioManager, IResourceStore gameResources, GameHost? host = null, WorkingBeatmap? defaultBeatmap = null, bool performOnlineLookups = false) + public BeatmapManager(Storage storage, RealmAccess realm, RulesetStore rulesets, IAPIProvider? api, AudioManager audioManager, IResourceStore gameResources, GameHost? host = null, WorkingBeatmap? defaultBeatmap = null, bool performOnlineLookups = false) { - this.contextFactory = contextFactory; + this.realm = realm; if (performOnlineLookups) { @@ -55,11 +55,11 @@ namespace osu.Game.Beatmaps onlineBeatmapLookupQueue = new BeatmapOnlineLookupQueue(api, storage); } - var userResources = new RealmFileStore(contextFactory, storage).Store; + var userResources = new RealmFileStore(realm, storage).Store; BeatmapTrackStore = audioManager.GetTrackStore(userResources); - beatmapModelManager = CreateBeatmapModelManager(storage, contextFactory, rulesets, onlineBeatmapLookupQueue); + beatmapModelManager = CreateBeatmapModelManager(storage, realm, rulesets, onlineBeatmapLookupQueue); workingBeatmapCache = CreateWorkingBeatmapCache(audioManager, gameResources, userResources, defaultBeatmap, host); beatmapModelManager.WorkingBeatmapCache = workingBeatmapCache; @@ -70,8 +70,8 @@ namespace osu.Game.Beatmaps return new WorkingBeatmapCache(BeatmapTrackStore, audioManager, resources, storage, defaultBeatmap, host); } - protected virtual BeatmapModelManager CreateBeatmapModelManager(Storage storage, RealmContextFactory contextFactory, RulesetStore rulesets, BeatmapOnlineLookupQueue? onlineLookupQueue) => - new BeatmapModelManager(contextFactory, storage, onlineLookupQueue); + protected virtual BeatmapModelManager CreateBeatmapModelManager(Storage storage, RealmAccess realm, RulesetStore rulesets, BeatmapOnlineLookupQueue? onlineLookupQueue) => + new BeatmapModelManager(realm, storage, onlineLookupQueue); /// /// Create a new . @@ -119,7 +119,7 @@ namespace osu.Game.Beatmaps /// The beatmap difficulty to hide. public void Hide(BeatmapInfo beatmapInfo) { - contextFactory.Run(realm => + realm.Run(realm => { using (var transaction = realm.BeginWrite()) { @@ -138,7 +138,7 @@ namespace osu.Game.Beatmaps /// The beatmap difficulty to restore. public void Restore(BeatmapInfo beatmapInfo) { - contextFactory.Run(realm => + realm.Run(realm => { using (var transaction = realm.BeginWrite()) { @@ -153,7 +153,7 @@ namespace osu.Game.Beatmaps public void RestoreAll() { - contextFactory.Run(realm => + realm.Run(realm => { using (var transaction = realm.BeginWrite()) { @@ -171,7 +171,7 @@ namespace osu.Game.Beatmaps /// A list of available . public List GetAllUsableBeatmapSets() { - return contextFactory.Run(realm => + return realm.Run(realm => { realm.Refresh(); return realm.All().Where(b => !b.DeletePending).Detach(); @@ -185,7 +185,7 @@ namespace osu.Game.Beatmaps /// The first result for the provided query, or null if no results were found. public ILive? QueryBeatmapSet(Expression> query) { - return contextFactory.Run(realm => realm.All().FirstOrDefault(query)?.ToLive(contextFactory)); + return realm.Run(r => r.All().FirstOrDefault(query)?.ToLive(realm)); } #region Delegation to BeatmapModelManager (methods which previously existed locally). @@ -240,7 +240,7 @@ namespace osu.Game.Beatmaps public void Delete(Expression>? filter = null, bool silent = false) { - contextFactory.Run(realm => + realm.Run(realm => { var items = realm.All().Where(s => !s.DeletePending && !s.Protected); @@ -253,7 +253,7 @@ namespace osu.Game.Beatmaps public void UndeleteAll() { - contextFactory.Run(realm => beatmapModelManager.Undelete(realm.All().Where(s => s.DeletePending).ToList())); + realm.Run(realm => beatmapModelManager.Undelete(realm.All().Where(s => s.DeletePending).ToList())); } public void Undelete(List items, bool silent = false) @@ -312,7 +312,7 @@ namespace osu.Game.Beatmaps // If we seem to be missing files, now is a good time to re-fetch. if (importedBeatmap?.BeatmapSet?.Files.Count == 0) { - contextFactory.Run(realm => + realm.Run(realm => { var refetch = realm.Find(importedBeatmap.ID)?.Detach(); diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs index ead86c1059..167d77d6f6 100644 --- a/osu.Game/Beatmaps/BeatmapModelManager.cs +++ b/osu.Game/Beatmaps/BeatmapModelManager.cs @@ -33,8 +33,8 @@ namespace osu.Game.Beatmaps protected override string[] HashableFileTypes => new[] { ".osu" }; - public BeatmapModelManager(RealmContextFactory contextFactory, Storage storage, BeatmapOnlineLookupQueue? onlineLookupQueue = null) - : base(contextFactory, storage, onlineLookupQueue) + public BeatmapModelManager(RealmAccess realm, Storage storage, BeatmapOnlineLookupQueue? onlineLookupQueue = null) + : base(realm, storage, onlineLookupQueue) { } @@ -98,12 +98,12 @@ namespace osu.Game.Beatmaps /// The first result for the provided query, or null if no results were found. public BeatmapInfo? QueryBeatmap(Expression> query) { - return ContextFactory.Run(realm => realm.All().FirstOrDefault(query)?.Detach()); + return Access.Run(realm => realm.All().FirstOrDefault(query)?.Detach()); } public void Update(BeatmapSetInfo item) { - ContextFactory.Write(realm => + Access.Write(realm => { var existing = realm.Find(item.ID); item.CopyChangesToRealm(existing); diff --git a/osu.Game/Beatmaps/WorkingBeatmapCache.cs b/osu.Game/Beatmaps/WorkingBeatmapCache.cs index 6947752c47..d3f356bb24 100644 --- a/osu.Game/Beatmaps/WorkingBeatmapCache.cs +++ b/osu.Game/Beatmaps/WorkingBeatmapCache.cs @@ -100,7 +100,7 @@ namespace osu.Game.Beatmaps TextureStore IBeatmapResourceProvider.LargeTextureStore => largeTextureStore; ITrackStore IBeatmapResourceProvider.Tracks => trackStore; AudioManager IStorageResourceProvider.AudioManager => audioManager; - RealmContextFactory IStorageResourceProvider.RealmContextFactory => null; + RealmAccess IStorageResourceProvider.RealmAccess => null; IResourceStore IStorageResourceProvider.Files => files; IResourceStore IStorageResourceProvider.Resources => resources; IResourceStore IStorageResourceProvider.CreateTextureLoaderStore(IResourceStore underlyingStore) => host?.CreateTextureLoaderStore(underlyingStore); diff --git a/osu.Game/Configuration/SettingsStore.cs b/osu.Game/Configuration/SettingsStore.cs index 2bba20fb09..e5d2d572c8 100644 --- a/osu.Game/Configuration/SettingsStore.cs +++ b/osu.Game/Configuration/SettingsStore.cs @@ -10,11 +10,11 @@ namespace osu.Game.Configuration // this class mostly exists as a wrapper to avoid breaking the ruleset API (see usage in RulesetConfigManager). // it may cease to exist going forward, depending on how the structure of the config data layer changes. - public readonly RealmContextFactory Realm; + public readonly RealmAccess Realm; - public SettingsStore(RealmContextFactory realmFactory) + public SettingsStore(RealmAccess realm) { - Realm = realmFactory; + Realm = realm; } } } diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index edce99e302..a0787e81e6 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -35,7 +35,7 @@ namespace osu.Game.Database private DatabaseContextFactory efContextFactory { get; set; } = null!; [Resolved] - private RealmContextFactory realmContextFactory { get; set; } = null!; + private RealmAccess realm { get; set; } = null!; [Resolved] private OsuConfigManager config { get; set; } = null!; @@ -101,7 +101,7 @@ namespace osu.Game.Database { using (var ef = efContextFactory.Get()) { - realmContextFactory.Write(realm => + realm.Write(realm => { // Before beginning, ensure realm is in an empty state. // Migrations which are half-completed could lead to issues if the user tries a second time. @@ -158,7 +158,7 @@ namespace osu.Game.Database int count = existingBeatmapSets.Count(); - realmContextFactory.Run(realm => + realm.Run(realm => { log($"Found {count} beatmaps in EF"); @@ -280,7 +280,7 @@ namespace osu.Game.Database int count = existingScores.Count(); - realmContextFactory.Run(realm => + realm.Run(realm => { log($"Found {count} scores in EF"); @@ -369,7 +369,7 @@ namespace osu.Game.Database break; } - realmContextFactory.Run(realm => + realm.Run(realm => { using (var transaction = realm.BeginWrite()) { @@ -428,7 +428,7 @@ namespace osu.Game.Database log("Beginning settings migration to realm"); - realmContextFactory.Run(realm => + realm.Run(realm => { using (var transaction = realm.BeginWrite()) { diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmAccess.cs similarity index 98% rename from osu.Game/Database/RealmContextFactory.cs rename to osu.Game/Database/RealmAccess.cs index 778092c543..2f397639c9 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -32,7 +32,7 @@ namespace osu.Game.Database /// /// A factory which provides both the main (update thread bound) realm context and creates contexts for async usage. /// - public class RealmContextFactory : IDisposable + public class RealmAccess : IDisposable { private readonly Storage storage; @@ -123,7 +123,7 @@ namespace osu.Game.Database /// The game storage which will be used to create the realm backing file. /// The filename to use for the realm backing file. A ".realm" extension will be added automatically if not specified. /// An EF factory used only for migration purposes. - public RealmContextFactory(Storage storage, string filename, IDatabaseContextFactory? efContextFactory = null) + public RealmAccess(Storage storage, string filename, IDatabaseContextFactory? efContextFactory = null) { this.storage = storage; this.efContextFactory = efContextFactory; @@ -365,7 +365,7 @@ namespace osu.Game.Database private Realm createContext() { if (isDisposed) - throw new ObjectDisposedException(nameof(RealmContextFactory)); + throw new ObjectDisposedException(nameof(RealmAccess)); bool tookSemaphoreLock = false; @@ -592,7 +592,7 @@ namespace osu.Game.Database public IDisposable BlockAllOperations() { if (isDisposed) - throw new ObjectDisposedException(nameof(RealmContextFactory)); + throw new ObjectDisposedException(nameof(RealmAccess)); SynchronizationContext? syncContext = null; @@ -652,7 +652,7 @@ namespace osu.Game.Database throw; } - return new InvokeOnDisposal(this, factory => + return new InvokeOnDisposal(this, factory => { factory.contextCreationLock.Release(); Logger.Log(@"Restoring realm operations.", LoggingTarget.Database); diff --git a/osu.Game/Database/RealmLive.cs b/osu.Game/Database/RealmLive.cs index df5e165f8e..29159fd5be 100644 --- a/osu.Game/Database/RealmLive.cs +++ b/osu.Game/Database/RealmLive.cs @@ -24,17 +24,17 @@ namespace osu.Game.Database /// private readonly T data; - private readonly RealmContextFactory realmFactory; + private readonly RealmAccess realm; /// /// Construct a new instance of live realm data. /// /// The realm data. - /// The realm factory the data was sourced from. May be null for an unmanaged object. - public RealmLive(T data, RealmContextFactory realmFactory) + /// The realm factory the data was sourced from. May be null for an unmanaged object. + public RealmLive(T data, RealmAccess realm) { this.data = data; - this.realmFactory = realmFactory; + this.realm = realm; ID = data.ID; } @@ -51,7 +51,7 @@ namespace osu.Game.Database return; } - realmFactory.Run(realm => + realm.Run(realm => { perform(retrieveFromID(realm, ID)); }); @@ -66,7 +66,7 @@ namespace osu.Game.Database if (!IsManaged) return perform(data); - return realmFactory.Run(realm => + return realm.Run(realm => { var returnData = perform(retrieveFromID(realm, ID)); @@ -104,7 +104,7 @@ namespace osu.Game.Database if (!ThreadSafety.IsUpdateThread) throw new InvalidOperationException($"Can't use {nameof(Value)} on managed objects from non-update threads"); - return realmFactory.Context.Find(ID); + return realm.Realm.Find(ID); } } diff --git a/osu.Game/Database/RealmObjectExtensions.cs b/osu.Game/Database/RealmObjectExtensions.cs index d9026d165d..d4f8978ac5 100644 --- a/osu.Game/Database/RealmObjectExtensions.cs +++ b/osu.Game/Database/RealmObjectExtensions.cs @@ -216,16 +216,16 @@ namespace osu.Game.Database return new RealmLiveUnmanaged(realmObject); } - public static List> ToLive(this IEnumerable realmList, RealmContextFactory realmContextFactory) + public static List> ToLive(this IEnumerable realmList, RealmAccess realm) where T : RealmObject, IHasGuidPrimaryKey { - return realmList.Select(l => new RealmLive(l, realmContextFactory)).Cast>().ToList(); + return realmList.Select(l => new RealmLive(l, realm)).Cast>().ToList(); } - public static ILive ToLive(this T realmObject, RealmContextFactory realmContextFactory) + public static ILive ToLive(this T realmObject, RealmAccess realm) where T : RealmObject, IHasGuidPrimaryKey { - return new RealmLive(realmObject, realmContextFactory); + return new RealmLive(realmObject, realm); } /// @@ -271,8 +271,8 @@ namespace osu.Game.Database public static IDisposable? QueryAsyncWithNotifications(this IRealmCollection collection, NotificationCallbackDelegate callback) where T : RealmObjectBase { - if (!RealmContextFactory.CurrentThreadSubscriptionsAllowed) - throw new InvalidOperationException($"Make sure to call {nameof(RealmContextFactory)}.{nameof(RealmContextFactory.RegisterForNotifications)}"); + if (!RealmAccess.CurrentThreadSubscriptionsAllowed) + throw new InvalidOperationException($"Make sure to call {nameof(RealmAccess)}.{nameof(RealmAccess.RegisterForNotifications)}"); return collection.SubscribeForNotifications(callback); } diff --git a/osu.Game/IO/IStorageResourceProvider.cs b/osu.Game/IO/IStorageResourceProvider.cs index 950b5aae09..b381ac70b0 100644 --- a/osu.Game/IO/IStorageResourceProvider.cs +++ b/osu.Game/IO/IStorageResourceProvider.cs @@ -28,7 +28,7 @@ namespace osu.Game.IO /// /// Access realm. /// - RealmContextFactory RealmContextFactory { get; } + RealmAccess RealmAccess { get; } /// /// Create a texture loader store based on an underlying data store. diff --git a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs index 9cd9865441..d54c049c99 100644 --- a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs +++ b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs @@ -25,7 +25,7 @@ namespace osu.Game.Input.Bindings private IDisposable realmSubscription; [Resolved] - private RealmContextFactory realmFactory { get; set; } + private RealmAccess realm { get; set; } public override IEnumerable DefaultKeyBindings => ruleset.CreateInstance().GetDefaultKeyBindings(variant ?? 0); @@ -49,13 +49,13 @@ namespace osu.Game.Input.Bindings private IQueryable queryRealmKeyBindings() { string rulesetName = ruleset?.ShortName; - return realmFactory.Context.All() - .Where(b => b.RulesetName == rulesetName && b.Variant == variant); + return realm.Realm.All() + .Where(b => b.RulesetName == rulesetName && b.Variant == variant); } protected override void LoadComplete() { - realmSubscription = realmFactory.RegisterForNotifications(realm => queryRealmKeyBindings(), (sender, changes, error) => + realmSubscription = realm.RegisterForNotifications(realm => queryRealmKeyBindings(), (sender, changes, error) => { // The first fire of this is a bit redundant as this is being called in base.LoadComplete, // but this is safest in case the subscription is restored after a context recycle. diff --git a/osu.Game/Input/RealmKeyBindingStore.cs b/osu.Game/Input/RealmKeyBindingStore.cs index 60f7eb2198..cccd42a9aa 100644 --- a/osu.Game/Input/RealmKeyBindingStore.cs +++ b/osu.Game/Input/RealmKeyBindingStore.cs @@ -16,12 +16,12 @@ namespace osu.Game.Input { public class RealmKeyBindingStore { - private readonly RealmContextFactory realmFactory; + private readonly RealmAccess realm; private readonly ReadableKeyCombinationProvider keyCombinationProvider; - public RealmKeyBindingStore(RealmContextFactory realmFactory, ReadableKeyCombinationProvider keyCombinationProvider) + public RealmKeyBindingStore(RealmAccess realm, ReadableKeyCombinationProvider keyCombinationProvider) { - this.realmFactory = realmFactory; + this.realm = realm; this.keyCombinationProvider = keyCombinationProvider; } @@ -34,7 +34,7 @@ namespace osu.Game.Input { List combinations = new List(); - realmFactory.Run(context => + realm.Run(context => { foreach (var action in context.All().Where(b => string.IsNullOrEmpty(b.RulesetName) && (GlobalAction)b.ActionInt == globalAction)) { @@ -56,7 +56,7 @@ namespace osu.Game.Input /// The rulesets to populate defaults from. public void Register(KeyBindingContainer container, IEnumerable rulesets) { - realmFactory.Run(realm => + realm.Run(realm => { using (var transaction = realm.BeginWrite()) { diff --git a/osu.Game/Online/BeatmapDownloadTracker.cs b/osu.Game/Online/BeatmapDownloadTracker.cs index 3f45afb6b2..f54dc30620 100644 --- a/osu.Game/Online/BeatmapDownloadTracker.cs +++ b/osu.Game/Online/BeatmapDownloadTracker.cs @@ -22,7 +22,7 @@ namespace osu.Game.Online private IDisposable? realmSubscription; [Resolved] - private RealmContextFactory realmContextFactory { get; set; } = null!; + private RealmAccess realm { get; set; } = null!; public BeatmapDownloadTracker(IBeatmapSetInfo trackedItem) : base(trackedItem) @@ -42,7 +42,7 @@ namespace osu.Game.Online // Used to interact with manager classes that don't support interface types. Will eventually be replaced. var beatmapSetInfo = new BeatmapSetInfo { OnlineID = TrackedItem.OnlineID }; - realmSubscription = realmContextFactory.RegisterForNotifications(realm => realm.All().Where(s => s.OnlineID == TrackedItem.OnlineID && !s.DeletePending), (items, changes, ___) => + realmSubscription = realm.RegisterForNotifications(realm => realm.All().Where(s => s.OnlineID == TrackedItem.OnlineID && !s.DeletePending), (items, changes, ___) => { if (items.Any()) Schedule(() => UpdateState(DownloadState.LocallyAvailable)); diff --git a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs index 28c41bec33..c562695ac1 100644 --- a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs @@ -30,7 +30,7 @@ namespace osu.Game.Online.Rooms protected override bool RequiresChildrenUpdate => true; [Resolved] - private RealmContextFactory realmContextFactory { get; set; } = null!; + private RealmAccess realm { get; set; } = null!; /// /// The availability state of the currently selected playlist item. @@ -78,7 +78,7 @@ namespace osu.Game.Online.Rooms // handles changes to hash that didn't occur from the import process (ie. a user editing the beatmap in the editor, somehow). realmSubscription?.Dispose(); - realmSubscription = realmContextFactory.RegisterForNotifications(realm => filteredBeatmaps(), (items, changes, ___) => + realmSubscription = realm.RegisterForNotifications(realm => filteredBeatmaps(), (items, changes, ___) => { if (changes == null) return; @@ -128,9 +128,9 @@ namespace osu.Game.Online.Rooms int onlineId = SelectedItem.Value.Beatmap.Value.OnlineID; string checksum = SelectedItem.Value.Beatmap.Value.MD5Hash; - return realmContextFactory.Context - .All() - .Filter("OnlineID == $0 && MD5Hash == $1 && BeatmapSet.DeletePending == false", onlineId, checksum); + return realm.Realm + .All() + .Filter("OnlineID == $0 && MD5Hash == $1 && BeatmapSet.DeletePending == false", onlineId, checksum); } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Online/ScoreDownloadTracker.cs b/osu.Game/Online/ScoreDownloadTracker.cs index 64ceee9fe6..81dfc811a4 100644 --- a/osu.Game/Online/ScoreDownloadTracker.cs +++ b/osu.Game/Online/ScoreDownloadTracker.cs @@ -23,7 +23,7 @@ namespace osu.Game.Online private IDisposable? realmSubscription; [Resolved] - private RealmContextFactory realmContextFactory { get; set; } = null!; + private RealmAccess realm { get; set; } = null!; public ScoreDownloadTracker(ScoreInfo trackedItem) : base(trackedItem) @@ -47,7 +47,7 @@ namespace osu.Game.Online Downloader.DownloadBegan += downloadBegan; Downloader.DownloadFailed += downloadFailed; - realmSubscription = realmContextFactory.RegisterForNotifications(realm => realm.All().Where(s => ((s.OnlineID > 0 && s.OnlineID == TrackedItem.OnlineID) || s.Hash == TrackedItem.Hash) && !s.DeletePending), (items, changes, ___) => + realmSubscription = realm.RegisterForNotifications(realm => realm.All().Where(s => ((s.OnlineID > 0 && s.OnlineID == TrackedItem.OnlineID) || s.Hash == TrackedItem.Hash) && !s.DeletePending), (items, changes, ___) => { if (items.Any()) Schedule(() => UpdateState(DownloadState.LocallyAvailable)); diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 710a7be8d4..7a6a126900 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -149,7 +149,7 @@ namespace osu.Game private MultiplayerClient multiplayerClient; - private RealmContextFactory realmFactory; + private RealmAccess realm; protected override Container Content => content; @@ -192,9 +192,9 @@ namespace osu.Game if (Storage.Exists(DatabaseContextFactory.DATABASE_NAME)) dependencies.Cache(EFContextFactory = new DatabaseContextFactory(Storage)); - dependencies.Cache(realmFactory = new RealmContextFactory(Storage, "client", EFContextFactory)); + dependencies.Cache(realm = new RealmAccess(Storage, "client", EFContextFactory)); - dependencies.Cache(RulesetStore = new RulesetStore(realmFactory, Storage)); + dependencies.Cache(RulesetStore = new RulesetStore(realm, Storage)); dependencies.CacheAs(RulesetStore); // Backup is taken here rather than in EFToRealmMigrator to avoid recycling realm contexts @@ -205,7 +205,7 @@ namespace osu.Game string migration = $"before_final_migration_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}"; EFContextFactory.CreateBackup($"client.{migration}.db"); - realmFactory.CreateBackup($"client.{migration}.realm"); + realm.CreateBackup($"client.{migration}.realm"); using (var source = Storage.GetStream("collection.db")) using (var destination = Storage.GetStream($"collection.{migration}.db", FileAccess.Write, FileMode.CreateNew)) @@ -225,7 +225,7 @@ namespace osu.Game Audio.Samples.PlaybackConcurrency = SAMPLE_CONCURRENCY; - dependencies.Cache(SkinManager = new SkinManager(Storage, realmFactory, Host, Resources, Audio, Scheduler)); + dependencies.Cache(SkinManager = new SkinManager(Storage, realm, Host, Resources, Audio, Scheduler)); dependencies.CacheAs(SkinManager); EndpointConfiguration endpoints = UseDevelopmentServer ? (EndpointConfiguration)new DevelopmentEndpointConfiguration() : new ProductionEndpointConfiguration(); @@ -240,8 +240,8 @@ namespace osu.Game var defaultBeatmap = new DummyWorkingBeatmap(Audio, Textures); // ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup() - dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, realmFactory, Scheduler, Host, () => difficultyCache, LocalConfig)); - dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, realmFactory, RulesetStore, API, Audio, Resources, Host, defaultBeatmap, performOnlineLookups: true)); + dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, realm, Scheduler, Host, () => difficultyCache, LocalConfig)); + dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, realm, RulesetStore, API, Audio, Resources, Host, defaultBeatmap, performOnlineLookups: true)); dependencies.Cache(BeatmapDownloader = new BeatmapModelDownloader(BeatmapManager, API)); dependencies.Cache(ScoreDownloader = new ScoreModelDownloader(ScoreManager, API)); @@ -259,7 +259,7 @@ namespace osu.Game dependencies.Cache(scorePerformanceManager); AddInternal(scorePerformanceManager); - dependencies.CacheAs(rulesetConfigCache = new RulesetConfigCache(realmFactory, RulesetStore)); + dependencies.CacheAs(rulesetConfigCache = new RulesetConfigCache(realm, RulesetStore)); var powerStatus = CreateBatteryInfo(); if (powerStatus != null) @@ -303,7 +303,7 @@ namespace osu.Game base.Content.Add(CreateScalingContainer().WithChildren(mainContent)); - KeyBindingStore = new RealmKeyBindingStore(realmFactory, keyCombinationProvider); + KeyBindingStore = new RealmKeyBindingStore(realm, keyCombinationProvider); KeyBindingStore.Register(globalBindings, RulesetStore.AvailableRulesets); dependencies.Cache(globalBindings); @@ -405,7 +405,7 @@ namespace osu.Game Scheduler.Add(() => { - realmBlocker = realmFactory.BlockAllOperations(); + realmBlocker = realm.BlockAllOperations(); readyToRun.Set(); }, false); @@ -483,7 +483,7 @@ namespace osu.Game BeatmapManager?.Dispose(); LocalConfig?.Dispose(); - realmFactory?.Dispose(); + realm?.Dispose(); } } } diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 8df7ed9736..8450446473 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -69,7 +69,7 @@ namespace osu.Game.Overlays public DrawableTrack CurrentTrack { get; private set; } = new DrawableTrack(new TrackVirtual(1000)); [Resolved] - private RealmContextFactory realmFactory { get; set; } + private RealmAccess realm { get; set; } [BackgroundDependencyLoader] private void load() @@ -81,9 +81,9 @@ namespace osu.Game.Overlays } private IQueryable queryRealmBeatmapSets() => - realmFactory.Context - .All() - .Where(s => !s.DeletePending); + realm.Realm + .All() + .Where(s => !s.DeletePending); protected override void LoadComplete() { @@ -94,7 +94,7 @@ namespace osu.Game.Overlays foreach (var s in queryRealmBeatmapSets()) beatmapSets.Add(s.Detach()); - beatmapSubscription = realmFactory.RegisterForNotifications(realm => queryRealmBeatmapSets(), beatmapsChanged); + beatmapSubscription = realm.RegisterForNotifications(realm => queryRealmBeatmapSets(), beatmapsChanged); } private void beatmapsChanged(IRealmCollection sender, ChangeSet changes, Exception error) diff --git a/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs b/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs index c5854981e6..3b94cae171 100644 --- a/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs +++ b/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs @@ -19,7 +19,7 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings protected override LocalisableString Header => DebugSettingsStrings.MemoryHeader; [BackgroundDependencyLoader] - private void load(GameHost host, RealmContextFactory realmFactory) + private void load(GameHost host, RealmAccess realm) { SettingsButton blockAction; SettingsButton unblockAction; @@ -37,7 +37,7 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings Action = () => { // Blocking operations implicitly causes a Compact(). - using (realmFactory.BlockAllOperations()) + using (realm.BlockAllOperations()) { } } @@ -56,7 +56,7 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings { try { - var token = realmFactory.BlockAllOperations(); + var token = realm.BlockAllOperations(); blockAction.Enabled.Value = false; diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs index 60aff91301..91883e4f41 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs @@ -79,7 +79,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input } [Resolved] - private RealmContextFactory realmFactory { get; set; } + private RealmAccess realm { get; set; } [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) @@ -386,7 +386,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input private void updateStoreFromButton(KeyButton button) { - realmFactory.Run(realm => + realm.Run(realm => { var binding = realm.Find(((IHasGuidPrimaryKey)button.KeyBinding).ID); realm.Write(() => binding.KeyCombinationString = button.KeyBinding.KeyCombinationString); diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs index 5b8a52240e..922d371261 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs @@ -30,13 +30,13 @@ namespace osu.Game.Overlays.Settings.Sections.Input } [BackgroundDependencyLoader] - private void load(RealmContextFactory realmFactory) + private void load(RealmAccess realm) { string rulesetName = Ruleset?.ShortName; - var bindings = realmFactory.Run(realm => realm.All() - .Where(b => b.RulesetName == rulesetName && b.Variant == variant) - .Detach()); + var bindings = realm.Run(r => r.All() + .Where(b => b.RulesetName == rulesetName && b.Variant == variant) + .Detach()); foreach (var defaultGroup in Defaults.GroupBy(d => d.Action)) { diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index 6e5cd0da2d..af3fd5c9bf 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -47,15 +47,15 @@ namespace osu.Game.Overlays.Settings.Sections private SkinManager skins { get; set; } [Resolved] - private RealmContextFactory realmFactory { get; set; } + private RealmAccess realm { get; set; } private IDisposable realmSubscription; private IQueryable queryRealmSkins() => - realmFactory.Context.All() - .Where(s => !s.DeletePending) - .OrderByDescending(s => s.Protected) // protected skins should be at the top. - .ThenBy(s => s.Name, StringComparer.OrdinalIgnoreCase); + realm.Realm.All() + .Where(s => !s.DeletePending) + .OrderByDescending(s => s.Protected) // protected skins should be at the top. + .ThenBy(s => s.Name, StringComparer.OrdinalIgnoreCase); [BackgroundDependencyLoader(permitNulls: true)] private void load(OsuConfigManager config, [CanBeNull] SkinEditorOverlay skinEditor) @@ -83,7 +83,7 @@ namespace osu.Game.Overlays.Settings.Sections skinDropdown.Current = dropdownBindable; - realmSubscription = realmFactory.RegisterForNotifications(realm => queryRealmSkins(), (sender, changes, error) => + realmSubscription = realm.RegisterForNotifications(realm => queryRealmSkins(), (sender, changes, error) => { // The first fire of this is a bit redundant due to the call below, // but this is safest in case the subscription is restored after a context recycle. @@ -130,7 +130,7 @@ namespace osu.Game.Overlays.Settings.Sections { int protectedCount = queryRealmSkins().Count(s => s.Protected); - skinItems = queryRealmSkins().ToLive(realmFactory); + skinItems = queryRealmSkins().ToLive(realm); skinItems.Insert(protectedCount, random_skin_info); diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index 75bebfa763..c855b76680 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -80,7 +80,7 @@ namespace osu.Game.Overlays.Toolbar protected FillFlowContainer Flow; [Resolved] - private RealmContextFactory realmFactory { get; set; } + private RealmAccess realm { get; set; } protected ToolbarButton() : base(HoverSampleSet.Toolbar) @@ -207,7 +207,7 @@ namespace osu.Game.Overlays.Toolbar { if (Hotkey == null) return; - var realmKeyBinding = realmFactory.Context.All().FirstOrDefault(rkb => rkb.RulesetName == null && rkb.ActionInt == (int)Hotkey.Value); + var realmKeyBinding = realm.Realm.All().FirstOrDefault(rkb => rkb.RulesetName == null && rkb.ActionInt == (int)Hotkey.Value); if (realmKeyBinding != null) { diff --git a/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs b/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs index 60a6b70221..cfa20e0b87 100644 --- a/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs +++ b/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Configuration public abstract class RulesetConfigManager : ConfigManager, IRulesetConfigManager where TLookup : struct, Enum { - private readonly RealmContextFactory realmFactory; + private readonly RealmAccess realm; private readonly int variant; @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Configuration protected RulesetConfigManager(SettingsStore store, RulesetInfo ruleset, int? variant = null) { - realmFactory = store?.Realm; + realm = store?.Realm; rulesetName = ruleset.ShortName; @@ -37,10 +37,10 @@ namespace osu.Game.Rulesets.Configuration protected override void PerformLoad() { - if (realmFactory != null) + if (realm != null) { // As long as RulesetConfigCache exists, there is no need to subscribe to realm events. - databasedSettings = realmFactory.Context.All().Where(b => b.RulesetName == rulesetName && b.Variant == variant).ToList(); + databasedSettings = realm.Realm.All().Where(b => b.RulesetName == rulesetName && b.Variant == variant).ToList(); } } @@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Configuration pendingWrites.Clear(); } - realmFactory?.Write(realm => + realm?.Write(realm => { foreach (var c in changed) { @@ -89,7 +89,7 @@ namespace osu.Game.Rulesets.Configuration Variant = variant, }; - realmFactory?.Context.Write(() => realmFactory.Context.Add(setting)); + realm?.Realm.Write(() => realm.Realm.Add(setting)); databasedSettings.Add(setting); } diff --git a/osu.Game/Rulesets/RulesetConfigCache.cs b/osu.Game/Rulesets/RulesetConfigCache.cs index dee13e74a5..c4f1933cd8 100644 --- a/osu.Game/Rulesets/RulesetConfigCache.cs +++ b/osu.Game/Rulesets/RulesetConfigCache.cs @@ -13,14 +13,14 @@ namespace osu.Game.Rulesets { public class RulesetConfigCache : Component, IRulesetConfigCache { - private readonly RealmContextFactory realmFactory; + private readonly RealmAccess realm; private readonly RulesetStore rulesets; private readonly Dictionary configCache = new Dictionary(); - public RulesetConfigCache(RealmContextFactory realmFactory, RulesetStore rulesets) + public RulesetConfigCache(RealmAccess realm, RulesetStore rulesets) { - this.realmFactory = realmFactory; + this.realm = realm; this.rulesets = rulesets; } @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets { base.LoadComplete(); - var settingsStore = new SettingsStore(realmFactory); + var settingsStore = new SettingsStore(realm); // let's keep things simple for now and just retrieve all the required configs at startup.. foreach (var ruleset in rulesets.AvailableRulesets) diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index a9e5ff797c..606bc65599 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets { public class RulesetStore : IDisposable, IRulesetStore { - private readonly RealmContextFactory realmFactory; + private readonly RealmAccess realm; private const string ruleset_library_prefix = @"osu.Game.Rulesets"; @@ -31,9 +31,9 @@ namespace osu.Game.Rulesets private readonly List availableRulesets = new List(); - public RulesetStore(RealmContextFactory realmFactory, Storage? storage = null) + public RulesetStore(RealmAccess realm, Storage? storage = null) { - this.realmFactory = realmFactory; + this.realm = realm; // On android in release configuration assemblies are loaded from the apk directly into memory. // We cannot read assemblies from cwd, so should check loaded assemblies instead. @@ -100,7 +100,7 @@ namespace osu.Game.Rulesets private void addMissingRulesets() { - realmFactory.Write(realm => + realm.Write(realm => { var rulesets = realm.All(); diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index f895134f97..e712d170cd 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -25,21 +25,21 @@ namespace osu.Game.Scoring { public class ScoreManager : IModelManager, IModelImporter { - private readonly RealmContextFactory contextFactory; + private readonly RealmAccess realm; private readonly Scheduler scheduler; private readonly Func difficulties; private readonly OsuConfigManager configManager; private readonly ScoreModelManager scoreModelManager; - public ScoreManager(RulesetStore rulesets, Func beatmaps, Storage storage, RealmContextFactory contextFactory, Scheduler scheduler, + public ScoreManager(RulesetStore rulesets, Func beatmaps, Storage storage, RealmAccess realm, Scheduler scheduler, IIpcHost importHost = null, Func difficulties = null, OsuConfigManager configManager = null) { - this.contextFactory = contextFactory; + this.realm = realm; this.scheduler = scheduler; this.difficulties = difficulties; this.configManager = configManager; - scoreModelManager = new ScoreModelManager(rulesets, beatmaps, storage, contextFactory); + scoreModelManager = new ScoreModelManager(rulesets, beatmaps, storage, realm); } public Score GetScore(ScoreInfo score) => scoreModelManager.GetScore(score); @@ -51,7 +51,7 @@ namespace osu.Game.Scoring /// The first result for the provided query, or null if no results were found. public ScoreInfo Query(Expression> query) { - return contextFactory.Run(realm => realm.All().FirstOrDefault(query)?.Detach()); + return realm.Run(realm => realm.All().FirstOrDefault(query)?.Detach()); } /// @@ -254,7 +254,7 @@ namespace osu.Game.Scoring public void Delete([CanBeNull] Expression> filter = null, bool silent = false) { - contextFactory.Run(realm => + realm.Run(realm => { var items = realm.All() .Where(s => !s.DeletePending); diff --git a/osu.Game/Scoring/ScoreModelManager.cs b/osu.Game/Scoring/ScoreModelManager.cs index 5e560effa1..2147ff1ba1 100644 --- a/osu.Game/Scoring/ScoreModelManager.cs +++ b/osu.Game/Scoring/ScoreModelManager.cs @@ -29,8 +29,8 @@ namespace osu.Game.Scoring private readonly RulesetStore rulesets; private readonly Func beatmaps; - public ScoreModelManager(RulesetStore rulesets, Func beatmaps, Storage storage, RealmContextFactory contextFactory) - : base(storage, contextFactory) + public ScoreModelManager(RulesetStore rulesets, Func beatmaps, Storage storage, RealmAccess realm) + : base(storage, realm) { this.rulesets = rulesets; this.beatmaps = beatmaps; @@ -74,7 +74,7 @@ namespace osu.Game.Scoring public override bool IsAvailableLocally(ScoreInfo model) { - return ContextFactory.Run(realm => realm.All().Any(s => s.OnlineID == model.OnlineID)); + return Access.Run(realm => realm.All().Any(s => s.OnlineID == model.OnlineID)); } } } diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index e66ecc74e1..fceb083916 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -85,7 +85,7 @@ namespace osu.Game.Screens.Menu private BeatmapManager beatmaps { get; set; } [BackgroundDependencyLoader] - private void load(OsuConfigManager config, Framework.Game game, RealmContextFactory realmContextFactory) + private void load(OsuConfigManager config, Framework.Game game, RealmAccess realm) { // prevent user from changing beatmap while the intro is still running. beatmap = Beatmap.BeginLease(false); @@ -97,9 +97,9 @@ namespace osu.Game.Screens.Menu // if the user has requested not to play theme music, we should attempt to find a random beatmap from their collection. if (!MenuMusic.Value) { - realmContextFactory.Run(realm => + realm.Run(r => { - var usableBeatmapSets = realm.All().Where(s => !s.DeletePending && !s.Protected).AsRealmCollection(); + var usableBeatmapSets = r.All().Where(s => !s.DeletePending && !s.Protected).AsRealmCollection(); int setCount = usableBeatmapSets.Count; diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 5a6295fe74..8e0fdea0f8 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -179,24 +179,24 @@ namespace osu.Game.Screens.Select if (!loadedTestBeatmaps) { - realmFactory.Run(realm => loadBeatmapSets(getBeatmapSets(realm))); + realm.Run(realm => loadBeatmapSets(getBeatmapSets(realm))); } } [Resolved] - private RealmContextFactory realmFactory { get; set; } + private RealmAccess realm { get; set; } protected override void LoadComplete() { base.LoadComplete(); - subscriptionSets = realmFactory.RegisterForNotifications(getBeatmapSets, beatmapSetsChanged); - subscriptionBeatmaps = realmFactory.RegisterForNotifications(realm => realm.All().Where(b => !b.Hidden), beatmapsChanged); + subscriptionSets = realm.RegisterForNotifications(getBeatmapSets, beatmapSetsChanged); + subscriptionBeatmaps = realm.RegisterForNotifications(realm => realm.All().Where(b => !b.Hidden), beatmapsChanged); // Can't use main subscriptions because we can't lookup deleted indices. // https://github.com/realm/realm-dotnet/discussions/2634#discussioncomment-1605595. - subscriptionDeletedSets = realmFactory.RegisterForNotifications(realm => realm.All().Where(s => s.DeletePending && !s.Protected), deletedBeatmapSetsChanged); - subscriptionHiddenBeatmaps = realmFactory.RegisterForNotifications(realm => realm.All().Where(b => b.Hidden), beatmapsChanged); + subscriptionDeletedSets = realm.RegisterForNotifications(realm => realm.All().Where(s => s.DeletePending && !s.Protected), deletedBeatmapSetsChanged); + subscriptionHiddenBeatmaps = realm.RegisterForNotifications(realm => realm.All().Where(b => b.Hidden), beatmapsChanged); } private void deletedBeatmapSetsChanged(IRealmCollection sender, ChangeSet changes, Exception error) @@ -231,7 +231,7 @@ namespace osu.Game.Screens.Select foreach (var id in realmSets) { if (!root.BeatmapSetsByID.ContainsKey(id)) - UpdateBeatmapSet(realmFactory.Context.Find(id).Detach()); + UpdateBeatmapSet(realm.Realm.Find(id).Detach()); } foreach (var id in root.BeatmapSetsByID.Keys) diff --git a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs index c03d464ef6..021dfd06f7 100644 --- a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs +++ b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs @@ -26,7 +26,7 @@ namespace osu.Game.Screens.Select.Carousel private IBindable ruleset { get; set; } [Resolved] - private RealmContextFactory realmFactory { get; set; } + private RealmAccess realm { get; set; } [Resolved] private IAPIProvider api { get; set; } @@ -48,7 +48,7 @@ namespace osu.Game.Screens.Select.Carousel ruleset.BindValueChanged(_ => { scoreSubscription?.Dispose(); - scoreSubscription = realmFactory.RegisterForNotifications(realm => + scoreSubscription = realm.RegisterForNotifications(realm => realm.All() .Filter($"{nameof(ScoreInfo.User)}.{nameof(RealmUser.OnlineID)} == $0" + $" && {nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} == $1" diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 5f288b972b..3d262f8b97 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -29,7 +29,7 @@ namespace osu.Game.Screens.Select.Leaderboards private RulesetStore rulesets { get; set; } [Resolved] - private RealmContextFactory realmFactory { get; set; } + private RealmAccess realm { get; set; } private BeatmapInfo beatmapInfo; @@ -113,7 +113,7 @@ namespace osu.Game.Screens.Select.Leaderboards if (beatmapInfo == null) return; - scoreSubscription = realmFactory.RegisterForNotifications(realm => + scoreSubscription = realm.RegisterForNotifications(realm => realm.All() .Filter($"{nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} = $0", beatmapInfo.ID), (_, changes, ___) => @@ -150,7 +150,7 @@ namespace osu.Game.Screens.Select.Leaderboards if (Scope == BeatmapLeaderboardScope.Local) { - realmFactory.Run(realm => + realm.Run(realm => { var scores = realm.All() .AsEnumerable() diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs index 4abde56ea6..3cf9f79611 100644 --- a/osu.Game/Screens/Spectate/SpectatorScreen.cs +++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs @@ -57,7 +57,7 @@ namespace osu.Game.Screens.Spectate } [Resolved] - private RealmContextFactory realmContextFactory { get; set; } + private RealmAccess realm { get; set; } private IDisposable realmSubscription; @@ -80,7 +80,7 @@ namespace osu.Game.Screens.Spectate playingUserStates.BindTo(spectatorClient.PlayingUserStates); playingUserStates.BindCollectionChanged(onPlayingUserStatesChanged, true); - realmSubscription = realmContextFactory.RegisterForNotifications( + realmSubscription = realm.RegisterForNotifications( realm => realm.All().Where(s => !s.DeletePending), beatmapsChanged); foreach ((int id, var _) in userMap) diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index d606d94b97..3685a26e26 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -43,8 +43,8 @@ namespace osu.Game.Skinning protected Skin(SkinInfo skin, IStorageResourceProvider resources, [CanBeNull] Stream configurationStream = null) { - SkinInfo = resources?.RealmContextFactory != null - ? skin.ToLive(resources.RealmContextFactory) + SkinInfo = resources?.RealmAccess != null + ? skin.ToLive(resources.RealmAccess) // This path should only be used in some tests. : skin.ToLiveUnmanaged(); diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 82bcd3b292..66956325da 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -54,7 +54,7 @@ namespace osu.Game.Skinning }; private readonly SkinModelManager skinModelManager; - private readonly RealmContextFactory contextFactory; + private readonly RealmAccess realm; private readonly IResourceStore userFiles; @@ -68,9 +68,9 @@ namespace osu.Game.Skinning /// public Skin DefaultLegacySkin { get; } - public SkinManager(Storage storage, RealmContextFactory contextFactory, GameHost host, IResourceStore resources, AudioManager audio, Scheduler scheduler) + public SkinManager(Storage storage, RealmAccess realm, GameHost host, IResourceStore resources, AudioManager audio, Scheduler scheduler) { - this.contextFactory = contextFactory; + this.realm = realm; this.audio = audio; this.scheduler = scheduler; this.host = host; @@ -78,7 +78,7 @@ namespace osu.Game.Skinning userFiles = new StorageBackedResourceStore(storage.GetStorageForDirectory("files")); - skinModelManager = new SkinModelManager(storage, contextFactory, host, this); + skinModelManager = new SkinModelManager(storage, realm, host, this); var defaultSkins = new[] { @@ -87,7 +87,7 @@ namespace osu.Game.Skinning }; // Ensure the default entries are present. - contextFactory.Write(realm => + realm.Write(realm => { foreach (var skin in defaultSkins) { @@ -110,10 +110,10 @@ namespace osu.Game.Skinning public void SelectRandomSkin() { - contextFactory.Run(realm => + realm.Run(r => { // choose from only user skins, removing the current selection to ensure a new one is chosen. - var randomChoices = realm.All().Where(s => !s.DeletePending && s.ID != CurrentSkinInfo.Value.ID).ToArray(); + var randomChoices = r.All().Where(s => !s.DeletePending && s.ID != CurrentSkinInfo.Value.ID).ToArray(); if (randomChoices.Length == 0) { @@ -123,7 +123,7 @@ namespace osu.Game.Skinning var chosen = randomChoices.ElementAt(RNG.Next(0, randomChoices.Length)); - CurrentSkinInfo.Value = chosen.ToLive(contextFactory); + CurrentSkinInfo.Value = chosen.ToLive(realm); }); } @@ -179,7 +179,7 @@ namespace osu.Game.Skinning /// The first result for the provided query, or null if no results were found. public ILive Query(Expression> query) { - return contextFactory.Run(realm => realm.All().FirstOrDefault(query)?.ToLive(contextFactory)); + return realm.Run(r => r.All().FirstOrDefault(query)?.ToLive(realm)); } public event Action SourceChanged; @@ -234,7 +234,7 @@ namespace osu.Game.Skinning AudioManager IStorageResourceProvider.AudioManager => audio; IResourceStore IStorageResourceProvider.Resources => resources; IResourceStore IStorageResourceProvider.Files => userFiles; - RealmContextFactory IStorageResourceProvider.RealmContextFactory => contextFactory; + RealmAccess IStorageResourceProvider.RealmAccess => realm; IResourceStore IStorageResourceProvider.CreateTextureLoaderStore(IResourceStore underlyingStore) => host.CreateTextureLoaderStore(underlyingStore); #endregion @@ -289,7 +289,7 @@ namespace osu.Game.Skinning public void Delete([CanBeNull] Expression> filter = null, bool silent = false) { - contextFactory.Run(realm => + realm.Run(realm => { var items = realm.All() .Where(s => !s.Protected && !s.DeletePending); diff --git a/osu.Game/Skinning/SkinModelManager.cs b/osu.Game/Skinning/SkinModelManager.cs index a1926913a9..c93cdb17dd 100644 --- a/osu.Game/Skinning/SkinModelManager.cs +++ b/osu.Game/Skinning/SkinModelManager.cs @@ -27,8 +27,8 @@ namespace osu.Game.Skinning private readonly IStorageResourceProvider skinResources; - public SkinModelManager(Storage storage, RealmContextFactory contextFactory, GameHost host, IStorageResourceProvider skinResources) - : base(storage, contextFactory) + public SkinModelManager(Storage storage, RealmAccess realm, GameHost host, IStorageResourceProvider skinResources) + : base(storage, realm) { this.skinResources = skinResources; @@ -205,7 +205,7 @@ namespace osu.Game.Skinning private void populateMissingHashes() { - ContextFactory.Run(realm => + Access.Run(realm => { var skinsWithoutHashes = realm.All().Where(i => !i.Protected && string.IsNullOrEmpty(i.Hash)).ToArray(); diff --git a/osu.Game/Stores/BeatmapImporter.cs b/osu.Game/Stores/BeatmapImporter.cs index 61178014ef..3d241e795c 100644 --- a/osu.Game/Stores/BeatmapImporter.cs +++ b/osu.Game/Stores/BeatmapImporter.cs @@ -44,8 +44,8 @@ namespace osu.Game.Stores private readonly BeatmapOnlineLookupQueue? onlineLookupQueue; - protected BeatmapImporter(RealmContextFactory contextFactory, Storage storage, BeatmapOnlineLookupQueue? onlineLookupQueue = null) - : base(storage, contextFactory) + protected BeatmapImporter(RealmAccess realm, Storage storage, BeatmapOnlineLookupQueue? onlineLookupQueue = null) + : base(storage, realm) { this.onlineLookupQueue = onlineLookupQueue; } @@ -165,7 +165,7 @@ namespace osu.Game.Stores public override bool IsAvailableLocally(BeatmapSetInfo model) { - return ContextFactory.Run(realm => realm.All().Any(b => b.OnlineID == model.OnlineID)); + return Access.Run(realm => realm.All().Any(b => b.OnlineID == model.OnlineID)); } public override string HumanisedModelName => "beatmap"; diff --git a/osu.Game/Stores/RealmArchiveModelImporter.cs b/osu.Game/Stores/RealmArchiveModelImporter.cs index 3d8e9f2703..23a860791e 100644 --- a/osu.Game/Stores/RealmArchiveModelImporter.cs +++ b/osu.Game/Stores/RealmArchiveModelImporter.cs @@ -59,7 +59,7 @@ namespace osu.Game.Stores protected readonly RealmFileStore Files; - protected readonly RealmContextFactory ContextFactory; + protected readonly RealmAccess Access; /// /// Fired when the user requests to view the resulting import. @@ -71,11 +71,11 @@ namespace osu.Game.Stores /// public Action? PostNotification { protected get; set; } - protected RealmArchiveModelImporter(Storage storage, RealmContextFactory contextFactory) + protected RealmArchiveModelImporter(Storage storage, RealmAccess realm) { - ContextFactory = contextFactory; + Access = realm; - Files = new RealmFileStore(contextFactory, storage); + Files = new RealmFileStore(realm, storage); } /// @@ -320,7 +320,7 @@ namespace osu.Game.Stores /// An optional cancellation token. public virtual Task?> Import(TModel item, ArchiveReader? archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) { - return ContextFactory.Run(realm => + return Access.Run(realm => { cancellationToken.ThrowIfCancellationRequested(); @@ -352,7 +352,7 @@ namespace osu.Game.Stores transaction.Commit(); } - return Task.FromResult((ILive?)existing.ToLive(ContextFactory)); + return Task.FromResult((ILive?)existing.ToLive(Access)); } LogForModel(item, @"Found existing (optimised) but failed pre-check."); @@ -387,7 +387,7 @@ namespace osu.Game.Stores existing.DeletePending = false; transaction.Commit(); - return Task.FromResult((ILive?)existing.ToLive(ContextFactory)); + return Task.FromResult((ILive?)existing.ToLive(Access)); } LogForModel(item, @"Found existing but failed re-use check."); @@ -413,7 +413,7 @@ namespace osu.Game.Stores throw; } - return Task.FromResult((ILive?)item.ToLive(ContextFactory)); + return Task.FromResult((ILive?)item.ToLive(Access)); }); } diff --git a/osu.Game/Stores/RealmArchiveModelManager.cs b/osu.Game/Stores/RealmArchiveModelManager.cs index 115fbf721d..01eb90d6e8 100644 --- a/osu.Game/Stores/RealmArchiveModelManager.cs +++ b/osu.Game/Stores/RealmArchiveModelManager.cs @@ -24,10 +24,10 @@ namespace osu.Game.Stores { private readonly RealmFileStore realmFileStore; - protected RealmArchiveModelManager(Storage storage, RealmContextFactory contextFactory) - : base(storage, contextFactory) + protected RealmArchiveModelManager(Storage storage, RealmAccess realm) + : base(storage, realm) { - realmFileStore = new RealmFileStore(contextFactory, storage); + realmFileStore = new RealmFileStore(realm, storage); } public void DeleteFile(TModel item, RealmNamedFileUsage file) => @@ -45,7 +45,7 @@ namespace osu.Game.Stores // This method should be removed as soon as all the surrounding pieces support non-detached operations. if (!item.IsManaged) { - var managed = ContextFactory.Context.Find(item.ID); + var managed = Access.Context.Find(item.ID); managed.Realm.Write(() => operation(managed)); item.Files.Clear(); @@ -165,7 +165,7 @@ namespace osu.Game.Stores public bool Delete(TModel item) { - return ContextFactory.Run(realm => + return Access.Run(realm => { if (!item.IsManaged) item = realm.Find(item.ID); @@ -180,7 +180,7 @@ namespace osu.Game.Stores public void Undelete(TModel item) { - ContextFactory.Run(realm => + Access.Run(realm => { if (!item.IsManaged) item = realm.Find(item.ID); diff --git a/osu.Game/Stores/RealmFileStore.cs b/osu.Game/Stores/RealmFileStore.cs index ca371e29be..5edc1be954 100644 --- a/osu.Game/Stores/RealmFileStore.cs +++ b/osu.Game/Stores/RealmFileStore.cs @@ -24,15 +24,15 @@ namespace osu.Game.Stores [ExcludeFromDynamicCompile] public class RealmFileStore { - private readonly RealmContextFactory realmFactory; + private readonly RealmAccess realm; public readonly IResourceStore Store; public readonly Storage Storage; - public RealmFileStore(RealmContextFactory realmFactory, Storage storage) + public RealmFileStore(RealmAccess realm, Storage storage) { - this.realmFactory = realmFactory; + this.realm = realm; Storage = storage.GetStorageForDirectory(@"files"); Store = new StorageBackedResourceStore(Storage); @@ -92,7 +92,7 @@ namespace osu.Game.Stores int removedFiles = 0; // can potentially be run asynchronously, although we will need to consider operation order for disk deletion vs realm removal. - realmFactory.Write(realm => + realm.Write(realm => { // TODO: consider using a realm native query to avoid iterating all files (https://github.com/realm/realm-dotnet/issues/2659#issuecomment-927823707) var files = realm.All().ToList(); diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs index 3d6240bc98..e6528a83bd 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs @@ -77,12 +77,12 @@ namespace osu.Game.Storyboards.Drawables } [BackgroundDependencyLoader(true)] - private void load(GameplayClock clock, CancellationToken? cancellationToken, GameHost host, RealmContextFactory realmContextFactory) + private void load(GameplayClock clock, CancellationToken? cancellationToken, GameHost host, RealmAccess realm) { if (clock != null) Clock = clock; - dependencies.Cache(new TextureStore(host.CreateTextureLoaderStore(new RealmFileStore(realmContextFactory, host.Storage).Store), false, scaleAdjust: 1)); + dependencies.Cache(new TextureStore(host.CreateTextureLoaderStore(new RealmFileStore(realm, host.Storage).Store), false, scaleAdjust: 1)); foreach (var layer in Storyboard.Layers) { diff --git a/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs b/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs index 10cb210f4d..f7e154b5e7 100644 --- a/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs +++ b/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs @@ -123,7 +123,7 @@ namespace osu.Game.Tests.Beatmaps public IResourceStore Files => userSkinResourceStore; public new IResourceStore Resources => base.Resources; public IResourceStore CreateTextureLoaderStore(IResourceStore underlyingStore) => null; - RealmContextFactory IStorageResourceProvider.RealmContextFactory => null; + RealmAccess IStorageResourceProvider.RealmAccess => null; #endregion diff --git a/osu.Game/Tests/Visual/EditorTestScene.cs b/osu.Game/Tests/Visual/EditorTestScene.cs index 6cc009514d..a6cac0ec86 100644 --- a/osu.Game/Tests/Visual/EditorTestScene.cs +++ b/osu.Game/Tests/Visual/EditorTestScene.cs @@ -53,7 +53,7 @@ namespace osu.Game.Tests.Visual working = CreateWorkingBeatmap(Ruleset.Value); if (IsolateSavingFromDatabase) - Dependencies.CacheAs(testBeatmapManager = new TestBeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.CacheAs(testBeatmapManager = new TestBeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); } protected override void LoadComplete() @@ -126,14 +126,14 @@ namespace osu.Game.Tests.Visual { public WorkingBeatmap TestBeatmap; - public TestBeatmapManager(Storage storage, RealmContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore resources, GameHost host, WorkingBeatmap defaultBeatmap) - : base(storage, contextFactory, rulesets, api, audioManager, resources, host, defaultBeatmap) + public TestBeatmapManager(Storage storage, RealmAccess realm, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore resources, GameHost host, WorkingBeatmap defaultBeatmap) + : base(storage, realm, rulesets, api, audioManager, resources, host, defaultBeatmap) { } - protected override BeatmapModelManager CreateBeatmapModelManager(Storage storage, RealmContextFactory contextFactory, RulesetStore rulesets, BeatmapOnlineLookupQueue onlineLookupQueue) + protected override BeatmapModelManager CreateBeatmapModelManager(Storage storage, RealmAccess realm, RulesetStore rulesets, BeatmapOnlineLookupQueue onlineLookupQueue) { - return new TestBeatmapModelManager(storage, contextFactory, rulesets, onlineLookupQueue); + return new TestBeatmapModelManager(storage, realm, rulesets, onlineLookupQueue); } protected override WorkingBeatmapCache CreateWorkingBeatmapCache(AudioManager audioManager, IResourceStore resources, IResourceStore storage, WorkingBeatmap defaultBeatmap, GameHost host) @@ -157,8 +157,8 @@ namespace osu.Game.Tests.Visual internal class TestBeatmapModelManager : BeatmapModelManager { - public TestBeatmapModelManager(Storage storage, RealmContextFactory databaseContextFactory, RulesetStore rulesetStore, BeatmapOnlineLookupQueue beatmapOnlineLookupQueue) - : base(databaseContextFactory, storage, beatmapOnlineLookupQueue) + public TestBeatmapModelManager(Storage storage, RealmAccess databaseAccess, RulesetStore rulesetStore, BeatmapOnlineLookupQueue beatmapOnlineLookupQueue) + : base(databaseAccess, storage, beatmapOnlineLookupQueue) { } diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index da8af49158..33dd1d45b8 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -75,9 +75,9 @@ namespace osu.Game.Tests.Visual /// /// In interactive runs (ie. VisualTests) this will use the user's database if is not set to true. /// - protected RealmContextFactory ContextFactory => contextFactory.Value; + protected RealmAccess Access => contextFactory.Value; - private Lazy contextFactory; + private Lazy contextFactory; /// /// Whether a fresh storage should be initialised per test (method) run. @@ -119,7 +119,7 @@ namespace osu.Game.Tests.Visual Resources = parent.Get().Resources; - contextFactory = new Lazy(() => new RealmContextFactory(LocalStorage, "client")); + contextFactory = new Lazy(() => new RealmAccess(LocalStorage, "client")); RecycleLocalStorage(false); diff --git a/osu.Game/Tests/Visual/SkinnableTestScene.cs b/osu.Game/Tests/Visual/SkinnableTestScene.cs index a080f47d66..cd675e467b 100644 --- a/osu.Game/Tests/Visual/SkinnableTestScene.cs +++ b/osu.Game/Tests/Visual/SkinnableTestScene.cs @@ -159,7 +159,7 @@ namespace osu.Game.Tests.Visual public IResourceStore Files => null; public new IResourceStore Resources => base.Resources; public IResourceStore CreateTextureLoaderStore(IResourceStore underlyingStore) => host.CreateTextureLoaderStore(underlyingStore); - RealmContextFactory IStorageResourceProvider.RealmContextFactory => null; + RealmAccess IStorageResourceProvider.RealmAccess => null; #endregion From f30894840c709505464cc2b903f4a8870feb7fd4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Jan 2022 20:11:36 +0900 Subject: [PATCH 616/996] Update terminology to realm "instance" rather than "context" This matches the terminology used by realm themselves, which feels better. --- osu.Game.Tests/Database/FileStoreTests.cs | 8 +- .../TestSceneChangeAndUseGameplayBindings.cs | 2 +- osu.Game/Database/RealmAccess.cs | 131 ++++++++---------- osu.Game/Stores/RealmArchiveModelManager.cs | 2 +- 4 files changed, 67 insertions(+), 76 deletions(-) diff --git a/osu.Game.Tests/Database/FileStoreTests.cs b/osu.Game.Tests/Database/FileStoreTests.cs index 008aa3d833..98b0ed99b5 100644 --- a/osu.Game.Tests/Database/FileStoreTests.cs +++ b/osu.Game.Tests/Database/FileStoreTests.cs @@ -21,7 +21,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealm((realmAccess, storage) => { - var realm = realmAccess.Context; + var realm = realmAccess.Realm; var files = new RealmFileStore(realmAccess, storage); var testData = new MemoryStream(new byte[] { 0, 1, 2, 3 }); @@ -38,7 +38,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealm((realmAccess, storage) => { - var realm = realmAccess.Context; + var realm = realmAccess.Realm; var files = new RealmFileStore(realmAccess, storage); var testData = new MemoryStream(new byte[] { 0, 1, 2, 3 }); @@ -55,7 +55,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealm((realmAccess, storage) => { - var realm = realmAccess.Context; + var realm = realmAccess.Realm; var files = new RealmFileStore(realmAccess, storage); var file = realm.Write(() => files.Add(new MemoryStream(new byte[] { 0, 1, 2, 3 }), realm)); @@ -94,7 +94,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealm((realmAccess, storage) => { - var realm = realmAccess.Context; + var realm = realmAccess.Realm; var files = new RealmFileStore(realmAccess, storage); var file = realm.Write(() => files.Add(new MemoryStream(new byte[] { 0, 1, 2, 3 }), realm)); diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneChangeAndUseGameplayBindings.cs b/osu.Game.Tests/Visual/Navigation/TestSceneChangeAndUseGameplayBindings.cs index 7cb29895eb..bfcefdbbfe 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneChangeAndUseGameplayBindings.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneChangeAndUseGameplayBindings.cs @@ -76,7 +76,7 @@ namespace osu.Game.Tests.Visual.Navigation .ChildrenOfType().SingleOrDefault(); private RealmKeyBinding firstOsuRulesetKeyBindings => Game.Dependencies - .Get().Context + .Get().Realm .All() .AsEnumerable() .First(k => k.RulesetName == "osu" && k.ActionInt == 0); diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 2f397639c9..66b4edbe84 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -59,9 +59,9 @@ namespace osu.Game.Database /// /// Lock object which is held during sections, blocking context creation during blocking periods. /// - private readonly SemaphoreSlim contextCreationLock = new SemaphoreSlim(1); + private readonly SemaphoreSlim realmCreationLock = new SemaphoreSlim(1); - private readonly ThreadLocal currentThreadCanCreateContexts = new ThreadLocal(); + private readonly ThreadLocal currentThreadCanCreateRealmInstances = new ThreadLocal(); /// /// Holds a map of functions registered via and and a coinciding action which when triggered, @@ -81,35 +81,35 @@ namespace osu.Game.Database /// private readonly Dictionary, Action> notificationsResetMap = new Dictionary, Action>(); - private static readonly GlobalStatistic contexts_created = GlobalStatistics.Get(@"Realm", @"Contexts (Created)"); + private static readonly GlobalStatistic realm_instances_created = GlobalStatistics.Get(@"Realm", @"Instances (Created)"); - private readonly object contextLock = new object(); + private readonly object realmLock = new object(); - private Realm? context; + private Realm? updateRealm; - public Realm Context => ensureUpdateContext(); + public Realm Realm => ensureUpdateRealm(); - private Realm ensureUpdateContext() + private Realm ensureUpdateRealm() { if (!ThreadSafety.IsUpdateThread) - throw new InvalidOperationException(@$"Use {nameof(createContext)} when performing realm operations from a non-update thread"); + throw new InvalidOperationException(@$"Use {nameof(getRealmInstance)} when performing realm operations from a non-update thread"); - lock (contextLock) + lock (realmLock) { - if (context == null) + if (updateRealm == null) { - context = createContext(); - Logger.Log(@$"Opened realm ""{context.Config.DatabasePath}"" at version {context.Config.SchemaVersion}"); + updateRealm = getRealmInstance(); + + Logger.Log(@$"Opened realm ""{updateRealm.Config.DatabasePath}"" at version {updateRealm.Config.SchemaVersion}"); // Resubscribe any subscriptions foreach (var action in customSubscriptionsResetMap.Keys) registerSubscription(action); } - Debug.Assert(context != null); + Debug.Assert(updateRealm != null); - // creating a context will ensure our schema is up-to-date and migrated. - return context; + return updateRealm; } } @@ -118,7 +118,7 @@ namespace osu.Game.Database private static readonly ThreadLocal current_thread_subscriptions_allowed = new ThreadLocal(); /// - /// Construct a new instance of a realm context factory. + /// Construct a new instance. /// /// The game storage which will be used to create the realm backing file. /// The filename to use for the realm backing file. A ".realm" extension will be added automatically if not specified. @@ -137,7 +137,7 @@ namespace osu.Game.Database try { - // This method triggers the first `CreateContext` call, which will implicitly run realm migrations and bring the schema up-to-date. + // This method triggers the first `getRealmInstance` call, which will implicitly run realm migrations and bring the schema up-to-date. cleanupPendingDeletions(); } catch (Exception e) @@ -153,7 +153,7 @@ namespace osu.Game.Database private void cleanupPendingDeletions() { - using (var realm = createContext()) + using (var realm = getRealmInstance()) using (var transaction = realm.BeginWrite()) { var pendingDeleteScores = realm.All().Where(s => s.DeletePending); @@ -201,34 +201,28 @@ namespace osu.Game.Database /// /// Run work on realm with a return value. /// - /// - /// Handles correct context management automatically. - /// /// The work to run. /// The return type. public T Run(Func action) { if (ThreadSafety.IsUpdateThread) - return action(Context); + return action(Realm); - using (var realm = createContext()) + using (var realm = getRealmInstance()) return action(realm); } /// /// Run work on realm. /// - /// - /// Handles correct context management automatically. - /// /// The work to run. public void Run(Action action) { if (ThreadSafety.IsUpdateThread) - action(Context); + action(Realm); else { - using (var realm = createContext()) + using (var realm = getRealmInstance()) action(realm); } } @@ -236,17 +230,14 @@ namespace osu.Game.Database /// /// Write changes to realm. /// - /// - /// Handles correct context management and transaction committing automatically. - /// /// The work to run. public void Write(Action action) { if (ThreadSafety.IsUpdateThread) - Context.Write(action); + Realm.Write(action); else { - using (var realm = createContext()) + using (var realm = getRealmInstance()) realm.Write(action); } } @@ -257,10 +248,10 @@ namespace osu.Game.Database /// /// This adds osu! specific thread and managed state safety checks on top of . /// - /// In addition to the documented realm behaviour, we have the additional requirement of handling subscriptions over potential context loss. + /// In addition to the documented realm behaviour, we have the additional requirement of handling subscriptions over potential realm instance recycle. /// When this happens, callback events will be automatically fired: - /// - On context loss, a callback with an empty collection and null will be invoked. - /// - On context revival, a standard initial realm callback will arrive, with null and an up-to-date collection. + /// - On recycle start, a callback with an empty collection and null will be invoked. + /// - On recycle end, a standard initial realm callback will arrive, with null and an up-to-date collection. /// /// The to observe for changes. /// Type of the elements in the list. @@ -276,7 +267,7 @@ namespace osu.Game.Database if (!ThreadSafety.IsUpdateThread) throw new InvalidOperationException(@$"{nameof(RegisterForNotifications)} must be called from the update thread."); - lock (contextLock) + lock (realmLock) { Func action = realm => query(realm).QueryAsyncWithNotifications(callback); @@ -287,7 +278,7 @@ namespace osu.Game.Database } /// - /// Run work on realm that will be run every time the update thread realm context gets recycled. + /// Run work on realm that will be run every time the update thread realm instance gets recycled. /// /// The work to run. Return value should be an from QueryAsyncWithNotifications, or an to clean up any bindings. /// An which should be disposed to unsubscribe any inner subscription. @@ -311,7 +302,7 @@ namespace osu.Game.Database void unsubscribe() { - lock (contextLock) + lock (realmLock) { if (customSubscriptionsResetMap.TryGetValue(action, out var unsubscriptionAction)) { @@ -328,12 +319,12 @@ namespace osu.Game.Database { Debug.Assert(ThreadSafety.IsUpdateThread); - lock (contextLock) + lock (realmLock) { - // Retrieve context outside of flag update to ensure that the context is constructed, + // Retrieve realm instance outside of flag update to ensure that the instance is retrieved, // as attempting to access it inside the subscription if it's not constructed would lead to // cyclic invocations of the subscription callback. - var realm = Context; + var realm = Realm; Debug.Assert(!customSubscriptionsResetMap.TryGetValue(action, out var found) || found == null); @@ -344,12 +335,12 @@ namespace osu.Game.Database } /// - /// Unregister all subscriptions when the realm context is to be recycled. - /// Subscriptions will still remain and will be re-subscribed when the realm context returns. + /// Unregister all subscriptions when the realm instance is to be recycled. + /// Subscriptions will still remain and will be re-subscribed when the realm instance returns. /// private void unregisterAllSubscriptions() { - lock (contextLock) + lock (realmLock) { foreach (var action in notificationsResetMap.Values) action(); @@ -362,7 +353,7 @@ namespace osu.Game.Database } } - private Realm createContext() + private Realm getRealmInstance() { if (isDisposed) throw new ObjectDisposedException(nameof(RealmAccess)); @@ -371,20 +362,20 @@ namespace osu.Game.Database try { - if (!currentThreadCanCreateContexts.Value) + if (!currentThreadCanCreateRealmInstances.Value) { - contextCreationLock.Wait(); - currentThreadCanCreateContexts.Value = true; + realmCreationLock.Wait(); + currentThreadCanCreateRealmInstances.Value = true; tookSemaphoreLock = true; } else { - // the semaphore is used to handle blocking of all context creation during certain periods. - // once the semaphore has been taken by this code section, it is safe to create further contexts on the same thread. - // this can happen if a realm subscription is active and triggers a callback which has user code that calls `CreateContext`. + // the semaphore is used to handle blocking of all realm retrieval during certain periods. + // once the semaphore has been taken by this code section, it is safe to retrieve further realm instances on the same thread. + // this can happen if a realm subscription is active and triggers a callback which has user code that calls `Run`. } - contexts_created.Value++; + realm_instances_created.Value++; return Realm.GetInstance(getConfiguration()); } @@ -392,8 +383,8 @@ namespace osu.Game.Database { if (tookSemaphoreLock) { - contextCreationLock.Release(); - currentThreadCanCreateContexts.Value = false; + realmCreationLock.Release(); + currentThreadCanCreateRealmInstances.Value = false; } } } @@ -582,7 +573,7 @@ namespace osu.Game.Database } /// - /// Flush any active contexts and block any further writes. + /// Flush any active realm instances and block any further writes. /// /// /// This should be used in places we need to ensure no ongoing reads/writes are occurring with realm. @@ -598,14 +589,14 @@ namespace osu.Game.Database try { - contextCreationLock.Wait(); + realmCreationLock.Wait(); - lock (contextLock) + lock (realmLock) { - if (context == null) + if (updateRealm == null) { - // null context means the update thread has not yet retrieved its context. - // we don't need to worry about reviving the update context in this case, so don't bother with the SynchronizationContext. + // null realm means the update thread has not yet retrieved its instance. + // we don't need to worry about reviving the update instance in this case, so don't bother with the SynchronizationContext. Debug.Assert(!ThreadSafety.IsUpdateThread); } else @@ -620,8 +611,8 @@ namespace osu.Game.Database Logger.Log(@"Blocking realm operations.", LoggingTarget.Database); - context?.Dispose(); - context = null; + updateRealm?.Dispose(); + updateRealm = null; } const int sleep_length = 200; @@ -648,17 +639,17 @@ namespace osu.Game.Database } catch { - contextCreationLock.Release(); + realmCreationLock.Release(); throw; } return new InvokeOnDisposal(this, factory => { - factory.contextCreationLock.Release(); + factory.realmCreationLock.Release(); Logger.Log(@"Restoring realm operations.", LoggingTarget.Database); // Post back to the update thread to revive any subscriptions. - syncContext?.Post(_ => ensureUpdateContext(), null); + syncContext?.Post(_ => ensureUpdateRealm(), null); }); } @@ -669,16 +660,16 @@ namespace osu.Game.Database public void Dispose() { - lock (contextLock) + lock (realmLock) { - context?.Dispose(); + updateRealm?.Dispose(); } if (!isDisposed) { // intentionally block context creation indefinitely. this ensures that nothing can start consuming a new context after disposal. - contextCreationLock.Wait(); - contextCreationLock.Dispose(); + realmCreationLock.Wait(); + realmCreationLock.Dispose(); isDisposed = true; } diff --git a/osu.Game/Stores/RealmArchiveModelManager.cs b/osu.Game/Stores/RealmArchiveModelManager.cs index 01eb90d6e8..00cd1c2958 100644 --- a/osu.Game/Stores/RealmArchiveModelManager.cs +++ b/osu.Game/Stores/RealmArchiveModelManager.cs @@ -45,7 +45,7 @@ namespace osu.Game.Stores // This method should be removed as soon as all the surrounding pieces support non-detached operations. if (!item.IsManaged) { - var managed = Access.Context.Find(item.ID); + var managed = Access.Realm.Find(item.ID); managed.Realm.Write(() => operation(managed)); item.Files.Clear(); From 6f4c337a5696ef2e51c10511ceeed873cd0834b3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Jan 2022 20:52:27 +0900 Subject: [PATCH 617/996] Fix a failed `BlockAllOperations` leaving update realm in unretrieved state If the operation timed out on.. ```csharp throw new TimeoutException(@"Took too long to acquire lock"); ``` ..from an update thread, it would not restore the update context. The next call would then fail on the assert that ensures a non-null context in such cases. Can add test coverage if required. --- osu.Game/Database/RealmAccess.cs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 66b4edbe84..64063664eb 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -595,8 +595,8 @@ namespace osu.Game.Database { if (updateRealm == null) { - // null realm means the update thread has not yet retrieved its instance. - // we don't need to worry about reviving the update instance in this case, so don't bother with the SynchronizationContext. + // null context means the update thread has not yet retrieved its context. + // we don't need to worry about reviving the update context in this case, so don't bother with the SynchronizationContext. Debug.Assert(!ThreadSafety.IsUpdateThread); } else @@ -639,18 +639,19 @@ namespace osu.Game.Database } catch { - realmCreationLock.Release(); + restoreOperation(); throw; } - return new InvokeOnDisposal(this, factory => - { - factory.realmCreationLock.Release(); - Logger.Log(@"Restoring realm operations.", LoggingTarget.Database); + return new InvokeOnDisposal(restoreOperation); + void restoreOperation() + { + Logger.Log(@"Restoring realm operations.", LoggingTarget.Database); + realmCreationLock.Release(); // Post back to the update thread to revive any subscriptions. syncContext?.Post(_ => ensureUpdateRealm(), null); - }); + } } // https://github.com/realm/realm-dotnet/blob/32f4ebcc88b3e80a3b254412665340cd9f3bd6b5/Realm/Realm/Extensions/ReflectionExtensions.cs#L46 From a7c0d507cefa16343475f5aa73a23d666b6b7446 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 24 Jan 2022 20:44:25 +0100 Subject: [PATCH 618/996] Rename flashlight settings to be more accurate --- .../Mods/CatchModFlashlight.cs | 20 +++++++++---------- .../Mods/ManiaModFlashlight.cs | 20 +++++++++---------- .../Mods/OsuModFlashlight.cs | 20 +++++++++---------- .../Mods/TaikoModFlashlight.cs | 20 +++++++++---------- osu.Game/Rulesets/Mods/ModFlashlight.cs | 8 ++++---- 5 files changed, 44 insertions(+), 44 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs index d48382a9ee..e7335dcc34 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs @@ -16,15 +16,8 @@ namespace osu.Game.Rulesets.Catch.Mods { public override double ScoreMultiplier => 1.12; - [SettingSource("Change radius based on combo", "Decrease the flashlight radius as combo increases.")] - public override BindableBool ChangeRadius { get; } = new BindableBool - { - Default = true, - Value = true - }; - - [SettingSource("Initial radius", "Initial radius of the flashlight area.")] - public override BindableNumber InitialRadius { get; } = new BindableNumber + [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")] + public override BindableNumber SizeMultiplier { get; } = new BindableNumber { MinValue = 0.4f, MaxValue = 1.7f, @@ -33,9 +26,16 @@ namespace osu.Game.Rulesets.Catch.Mods Precision = 0.1f }; + [SettingSource("Change size based on combo", "Decrease the flashlight size as combo increases.")] + public override BindableBool ComboBasedSize { get; } = new BindableBool + { + Default = true, + Value = true + }; + protected virtual float DefaultFlashlightSize => 350; - public override Flashlight CreateFlashlight() => new CatchFlashlight(playfield, ChangeRadius.Value, InitialRadius.Value, DefaultFlashlightSize); + public override Flashlight CreateFlashlight() => new CatchFlashlight(playfield, ComboBasedSize.Value, SizeMultiplier.Value, DefaultFlashlightSize); private CatchPlayfield playfield; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs index eb3f60edce..f89c131fea 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs @@ -17,15 +17,8 @@ namespace osu.Game.Rulesets.Mania.Mods public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(ModHidden) }; - [SettingSource("Change radius based on combo", "Decrease the flashlight radius as combo increases.")] - public override BindableBool ChangeRadius { get; } = new BindableBool - { - Default = false, - Value = false - }; - - [SettingSource("Initial radius", "Initial radius of the flashlight area.")] - public override BindableNumber InitialRadius { get; } = new BindableNumber + [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")] + public override BindableNumber SizeMultiplier { get; } = new BindableNumber { MinValue = 0f, MaxValue = 4.5f, @@ -34,9 +27,16 @@ namespace osu.Game.Rulesets.Mania.Mods Precision = 0.1f }; + [SettingSource("Change size based on combo", "Decrease the flashlight size as combo increases.")] + public override BindableBool ComboBasedSize { get; } = new BindableBool + { + Default = false, + Value = false + }; + protected virtual float DefaultFlashlightSize => 50; - public override Flashlight CreateFlashlight() => new ManiaFlashlight(ChangeRadius.Value, InitialRadius.Value, DefaultFlashlightSize); + public override Flashlight CreateFlashlight() => new ManiaFlashlight(ComboBasedSize.Value, SizeMultiplier.Value, DefaultFlashlightSize); private class ManiaFlashlight : Flashlight { diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs index 6a9d199c54..bc915591d0 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs @@ -30,15 +30,8 @@ namespace osu.Game.Rulesets.Osu.Mods Precision = default_follow_delay, }; - [SettingSource("Change radius based on combo", "Decrease the flashlight radius as combo increases.")] - public override BindableBool ChangeRadius { get; } = new BindableBool - { - Default = true, - Value = true - }; - - [SettingSource("Initial radius", "Initial radius of the flashlight area.")] - public override BindableNumber InitialRadius { get; } = new BindableNumber + [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")] + public override BindableNumber SizeMultiplier { get; } = new BindableNumber { MinValue = 0.5f, MaxValue = 2f, @@ -47,11 +40,18 @@ namespace osu.Game.Rulesets.Osu.Mods Precision = 0.1f }; + [SettingSource("Change size based on combo", "Decrease the flashlight size as combo increases.")] + public override BindableBool ComboBasedSize { get; } = new BindableBool + { + Default = true, + Value = true + }; + protected virtual float DefaultFlashlightSize => 180; private OsuFlashlight flashlight; - public override Flashlight CreateFlashlight() => flashlight = new OsuFlashlight(ChangeRadius.Value, InitialRadius.Value, FollowDelay.Value, DefaultFlashlightSize); + public override Flashlight CreateFlashlight() => flashlight = new OsuFlashlight(ComboBasedSize.Value, SizeMultiplier.Value, FollowDelay.Value, DefaultFlashlightSize); public void ApplyToDrawableHitObject(DrawableHitObject drawable) { diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs index 8de7c859c4..48e56c8784 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs @@ -17,15 +17,8 @@ namespace osu.Game.Rulesets.Taiko.Mods { public override double ScoreMultiplier => 1.12; - [SettingSource("Change radius based on combo", "Decrease the flashlight radius as combo increases.")] - public override BindableBool ChangeRadius { get; } = new BindableBool - { - Default = true, - Value = true - }; - - [SettingSource("Initial radius", "Initial radius of the flashlight area.")] - public override BindableNumber InitialRadius { get; } = new BindableNumber + [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")] + public override BindableNumber SizeMultiplier { get; } = new BindableNumber { MinValue = 0, MaxValue = 1.66f, @@ -34,9 +27,16 @@ namespace osu.Game.Rulesets.Taiko.Mods Precision = 0.1f }; + [SettingSource("Change size based on combo", "Decrease the flashlight size as combo increases.")] + public override BindableBool ComboBasedSize { get; } = new BindableBool + { + Default = true, + Value = true + }; + protected virtual float DefaultFlashlightSize => 250; - public override Flashlight CreateFlashlight() => new TaikoFlashlight(playfield, ChangeRadius.Value, InitialRadius.Value, DefaultFlashlightSize); + public override Flashlight CreateFlashlight() => new TaikoFlashlight(playfield, ComboBasedSize.Value, SizeMultiplier.Value, DefaultFlashlightSize); private TaikoPlayfield playfield; diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index 531ee92b7a..d3998bcad4 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -33,11 +33,11 @@ namespace osu.Game.Rulesets.Mods public override ModType Type => ModType.DifficultyIncrease; public override string Description => "Restricted view area."; - [SettingSource("Change radius based on combo", "Decrease the flashlight radius as combo increases.")] - public abstract BindableBool ChangeRadius { get; } + [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")] + public abstract BindableNumber SizeMultiplier { get; } - [SettingSource("Initial radius", "Initial radius of the flashlight area.")] - public abstract BindableNumber InitialRadius { get; } + [SettingSource("Change size based on combo", "Decrease the flashlight size as combo increases.")] + public abstract BindableBool ComboBasedSize { get; } } public abstract class ModFlashlight : ModFlashlight, IApplicableToDrawableRuleset, IApplicableToScoreProcessor From 5874475dffbc5ba6d1bf620d07e026360858a3a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 24 Jan 2022 20:46:54 +0100 Subject: [PATCH 619/996] Extract `DefaultFlashlightSize` to base flashlight class --- osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs | 2 +- osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs | 2 +- osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs | 2 +- osu.Game/Rulesets/Mods/ModFlashlight.cs | 6 ++++++ 5 files changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs index e7335dcc34..4c5dcf84d2 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Catch.Mods Value = true }; - protected virtual float DefaultFlashlightSize => 350; + public override float DefaultFlashlightSize => 350; public override Flashlight CreateFlashlight() => new CatchFlashlight(playfield, ComboBasedSize.Value, SizeMultiplier.Value, DefaultFlashlightSize); diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs index f89c131fea..03be00c5db 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Mania.Mods Value = false }; - protected virtual float DefaultFlashlightSize => 50; + public override float DefaultFlashlightSize => 50; public override Flashlight CreateFlashlight() => new ManiaFlashlight(ComboBasedSize.Value, SizeMultiplier.Value, DefaultFlashlightSize); diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs index bc915591d0..f344bb017e 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs @@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Osu.Mods Value = true }; - protected virtual float DefaultFlashlightSize => 180; + public override float DefaultFlashlightSize => 180; private OsuFlashlight flashlight; diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs index 48e56c8784..576fbb65a0 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Taiko.Mods Value = true }; - protected virtual float DefaultFlashlightSize => 250; + public override float DefaultFlashlightSize => 250; public override Flashlight CreateFlashlight() => new TaikoFlashlight(playfield, ComboBasedSize.Value, SizeMultiplier.Value, DefaultFlashlightSize); diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index d3998bcad4..f2fd86d549 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -38,6 +38,12 @@ namespace osu.Game.Rulesets.Mods [SettingSource("Change size based on combo", "Decrease the flashlight size as combo increases.")] public abstract BindableBool ComboBasedSize { get; } + + /// + /// The default size of the flashlight in ruleset-appropriate dimensions. + /// and will apply their adjustments on top of this size. + /// + public abstract float DefaultFlashlightSize { get; } } public abstract class ModFlashlight : ModFlashlight, IApplicableToDrawableRuleset, IApplicableToScoreProcessor From a227af75edb70a847aac468c47371bacc4d3d49f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 24 Jan 2022 20:55:24 +0100 Subject: [PATCH 620/996] Simplify flashlight parameter passing flow --- .../Mods/CatchModFlashlight.cs | 10 +++---- .../Mods/ManiaModFlashlight.cs | 10 +++---- .../Mods/OsuModFlashlight.cs | 19 +++++------- .../Mods/TaikoModFlashlight.cs | 8 ++--- osu.Game/Rulesets/Mods/ModFlashlight.cs | 30 +++++++++---------- 5 files changed, 37 insertions(+), 40 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs index 4c5dcf84d2..6bd914de33 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Catch.Mods public override float DefaultFlashlightSize => 350; - public override Flashlight CreateFlashlight() => new CatchFlashlight(playfield, ComboBasedSize.Value, SizeMultiplier.Value, DefaultFlashlightSize); + protected override Flashlight CreateFlashlight() => new CatchFlashlight(this, playfield); private CatchPlayfield playfield; @@ -49,11 +49,11 @@ namespace osu.Game.Rulesets.Catch.Mods { private readonly CatchPlayfield playfield; - public CatchFlashlight(CatchPlayfield playfield, bool isRadiusBasedOnCombo, float initialRadius, float defaultFlashlightSize) - : base(isRadiusBasedOnCombo, initialRadius, defaultFlashlightSize) + public CatchFlashlight(CatchModFlashlight modFlashlight, CatchPlayfield playfield) + : base(modFlashlight) { this.playfield = playfield; - FlashlightSize = new Vector2(0, GetRadiusFor(0)); + FlashlightSize = new Vector2(0, GetSizeFor(0)); } protected override void Update() @@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Catch.Mods protected override void OnComboChange(ValueChangedEvent e) { - this.TransformTo(nameof(FlashlightSize), new Vector2(0, GetRadiusFor(e.NewValue)), FLASHLIGHT_FADE_DURATION); + this.TransformTo(nameof(FlashlightSize), new Vector2(0, GetSizeFor(e.NewValue)), FLASHLIGHT_FADE_DURATION); } protected override string FragmentShader => "CircularFlashlight"; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs index 03be00c5db..def3f8b274 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs @@ -36,16 +36,16 @@ namespace osu.Game.Rulesets.Mania.Mods public override float DefaultFlashlightSize => 50; - public override Flashlight CreateFlashlight() => new ManiaFlashlight(ComboBasedSize.Value, SizeMultiplier.Value, DefaultFlashlightSize); + protected override Flashlight CreateFlashlight() => new ManiaFlashlight(this); private class ManiaFlashlight : Flashlight { private readonly LayoutValue flashlightProperties = new LayoutValue(Invalidation.DrawSize); - public ManiaFlashlight(bool isRadiusBasedOnCombo, float initialRadius, float defaultFlashlightSize) - : base(isRadiusBasedOnCombo, initialRadius, defaultFlashlightSize) + public ManiaFlashlight(ManiaModFlashlight modFlashlight) + : base(modFlashlight) { - FlashlightSize = new Vector2(DrawWidth, GetRadiusFor(0)); + FlashlightSize = new Vector2(DrawWidth, GetSizeFor(0)); AddLayout(flashlightProperties); } @@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Mania.Mods protected override void OnComboChange(ValueChangedEvent e) { - this.TransformTo(nameof(FlashlightSize), new Vector2(DrawWidth, GetRadiusFor(e.NewValue)), FLASHLIGHT_FADE_DURATION); + this.TransformTo(nameof(FlashlightSize), new Vector2(DrawWidth, GetSizeFor(e.NewValue)), FLASHLIGHT_FADE_DURATION); } protected override string FragmentShader => "RectangularFlashlight"; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs index f344bb017e..b4eff57c55 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Osu.Mods private OsuFlashlight flashlight; - public override Flashlight CreateFlashlight() => flashlight = new OsuFlashlight(ComboBasedSize.Value, SizeMultiplier.Value, FollowDelay.Value, DefaultFlashlightSize); + protected override Flashlight CreateFlashlight() => flashlight = new OsuFlashlight(this); public void ApplyToDrawableHitObject(DrawableHitObject drawable) { @@ -61,17 +61,14 @@ namespace osu.Game.Rulesets.Osu.Mods private class OsuFlashlight : Flashlight, IRequireHighFrequencyMousePosition { - public double FollowDelay { private get; set; } + private readonly double followDelay; - //public float InitialRadius { private get; set; } - public bool ChangeRadius { private get; set; } - - public OsuFlashlight(bool isRadiusBasedOnCombo, float initialRadius, double followDelay, float defaultFlashlightSize) - : base(isRadiusBasedOnCombo, initialRadius, defaultFlashlightSize) + public OsuFlashlight(OsuModFlashlight modFlashlight) + : base(modFlashlight) { - FollowDelay = followDelay; + followDelay = modFlashlight.FollowDelay.Value; - FlashlightSize = new Vector2(0, GetRadiusFor(0)); + FlashlightSize = new Vector2(0, GetSizeFor(0)); } public void OnSliderTrackingChange(ValueChangedEvent e) @@ -86,14 +83,14 @@ namespace osu.Game.Rulesets.Osu.Mods var destination = e.MousePosition; FlashlightPosition = Interpolation.ValueAt( - Math.Min(Math.Abs(Clock.ElapsedFrameTime), FollowDelay), position, destination, 0, FollowDelay, Easing.Out); + Math.Min(Math.Abs(Clock.ElapsedFrameTime), followDelay), position, destination, 0, followDelay, Easing.Out); return base.OnMouseMove(e); } protected override void OnComboChange(ValueChangedEvent e) { - this.TransformTo(nameof(FlashlightSize), new Vector2(0, GetRadiusFor(e.NewValue)), FLASHLIGHT_FADE_DURATION); + this.TransformTo(nameof(FlashlightSize), new Vector2(0, GetSizeFor(e.NewValue)), FLASHLIGHT_FADE_DURATION); } protected override string FragmentShader => "CircularFlashlight"; diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs index 576fbb65a0..84444dded9 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Taiko.Mods public override float DefaultFlashlightSize => 250; - public override Flashlight CreateFlashlight() => new TaikoFlashlight(playfield, ComboBasedSize.Value, SizeMultiplier.Value, DefaultFlashlightSize); + protected override Flashlight CreateFlashlight() => new TaikoFlashlight(this, playfield); private TaikoPlayfield playfield; @@ -51,8 +51,8 @@ namespace osu.Game.Rulesets.Taiko.Mods private readonly LayoutValue flashlightProperties = new LayoutValue(Invalidation.DrawSize); private readonly TaikoPlayfield taikoPlayfield; - public TaikoFlashlight(TaikoPlayfield taikoPlayfield, bool isRadiusBasedOnCombo, float initialRadius, float defaultFlashlightSize) - : base(isRadiusBasedOnCombo, initialRadius, defaultFlashlightSize) + public TaikoFlashlight(TaikoModFlashlight modFlashlight, TaikoPlayfield taikoPlayfield) + : base(modFlashlight) { this.taikoPlayfield = taikoPlayfield; FlashlightSize = getSizeFor(0); @@ -63,7 +63,7 @@ namespace osu.Game.Rulesets.Taiko.Mods private Vector2 getSizeFor(int combo) { // Preserve flashlight size through the playfield's aspect adjustment. - return new Vector2(0, GetRadiusFor(combo) * taikoPlayfield.DrawHeight / TaikoPlayfield.DEFAULT_HEIGHT); + return new Vector2(0, GetSizeFor(combo) * taikoPlayfield.DrawHeight / TaikoPlayfield.DEFAULT_HEIGHT); } protected override void OnComboChange(ValueChangedEvent e) diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index f2fd86d549..e6487c6b29 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -88,7 +88,7 @@ namespace osu.Game.Rulesets.Mods flashlight.Breaks = drawableRuleset.Beatmap.Breaks; } - public abstract Flashlight CreateFlashlight(); + protected abstract Flashlight CreateFlashlight(); public abstract class Flashlight : Drawable { @@ -102,17 +102,15 @@ namespace osu.Game.Rulesets.Mods public List Breaks; - public readonly bool IsRadiusBasedOnCombo; + private readonly float defaultFlashlightSize; + private readonly float sizeMultiplier; + private readonly bool comboBasedSize; - public readonly float InitialRadius; - - public readonly float DefaultFlashlightSize; - - protected Flashlight(bool isRadiusBasedOnCombo, float initialRadius, float defaultFlashlightSize) + protected Flashlight(ModFlashlight modFlashlight) { - IsRadiusBasedOnCombo = isRadiusBasedOnCombo; - InitialRadius = initialRadius; - DefaultFlashlightSize = defaultFlashlightSize; + defaultFlashlightSize = modFlashlight.DefaultFlashlightSize; + sizeMultiplier = modFlashlight.SizeMultiplier.Value; + comboBasedSize = modFlashlight.ComboBasedSize.Value; } [BackgroundDependencyLoader] @@ -146,17 +144,19 @@ namespace osu.Game.Rulesets.Mods protected abstract string FragmentShader { get; } - protected float GetRadiusFor(int combo) + protected float GetSizeFor(int combo) { - if (IsRadiusBasedOnCombo) + float size = defaultFlashlightSize * sizeMultiplier; + + if (comboBasedSize) { if (combo > 200) - return InitialRadius * 0.8f * DefaultFlashlightSize; + size *= 0.8f; else if (combo > 100) - return InitialRadius * 0.9f * DefaultFlashlightSize; + size *= 0.9f; } - return InitialRadius * DefaultFlashlightSize; + return size; } private Vector2 flashlightPosition; From 4a13c93ca7e98c89ba5938c89c1e00a2856c08c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 24 Jan 2022 20:56:59 +0100 Subject: [PATCH 621/996] Disallow zero size multiplier in flashlight implementations --- osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs | 2 +- osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs index def3f8b274..60de063b24 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Mania.Mods [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")] public override BindableNumber SizeMultiplier { get; } = new BindableNumber { - MinValue = 0f, + MinValue = 0.1f, MaxValue = 4.5f, Default = 1f, Value = 1f, diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs index 84444dded9..bdb30e9ded 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Taiko.Mods [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")] public override BindableNumber SizeMultiplier { get; } = new BindableNumber { - MinValue = 0, + MinValue = 0.1f, MaxValue = 1.66f, Default = 1f, Value = 1f, From 2375420d4cbce1372fd2848b7de799bee2f281b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 24 Jan 2022 21:16:10 +0100 Subject: [PATCH 622/996] Tweak allowable ranges of size multiplier --- osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs | 4 ++-- osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs | 4 ++-- osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs index 6bd914de33..2d92c925d7 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs @@ -19,8 +19,8 @@ namespace osu.Game.Rulesets.Catch.Mods [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")] public override BindableNumber SizeMultiplier { get; } = new BindableNumber { - MinValue = 0.4f, - MaxValue = 1.7f, + MinValue = 0.5f, + MaxValue = 1.5f, Default = 1f, Value = 1f, Precision = 0.1f diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs index 60de063b24..1ee4ea12e3 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs @@ -20,8 +20,8 @@ namespace osu.Game.Rulesets.Mania.Mods [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")] public override BindableNumber SizeMultiplier { get; } = new BindableNumber { - MinValue = 0.1f, - MaxValue = 4.5f, + MinValue = 0.5f, + MaxValue = 3f, Default = 1f, Value = 1f, Precision = 0.1f diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs index bdb30e9ded..fb07c687bb 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs @@ -20,8 +20,8 @@ namespace osu.Game.Rulesets.Taiko.Mods [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")] public override BindableNumber SizeMultiplier { get; } = new BindableNumber { - MinValue = 0.1f, - MaxValue = 1.66f, + MinValue = 0.5f, + MaxValue = 1.5f, Default = 1f, Value = 1f, Precision = 0.1f From 2d34831b5fd0383c75cf1c4cdc5d49a9b256d892 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Jan 2022 12:54:38 +0900 Subject: [PATCH 623/996] Fix `TestMigration` failing due to changes in realm migration logic Fixes failures as seen at https://github.com/ppy/osu/runs/4927916031?check_suite_focus=true. --- .../NonVisual/CustomDataDirectoryTest.cs | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs index 61ef31e07e..834930a05e 100644 --- a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs +++ b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs @@ -142,19 +142,28 @@ namespace osu.Game.Tests.NonVisual Assert.That(osuStorage, Is.Not.Null); + // In the following tests, realm files are ignored as + // - in the case of checking the source, interacting with the pipe files (client.realm.note) may + // lead to unexpected behaviour. + // - in the case of checking the destination, the files may have already been recreated by the game + // as part of the standard migration flow. + foreach (string file in osuStorage.IgnoreFiles) { - // avoid touching realm files which may be a pipe and break everything. - // this is also done locally inside OsuStorage via the IgnoreFiles list. - if (file.EndsWith(".ini", StringComparison.Ordinal)) + if (!file.Contains("realm", StringComparison.Ordinal)) + { Assert.That(File.Exists(Path.Combine(originalDirectory, file))); - Assert.That(storage.Exists(file), Is.False); + Assert.That(storage.Exists(file), Is.False, () => $"{file} exists in destination when it was expected to be ignored"); + } } foreach (string dir in osuStorage.IgnoreDirectories) { - Assert.That(Directory.Exists(Path.Combine(originalDirectory, dir))); - Assert.That(storage.ExistsDirectory(dir), Is.False); + if (!dir.Contains("realm", StringComparison.Ordinal)) + { + Assert.That(Directory.Exists(Path.Combine(originalDirectory, dir))); + Assert.That(storage.Exists(dir), Is.False, () => $"{dir} exists in destination when it was expected to be ignored"); + } } Assert.That(new StreamReader(Path.Combine(originalDirectory, "storage.ini")).ReadToEnd().Contains($"FullPath = {customPath}")); From 3e5c9e843606647561eb35541459386ecf54da20 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Jan 2022 12:58:15 +0900 Subject: [PATCH 624/996] Fix cases of `Access` instead of `Realm` --- .../TestSceneOnlinePlayBeatmapAvailabilityTracker.cs | 8 ++++---- .../Visual/Background/TestSceneUserDimBackgrounds.cs | 6 +++--- .../Collections/TestSceneManageCollectionsDialog.cs | 6 +++--- .../Visual/Multiplayer/QueueModeTestScene.cs | 6 +++--- .../Multiplayer/TestSceneDrawableRoomPlaylist.cs | 6 +++--- .../Visual/Multiplayer/TestSceneMultiplayer.cs | 6 +++--- .../TestSceneMultiplayerMatchSongSelect.cs | 6 +++--- .../TestSceneMultiplayerMatchSubScreen.cs | 6 +++--- .../Multiplayer/TestSceneMultiplayerPlaylist.cs | 6 +++--- .../Multiplayer/TestSceneMultiplayerQueueList.cs | 6 +++--- .../Multiplayer/TestSceneMultiplayerReadyButton.cs | 6 +++--- .../TestSceneMultiplayerSpectateButton.cs | 6 +++--- .../Multiplayer/TestScenePlaylistsSongSelect.cs | 6 +++--- .../Visual/Multiplayer/TestSceneTeamVersus.cs | 6 +++--- .../Playlists/TestScenePlaylistsRoomCreation.cs | 6 +++--- .../Visual/SongSelect/TestSceneBeatmapLeaderboard.cs | 8 ++++---- .../Visual/SongSelect/TestSceneFilterControl.cs | 6 +++--- .../Visual/SongSelect/TestScenePlaySongSelect.cs | 6 +++--- .../Visual/SongSelect/TestSceneTopLocalRank.cs | 8 ++++---- .../UserInterface/TestSceneDeleteLocalScore.cs | 8 ++++---- osu.Game/Beatmaps/BeatmapModelManager.cs | 4 ++-- osu.Game/Scoring/ScoreModelManager.cs | 2 +- osu.Game/Skinning/SkinModelManager.cs | 2 +- osu.Game/Stores/BeatmapImporter.cs | 2 +- osu.Game/Stores/RealmArchiveModelImporter.cs | 12 ++++++------ osu.Game/Stores/RealmArchiveModelManager.cs | 6 +++--- osu.Game/Tests/Visual/EditorTestScene.cs | 2 +- osu.Game/Tests/Visual/OsuTestScene.cs | 6 +++--- 28 files changed, 82 insertions(+), 82 deletions(-) diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index e35e4d9b15..144cbe15c3 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -45,8 +45,8 @@ namespace osu.Game.Tests.Online [BackgroundDependencyLoader] private void load(AudioManager audio, GameHost host) { - Dependencies.Cache(rulesets = new RulesetStore(Access)); - Dependencies.CacheAs(beatmaps = new TestBeatmapManager(LocalStorage, Access, rulesets, API, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.CacheAs(beatmaps = new TestBeatmapManager(LocalStorage, Realm, rulesets, API, audio, Resources, host, Beatmap.Default)); Dependencies.CacheAs(beatmapDownloader = new TestBeatmapModelDownloader(beatmaps, API, host)); } @@ -60,8 +60,8 @@ namespace osu.Game.Tests.Online testBeatmapInfo = getTestBeatmapInfo(testBeatmapFile); testBeatmapSet = testBeatmapInfo.BeatmapSet; - Access.Write(r => r.RemoveAll()); - Access.Write(r => r.RemoveAll()); + Realm.Write(r => r.RemoveAll()); + Realm.Write(r => r.RemoveAll()); selectedItem.Value = new PlaylistItem { diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs index fbfa7eda6a..40e7c0a844 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs @@ -47,10 +47,10 @@ namespace osu.Game.Tests.Visual.Background [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Access)); - Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(new OsuConfigManager(LocalStorage)); - Dependencies.Cache(Access); + Dependencies.Cache(Realm); manager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); diff --git a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs index fd0645a1e9..d4c13059da 100644 --- a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs +++ b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs @@ -36,9 +36,9 @@ namespace osu.Game.Tests.Visual.Collections [BackgroundDependencyLoader] private void load(GameHost host) { - Dependencies.Cache(rulesets = new RulesetStore(Access)); - Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Access, rulesets, null, Audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(Access); + Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, rulesets, null, Audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Realm); beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs index 07b2bdcba3..4cd19b53a4 100644 --- a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs +++ b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs @@ -47,9 +47,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Access)); - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(Access); + Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Realm); } public override void SetUpSteps() diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index 0bf1d60ac5..30ac1302aa 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -43,9 +43,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Access)); - Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(Access); + Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Realm); } [Test] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 063d886729..3f151a0ae8 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -61,9 +61,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Access)); - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Access, rulesets, API, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(Access); + Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, API, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Realm); } public override void SetUpSteps() diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index 8c79c468d7..7e3c9722cf 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -42,9 +42,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Access)); - Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(Access); + Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Realm); beatmaps = new List(); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index 77e2c9c714..9d14d80d07 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -38,9 +38,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Access)); - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(Access); + Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Realm); beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs index 4270818b1a..d970ab6c34 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs @@ -33,9 +33,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Access)); - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(Access); + Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Realm); } [SetUp] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs index 56e64292c6..d83421ee3a 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs @@ -38,9 +38,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Access)); - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Access, rulesets, API, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(Access); + Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, API, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Realm); } public override void SetUpSteps() diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs index afb60b62aa..9867e5225e 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs @@ -40,9 +40,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Access)); - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(Access); + Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Realm); beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs index 79b29f0eca..42ae279667 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs @@ -41,9 +41,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Access)); - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(Access); + Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Realm); beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs index 6ed57e9899..d7a0885c95 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs @@ -34,9 +34,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Access)); - Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(Access); + Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Realm); var beatmapSet = TestResources.CreateTestBeatmapSetInfo(); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs index bd95b297d4..73c67d26d9 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs @@ -42,9 +42,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Access)); - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(Access); + Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Realm); } public override void SetUpSteps() diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs index 716e3a535d..d397b37d05 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs @@ -40,9 +40,9 @@ namespace osu.Game.Tests.Visual.Playlists [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Access)); - Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Access, rulesets, API, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(Access); + Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, API, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Realm); } [SetUpSteps] diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 2f5594379b..5e977d075d 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -42,10 +42,10 @@ namespace osu.Game.Tests.Visual.SongSelect { var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - dependencies.Cache(rulesetStore = new RulesetStore(Access)); - dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Access, rulesetStore, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default)); - dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, Access, Scheduler)); - Dependencies.Cache(Access); + dependencies.Cache(rulesetStore = new RulesetStore(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(Realm); return dependencies; } diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs index 4868a4a075..b384061531 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs @@ -36,9 +36,9 @@ namespace osu.Game.Tests.Visual.SongSelect [BackgroundDependencyLoader] private void load(GameHost host) { - Dependencies.Cache(rulesets = new RulesetStore(Access)); - Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Access, rulesets, null, Audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(Access); + Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, rulesets, null, Audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Realm); beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index d8a39dda01..0dc4556ed9 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -47,9 +47,9 @@ namespace osu.Game.Tests.Visual.SongSelect { // These DI caches are required to ensure for interactive runs this test scene doesn't nuke all user beatmaps in the local install. // At a point we have isolated interactive test runs enough, this can likely be removed. - Dependencies.Cache(rulesets = new RulesetStore(Access)); - Dependencies.Cache(Access); - Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, defaultBeatmap = Beatmap.Default)); + Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(Realm); + Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, defaultBeatmap = Beatmap.Default)); Dependencies.Cache(music = new MusicController()); diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs index a0657ffdf6..8e5f76a2eb 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs @@ -28,10 +28,10 @@ namespace osu.Game.Tests.Visual.SongSelect [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Access)); - Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(scoreManager = new ScoreManager(rulesets, () => beatmapManager, LocalStorage, Access, Scheduler)); - Dependencies.Cache(Access); + Dependencies.Cache(rulesets = new RulesetStore(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(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 2278d3f8bf..94ce85ef38 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -87,10 +87,10 @@ namespace osu.Game.Tests.Visual.UserInterface { var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - dependencies.Cache(rulesetStore = new RulesetStore(Access)); - dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Access, rulesetStore, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default)); - dependencies.Cache(scoreManager = new ScoreManager(dependencies.Get(), () => beatmapManager, LocalStorage, Access, Scheduler)); - Dependencies.Cache(Access); + dependencies.Cache(rulesetStore = new RulesetStore(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(Realm); var imported = beatmapManager.Import(new ImportTask(TestResources.GetQuickTestBeatmapForImport())).GetResultSafely(); diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs index 167d77d6f6..e8104f2ecb 100644 --- a/osu.Game/Beatmaps/BeatmapModelManager.cs +++ b/osu.Game/Beatmaps/BeatmapModelManager.cs @@ -98,12 +98,12 @@ namespace osu.Game.Beatmaps /// The first result for the provided query, or null if no results were found. public BeatmapInfo? QueryBeatmap(Expression> query) { - return Access.Run(realm => realm.All().FirstOrDefault(query)?.Detach()); + return Realm.Run(realm => realm.All().FirstOrDefault(query)?.Detach()); } public void Update(BeatmapSetInfo item) { - Access.Write(realm => + Realm.Write(realm => { var existing = realm.Find(item.ID); item.CopyChangesToRealm(existing); diff --git a/osu.Game/Scoring/ScoreModelManager.cs b/osu.Game/Scoring/ScoreModelManager.cs index 2147ff1ba1..59102360f9 100644 --- a/osu.Game/Scoring/ScoreModelManager.cs +++ b/osu.Game/Scoring/ScoreModelManager.cs @@ -74,7 +74,7 @@ namespace osu.Game.Scoring public override bool IsAvailableLocally(ScoreInfo model) { - return Access.Run(realm => realm.All().Any(s => s.OnlineID == model.OnlineID)); + return Realm.Run(realm => realm.All().Any(s => s.OnlineID == model.OnlineID)); } } } diff --git a/osu.Game/Skinning/SkinModelManager.cs b/osu.Game/Skinning/SkinModelManager.cs index c93cdb17dd..0af31100a9 100644 --- a/osu.Game/Skinning/SkinModelManager.cs +++ b/osu.Game/Skinning/SkinModelManager.cs @@ -205,7 +205,7 @@ namespace osu.Game.Skinning private void populateMissingHashes() { - Access.Run(realm => + Realm.Run(realm => { var skinsWithoutHashes = realm.All().Where(i => !i.Protected && string.IsNullOrEmpty(i.Hash)).ToArray(); diff --git a/osu.Game/Stores/BeatmapImporter.cs b/osu.Game/Stores/BeatmapImporter.cs index 3d241e795c..a6f20c8d4f 100644 --- a/osu.Game/Stores/BeatmapImporter.cs +++ b/osu.Game/Stores/BeatmapImporter.cs @@ -165,7 +165,7 @@ namespace osu.Game.Stores public override bool IsAvailableLocally(BeatmapSetInfo model) { - return Access.Run(realm => realm.All().Any(b => b.OnlineID == model.OnlineID)); + return Realm.Run(realm => realm.All().Any(b => b.OnlineID == model.OnlineID)); } public override string HumanisedModelName => "beatmap"; diff --git a/osu.Game/Stores/RealmArchiveModelImporter.cs b/osu.Game/Stores/RealmArchiveModelImporter.cs index 23a860791e..d9ca3f50a3 100644 --- a/osu.Game/Stores/RealmArchiveModelImporter.cs +++ b/osu.Game/Stores/RealmArchiveModelImporter.cs @@ -59,7 +59,7 @@ namespace osu.Game.Stores protected readonly RealmFileStore Files; - protected readonly RealmAccess Access; + protected readonly RealmAccess Realm; /// /// Fired when the user requests to view the resulting import. @@ -73,7 +73,7 @@ namespace osu.Game.Stores protected RealmArchiveModelImporter(Storage storage, RealmAccess realm) { - Access = realm; + Realm = realm; Files = new RealmFileStore(realm, storage); } @@ -320,7 +320,7 @@ namespace osu.Game.Stores /// An optional cancellation token. public virtual Task?> Import(TModel item, ArchiveReader? archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) { - return Access.Run(realm => + return Realm.Run(realm => { cancellationToken.ThrowIfCancellationRequested(); @@ -352,7 +352,7 @@ namespace osu.Game.Stores transaction.Commit(); } - return Task.FromResult((ILive?)existing.ToLive(Access)); + return Task.FromResult((ILive?)existing.ToLive(Realm)); } LogForModel(item, @"Found existing (optimised) but failed pre-check."); @@ -387,7 +387,7 @@ namespace osu.Game.Stores existing.DeletePending = false; transaction.Commit(); - return Task.FromResult((ILive?)existing.ToLive(Access)); + return Task.FromResult((ILive?)existing.ToLive(Realm)); } LogForModel(item, @"Found existing but failed re-use check."); @@ -413,7 +413,7 @@ namespace osu.Game.Stores throw; } - return Task.FromResult((ILive?)item.ToLive(Access)); + return Task.FromResult((ILive?)item.ToLive(Realm)); }); } diff --git a/osu.Game/Stores/RealmArchiveModelManager.cs b/osu.Game/Stores/RealmArchiveModelManager.cs index 00cd1c2958..57e51b79aa 100644 --- a/osu.Game/Stores/RealmArchiveModelManager.cs +++ b/osu.Game/Stores/RealmArchiveModelManager.cs @@ -45,7 +45,7 @@ namespace osu.Game.Stores // This method should be removed as soon as all the surrounding pieces support non-detached operations. if (!item.IsManaged) { - var managed = Access.Realm.Find(item.ID); + var managed = Realm.Realm.Find(item.ID); managed.Realm.Write(() => operation(managed)); item.Files.Clear(); @@ -165,7 +165,7 @@ namespace osu.Game.Stores public bool Delete(TModel item) { - return Access.Run(realm => + return Realm.Run(realm => { if (!item.IsManaged) item = realm.Find(item.ID); @@ -180,7 +180,7 @@ namespace osu.Game.Stores public void Undelete(TModel item) { - Access.Run(realm => + Realm.Run(realm => { if (!item.IsManaged) item = realm.Find(item.ID); diff --git a/osu.Game/Tests/Visual/EditorTestScene.cs b/osu.Game/Tests/Visual/EditorTestScene.cs index a6cac0ec86..6f751b1736 100644 --- a/osu.Game/Tests/Visual/EditorTestScene.cs +++ b/osu.Game/Tests/Visual/EditorTestScene.cs @@ -53,7 +53,7 @@ namespace osu.Game.Tests.Visual working = CreateWorkingBeatmap(Ruleset.Value); if (IsolateSavingFromDatabase) - Dependencies.CacheAs(testBeatmapManager = new TestBeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.CacheAs(testBeatmapManager = new TestBeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); } protected override void LoadComplete() diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 33dd1d45b8..42e96f80ca 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -75,9 +75,9 @@ namespace osu.Game.Tests.Visual /// /// In interactive runs (ie. VisualTests) this will use the user's database if is not set to true. /// - protected RealmAccess Access => contextFactory.Value; + protected RealmAccess Realm => realm.Value; - private Lazy contextFactory; + private Lazy realm; /// /// Whether a fresh storage should be initialised per test (method) run. @@ -119,7 +119,7 @@ namespace osu.Game.Tests.Visual Resources = parent.Get().Resources; - contextFactory = new Lazy(() => new RealmAccess(LocalStorage, "client")); + realm = new Lazy(() => new RealmAccess(LocalStorage, "client")); RecycleLocalStorage(false); From e23b10e6a5a91ba1d7fd1f746846043c73c0427c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Jan 2022 13:04:05 +0900 Subject: [PATCH 625/996] Update remaining cases of clashing variable name in `realm.Run(realm..` --- osu.Game.Benchmarks/BenchmarkRealmReads.cs | 10 ++-- .../Database/TestRealmKeyBindingStore.cs | 4 +- .../Visual/Ranking/TestSceneResultsScreen.cs | 8 ++-- .../TestSceneDeleteLocalScore.cs | 4 +- osu.Game/Beatmaps/BeatmapManager.cs | 34 +++++++------- osu.Game/Database/EFToRealmMigrator.cs | 46 +++++++++---------- osu.Game/Database/RealmLive.cs | 9 ++-- osu.Game/Input/RealmKeyBindingStore.cs | 10 ++-- .../Settings/Sections/Input/KeyBindingRow.cs | 6 +-- osu.Game/Scoring/ScoreManager.cs | 8 ++-- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- .../Select/Leaderboards/BeatmapLeaderboard.cs | 10 ++-- osu.Game/Skinning/SkinManager.cs | 6 +-- 13 files changed, 77 insertions(+), 80 deletions(-) diff --git a/osu.Game.Benchmarks/BenchmarkRealmReads.cs b/osu.Game.Benchmarks/BenchmarkRealmReads.cs index 412f86bd1c..bf9467700c 100644 --- a/osu.Game.Benchmarks/BenchmarkRealmReads.cs +++ b/osu.Game.Benchmarks/BenchmarkRealmReads.cs @@ -29,7 +29,7 @@ namespace osu.Game.Benchmarks realm = new RealmAccess(storage, "client"); - realm.Run(realm => + realm.Run(r => { realm.Write(c => c.Add(TestResources.CreateTestBeatmapSetInfo(rulesets: new[] { new OsuRuleset().RulesetInfo }))); }); @@ -41,9 +41,9 @@ namespace osu.Game.Benchmarks [Benchmark] public void BenchmarkDirectPropertyRead() { - realm.Run(realm => + realm.Run(r => { - var beatmapSet = realm.All().First(); + var beatmapSet = r.All().First(); for (int i = 0; i < ReadsPerFetch; i++) { @@ -119,9 +119,9 @@ namespace osu.Game.Benchmarks [Benchmark] public void BenchmarkDetachedPropertyRead() { - realm.Run(realm => + realm.Run(r => { - var beatmapSet = realm.All().First().Detach(); + var beatmapSet = r.All().First().Detach(); for (int i = 0; i < ReadsPerFetch; i++) { diff --git a/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs index 4b8816f142..7b2d2a1278 100644 --- a/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs +++ b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs @@ -76,9 +76,9 @@ namespace osu.Game.Tests.Database private int queryCount(GlobalAction? match = null) { - return realm.Run(realm => + return realm.Run(r => { - var results = realm.All(); + var results = r.All(); if (match.HasValue) results = results.Where(k => k.ActionInt == (int)match.Value); return results.Count(); diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index 13b4af5223..988f429ff5 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs @@ -42,11 +42,11 @@ namespace osu.Game.Tests.Visual.Ranking { base.LoadComplete(); - realm.Run(realm => + realm.Run(r => { - var beatmapInfo = realm.All() - .Filter($"{nameof(BeatmapInfo.Ruleset)}.{nameof(RulesetInfo.OnlineID)} = $0", 0) - .FirstOrDefault(); + var beatmapInfo = r.All() + .Filter($"{nameof(BeatmapInfo.Ruleset)}.{nameof(RulesetInfo.OnlineID)} = $0", 0) + .FirstOrDefault(); if (beatmapInfo != null) Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmapInfo); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index 94ce85ef38..55031a9699 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -122,10 +122,10 @@ namespace osu.Game.Tests.Visual.UserInterface [SetUp] public void Setup() => Schedule(() => { - realm.Run(realm => + realm.Run(r => { // Due to soft deletions, we can re-use deleted scores between test runs - scoreManager.Undelete(realm.All().Where(s => s.DeletePending).ToList()); + scoreManager.Undelete(r.All().Where(s => s.DeletePending).ToList()); }); leaderboard.Scores = null; diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index ddc1d054cc..4488301fe5 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -119,12 +119,12 @@ namespace osu.Game.Beatmaps /// The beatmap difficulty to hide. public void Hide(BeatmapInfo beatmapInfo) { - realm.Run(realm => + realm.Run(r => { - using (var transaction = realm.BeginWrite()) + using (var transaction = r.BeginWrite()) { if (!beatmapInfo.IsManaged) - beatmapInfo = realm.Find(beatmapInfo.ID); + beatmapInfo = r.Find(beatmapInfo.ID); beatmapInfo.Hidden = true; transaction.Commit(); @@ -138,12 +138,12 @@ namespace osu.Game.Beatmaps /// The beatmap difficulty to restore. public void Restore(BeatmapInfo beatmapInfo) { - realm.Run(realm => + realm.Run(r => { - using (var transaction = realm.BeginWrite()) + using (var transaction = r.BeginWrite()) { if (!beatmapInfo.IsManaged) - beatmapInfo = realm.Find(beatmapInfo.ID); + beatmapInfo = r.Find(beatmapInfo.ID); beatmapInfo.Hidden = false; transaction.Commit(); @@ -153,11 +153,11 @@ namespace osu.Game.Beatmaps public void RestoreAll() { - realm.Run(realm => + realm.Run(r => { - using (var transaction = realm.BeginWrite()) + using (var transaction = r.BeginWrite()) { - foreach (var beatmap in realm.All().Where(b => b.Hidden)) + foreach (var beatmap in r.All().Where(b => b.Hidden)) beatmap.Hidden = false; transaction.Commit(); @@ -171,10 +171,10 @@ namespace osu.Game.Beatmaps /// A list of available . public List GetAllUsableBeatmapSets() { - return realm.Run(realm => + return realm.Run(r => { - realm.Refresh(); - return realm.All().Where(b => !b.DeletePending).Detach(); + r.Refresh(); + return r.All().Where(b => !b.DeletePending).Detach(); }); } @@ -240,9 +240,9 @@ namespace osu.Game.Beatmaps public void Delete(Expression>? filter = null, bool silent = false) { - realm.Run(realm => + realm.Run(r => { - var items = realm.All().Where(s => !s.DeletePending && !s.Protected); + var items = r.All().Where(s => !s.DeletePending && !s.Protected); if (filter != null) items = items.Where(filter); @@ -253,7 +253,7 @@ namespace osu.Game.Beatmaps public void UndeleteAll() { - realm.Run(realm => beatmapModelManager.Undelete(realm.All().Where(s => s.DeletePending).ToList())); + realm.Run(r => beatmapModelManager.Undelete(r.All().Where(s => s.DeletePending).ToList())); } public void Undelete(List items, bool silent = false) @@ -312,9 +312,9 @@ namespace osu.Game.Beatmaps // If we seem to be missing files, now is a good time to re-fetch. if (importedBeatmap?.BeatmapSet?.Files.Count == 0) { - realm.Run(realm => + realm.Run(r => { - var refetch = realm.Find(importedBeatmap.ID)?.Detach(); + var refetch = r.Find(importedBeatmap.ID)?.Detach(); if (refetch != null) importedBeatmap = refetch; diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index a0787e81e6..349452afce 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -158,11 +158,11 @@ namespace osu.Game.Database int count = existingBeatmapSets.Count(); - realm.Run(realm => + realm.Run(r => { log($"Found {count} beatmaps in EF"); - var transaction = realm.BeginWrite(); + var transaction = r.BeginWrite(); int written = 0; try @@ -172,7 +172,7 @@ namespace osu.Game.Database if (++written % 1000 == 0) { transaction.Commit(); - transaction = realm.BeginWrite(); + transaction = r.BeginWrite(); log($"Migrated {written}/{count} beatmaps..."); } @@ -186,11 +186,11 @@ namespace osu.Game.Database Protected = beatmapSet.Protected, }; - migrateFiles(beatmapSet, realm, realmBeatmapSet); + migrateFiles(beatmapSet, r, realmBeatmapSet); foreach (var beatmap in beatmapSet.Beatmaps) { - var ruleset = realm.Find(beatmap.RulesetInfo.ShortName); + var ruleset = r.Find(beatmap.RulesetInfo.ShortName); var metadata = getBestMetadata(beatmap.Metadata, beatmapSet.Metadata); var realmBeatmap = new BeatmapInfo(ruleset, new BeatmapDifficulty(beatmap.BaseDifficulty), metadata) @@ -225,7 +225,7 @@ namespace osu.Game.Database realmBeatmapSet.Beatmaps.Add(realmBeatmap); } - realm.Add(realmBeatmapSet); + r.Add(realmBeatmapSet); } } finally @@ -280,11 +280,11 @@ namespace osu.Game.Database int count = existingScores.Count(); - realm.Run(realm => + realm.Run(r => { log($"Found {count} scores in EF"); - var transaction = realm.BeginWrite(); + var transaction = r.BeginWrite(); int written = 0; try @@ -294,12 +294,12 @@ namespace osu.Game.Database if (++written % 1000 == 0) { transaction.Commit(); - transaction = realm.BeginWrite(); + transaction = r.BeginWrite(); log($"Migrated {written}/{count} scores..."); } - var beatmap = realm.All().First(b => b.Hash == score.BeatmapInfo.Hash); - var ruleset = realm.Find(score.Ruleset.ShortName); + var beatmap = r.All().First(b => b.Hash == score.BeatmapInfo.Hash); + var ruleset = r.Find(score.Ruleset.ShortName); var user = new RealmUser { OnlineID = score.User.OnlineID, @@ -329,9 +329,9 @@ namespace osu.Game.Database APIMods = score.APIMods, }; - migrateFiles(score, realm, realmScore); + migrateFiles(score, r, realmScore); - realm.Add(realmScore); + r.Add(realmScore); } } finally @@ -369,13 +369,13 @@ namespace osu.Game.Database break; } - realm.Run(realm => + realm.Run(r => { - using (var transaction = realm.BeginWrite()) + using (var transaction = r.BeginWrite()) { // only migrate data if the realm database is empty. - // note that this cannot be written as: `realm.All().All(s => s.Protected)`, because realm does not support `.All()`. - if (!realm.All().Any(s => !s.Protected)) + // note that this cannot be written as: `r.All().All(s => s.Protected)`, because realm does not support `.All()`. + if (!r.All().Any(s => !s.Protected)) { log($"Migrating {existingSkins.Count} skins"); @@ -390,9 +390,9 @@ namespace osu.Game.Database InstantiationInfo = skin.InstantiationInfo, }; - migrateFiles(skin, realm, realmSkin); + migrateFiles(skin, r, realmSkin); - realm.Add(realmSkin); + r.Add(realmSkin); if (skin.ID == userSkinInt) userSkinChoice.Value = realmSkin.ID.ToString(); @@ -428,12 +428,12 @@ namespace osu.Game.Database log("Beginning settings migration to realm"); - realm.Run(realm => + realm.Run(r => { - using (var transaction = realm.BeginWrite()) + using (var transaction = r.BeginWrite()) { // only migrate data if the realm database is empty. - if (!realm.All().Any()) + if (!r.All().Any()) { log($"Migrating {existingSettings.Count} settings"); @@ -447,7 +447,7 @@ namespace osu.Game.Database if (string.IsNullOrEmpty(shortName)) continue; - realm.Add(new RealmRulesetSetting + r.Add(new RealmRulesetSetting { Key = dkb.Key, Value = dkb.StringValue, diff --git a/osu.Game/Database/RealmLive.cs b/osu.Game/Database/RealmLive.cs index 29159fd5be..f06ba1eff9 100644 --- a/osu.Game/Database/RealmLive.cs +++ b/osu.Game/Database/RealmLive.cs @@ -51,10 +51,7 @@ namespace osu.Game.Database return; } - realm.Run(realm => - { - perform(retrieveFromID(realm, ID)); - }); + realm.Run(r => perform(retrieveFromID(r, ID))); } /// @@ -66,9 +63,9 @@ namespace osu.Game.Database if (!IsManaged) return perform(data); - return realm.Run(realm => + return realm.Run(r => { - var returnData = perform(retrieveFromID(realm, ID)); + var returnData = perform(retrieveFromID(r, ID)); if (returnData is RealmObjectBase realmObject && realmObject.IsManaged) throw new InvalidOperationException(@$"Managed realm objects should not exit the scope of {nameof(PerformRead)}."); diff --git a/osu.Game/Input/RealmKeyBindingStore.cs b/osu.Game/Input/RealmKeyBindingStore.cs index cccd42a9aa..20971ffca5 100644 --- a/osu.Game/Input/RealmKeyBindingStore.cs +++ b/osu.Game/Input/RealmKeyBindingStore.cs @@ -56,21 +56,21 @@ namespace osu.Game.Input /// The rulesets to populate defaults from. public void Register(KeyBindingContainer container, IEnumerable rulesets) { - realm.Run(realm => + realm.Run(r => { - using (var transaction = realm.BeginWrite()) + using (var transaction = r.BeginWrite()) { // intentionally flattened to a list rather than querying against the IQueryable, as nullable fields being queried against aren't indexed. // this is much faster as a result. - var existingBindings = realm.All().ToList(); + var existingBindings = r.All().ToList(); - insertDefaults(realm, existingBindings, container.DefaultKeyBindings); + insertDefaults(r, existingBindings, container.DefaultKeyBindings); foreach (var ruleset in rulesets) { var instance = ruleset.CreateInstance(); foreach (int variant in instance.AvailableVariants) - insertDefaults(realm, existingBindings, instance.GetDefaultKeyBindings(variant), ruleset.ShortName, variant); + insertDefaults(r, existingBindings, instance.GetDefaultKeyBindings(variant), ruleset.ShortName, variant); } transaction.Commit(); diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs index 91883e4f41..2405618917 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs @@ -386,10 +386,10 @@ namespace osu.Game.Overlays.Settings.Sections.Input private void updateStoreFromButton(KeyButton button) { - realm.Run(realm => + realm.Run(r => { - var binding = realm.Find(((IHasGuidPrimaryKey)button.KeyBinding).ID); - realm.Write(() => binding.KeyCombinationString = button.KeyBinding.KeyCombinationString); + var binding = r.Find(((IHasGuidPrimaryKey)button.KeyBinding).ID); + r.Write(() => binding.KeyCombinationString = button.KeyBinding.KeyCombinationString); }); } diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index e712d170cd..bb26bd3b04 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -51,7 +51,7 @@ namespace osu.Game.Scoring /// The first result for the provided query, or null if no results were found. public ScoreInfo Query(Expression> query) { - return realm.Run(realm => realm.All().FirstOrDefault(query)?.Detach()); + return realm.Run(r => r.All().FirstOrDefault(query)?.Detach()); } /// @@ -254,10 +254,10 @@ namespace osu.Game.Scoring public void Delete([CanBeNull] Expression> filter = null, bool silent = false) { - realm.Run(realm => + realm.Run(r => { - var items = realm.All() - .Where(s => !s.DeletePending); + var items = r.All() + .Where(s => !s.DeletePending); if (filter != null) items = items.Where(filter); diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 8e0fdea0f8..b02abd2f7a 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -179,7 +179,7 @@ namespace osu.Game.Screens.Select if (!loadedTestBeatmaps) { - realm.Run(realm => loadBeatmapSets(getBeatmapSets(realm))); + realm.Run(r => loadBeatmapSets(getBeatmapSets(r))); } } diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 3d262f8b97..b53aa6f43c 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -150,12 +150,12 @@ namespace osu.Game.Screens.Select.Leaderboards if (Scope == BeatmapLeaderboardScope.Local) { - realm.Run(realm => + realm.Run(r => { - var scores = realm.All() - .AsEnumerable() - // TODO: update to use a realm filter directly (or at least figure out the beatmap part to reduce scope). - .Where(s => !s.DeletePending && s.BeatmapInfo.ID == fetchBeatmapInfo.ID && s.Ruleset.OnlineID == ruleset.Value.ID); + var scores = r.All() + .AsEnumerable() + // TODO: update to use a realm filter directly (or at least figure out the beatmap part to reduce scope). + .Where(s => !s.DeletePending && s.BeatmapInfo.ID == fetchBeatmapInfo.ID && s.Ruleset.OnlineID == ruleset.Value.ID); if (filterMods && !mods.Value.Any()) { diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 66956325da..22fb8ce86f 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -289,10 +289,10 @@ namespace osu.Game.Skinning public void Delete([CanBeNull] Expression> filter = null, bool silent = false) { - realm.Run(realm => + realm.Run(r => { - var items = realm.All() - .Where(s => !s.Protected && !s.DeletePending); + var items = r.All() + .Where(s => !s.Protected && !s.DeletePending); if (filter != null) items = items.Where(filter); From d7342880f5a099e3260695bd04741a61864ff921 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Jan 2022 13:09:47 +0900 Subject: [PATCH 626/996] Update remaining cases of clashes with `realm.Write` and `realm.RegisterForNotifications` --- .../Database/TestRealmKeyBindingStore.cs | 8 ++++---- osu.Game/Database/EFToRealmMigrator.cs | 10 +++++----- .../Input/Bindings/DatabasedKeyBindingContainer.cs | 2 +- osu.Game/Online/BeatmapDownloadTracker.cs | 2 +- .../Rooms/OnlinePlayBeatmapAvailabilityTracker.cs | 2 +- osu.Game/Online/ScoreDownloadTracker.cs | 2 +- osu.Game/Overlays/MusicController.cs | 2 +- osu.Game/Overlays/Settings/Sections/SkinSection.cs | 2 +- .../Rulesets/Configuration/RulesetConfigManager.cs | 4 ++-- osu.Game/Rulesets/RulesetStore.cs | 6 +++--- osu.Game/Screens/Select/BeatmapCarousel.cs | 6 +++--- osu.Game/Screens/Select/Carousel/TopLocalRank.cs | 14 +++++++------- .../Select/Leaderboards/BeatmapLeaderboard.cs | 6 +++--- osu.Game/Skinning/SkinManager.cs | 6 +++--- osu.Game/Stores/RealmFileStore.cs | 6 +++--- 15 files changed, 39 insertions(+), 39 deletions(-) diff --git a/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs index 7b2d2a1278..891801865f 100644 --- a/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs +++ b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs @@ -60,11 +60,11 @@ namespace osu.Game.Tests.Database KeyBindingContainer testContainer = new TestKeyBindingContainer(); // Add some excess bindings for an action which only supports 1. - realm.Write(realm => + realm.Write(r => { - realm.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.A))); - realm.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.S))); - realm.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.D))); + r.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.A))); + r.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.S))); + r.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.D))); }); Assert.That(queryCount(GlobalAction.Back), Is.EqualTo(3)); diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index 349452afce..adf91e4a41 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -101,15 +101,15 @@ namespace osu.Game.Database { using (var ef = efContextFactory.Get()) { - realm.Write(realm => + realm.Write(r => { // Before beginning, ensure realm is in an empty state. // Migrations which are half-completed could lead to issues if the user tries a second time. // Note that we only do this for beatmaps and scores since the other migrations are yonks old. - realm.RemoveAll(); - realm.RemoveAll(); - realm.RemoveAll(); - realm.RemoveAll(); + r.RemoveAll(); + r.RemoveAll(); + r.RemoveAll(); + r.RemoveAll(); }); migrateSettings(ef); diff --git a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs index d54c049c99..3e4a9759a3 100644 --- a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs +++ b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs @@ -55,7 +55,7 @@ namespace osu.Game.Input.Bindings protected override void LoadComplete() { - realmSubscription = realm.RegisterForNotifications(realm => queryRealmKeyBindings(), (sender, changes, error) => + realmSubscription = realm.RegisterForNotifications(r => queryRealmKeyBindings(), (sender, changes, error) => { // The first fire of this is a bit redundant as this is being called in base.LoadComplete, // but this is safest in case the subscription is restored after a context recycle. diff --git a/osu.Game/Online/BeatmapDownloadTracker.cs b/osu.Game/Online/BeatmapDownloadTracker.cs index f54dc30620..9f795f007a 100644 --- a/osu.Game/Online/BeatmapDownloadTracker.cs +++ b/osu.Game/Online/BeatmapDownloadTracker.cs @@ -42,7 +42,7 @@ namespace osu.Game.Online // Used to interact with manager classes that don't support interface types. Will eventually be replaced. var beatmapSetInfo = new BeatmapSetInfo { OnlineID = TrackedItem.OnlineID }; - realmSubscription = realm.RegisterForNotifications(realm => realm.All().Where(s => s.OnlineID == TrackedItem.OnlineID && !s.DeletePending), (items, changes, ___) => + realmSubscription = realm.RegisterForNotifications(r => r.All().Where(s => s.OnlineID == TrackedItem.OnlineID && !s.DeletePending), (items, changes, ___) => { if (items.Any()) Schedule(() => UpdateState(DownloadState.LocallyAvailable)); diff --git a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs index c562695ac1..c67cbade6a 100644 --- a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs @@ -78,7 +78,7 @@ namespace osu.Game.Online.Rooms // handles changes to hash that didn't occur from the import process (ie. a user editing the beatmap in the editor, somehow). realmSubscription?.Dispose(); - realmSubscription = realm.RegisterForNotifications(realm => filteredBeatmaps(), (items, changes, ___) => + realmSubscription = realm.RegisterForNotifications(r => filteredBeatmaps(), (items, changes, ___) => { if (changes == null) return; diff --git a/osu.Game/Online/ScoreDownloadTracker.cs b/osu.Game/Online/ScoreDownloadTracker.cs index 81dfc811a4..d7e31c8a59 100644 --- a/osu.Game/Online/ScoreDownloadTracker.cs +++ b/osu.Game/Online/ScoreDownloadTracker.cs @@ -47,7 +47,7 @@ namespace osu.Game.Online Downloader.DownloadBegan += downloadBegan; Downloader.DownloadFailed += downloadFailed; - realmSubscription = realm.RegisterForNotifications(realm => realm.All().Where(s => ((s.OnlineID > 0 && s.OnlineID == TrackedItem.OnlineID) || s.Hash == TrackedItem.Hash) && !s.DeletePending), (items, changes, ___) => + realmSubscription = realm.RegisterForNotifications(r => r.All().Where(s => ((s.OnlineID > 0 && s.OnlineID == TrackedItem.OnlineID) || s.Hash == TrackedItem.Hash) && !s.DeletePending), (items, changes, ___) => { if (items.Any()) Schedule(() => UpdateState(DownloadState.LocallyAvailable)); diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 0f8e390ebf..9dc55d24dd 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -79,7 +79,7 @@ namespace osu.Game.Overlays protected override void LoadComplete() { base.LoadComplete(); - beatmapSubscription = realm.RegisterForNotifications(realm => queryRealmBeatmapSets(), beatmapsChanged); + beatmapSubscription = realm.RegisterForNotifications(r => queryRealmBeatmapSets(), beatmapsChanged); } private void beatmapsChanged(IRealmCollection sender, ChangeSet changes, Exception error) diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index af3fd5c9bf..8ab296c0a8 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -83,7 +83,7 @@ namespace osu.Game.Overlays.Settings.Sections skinDropdown.Current = dropdownBindable; - realmSubscription = realm.RegisterForNotifications(realm => queryRealmSkins(), (sender, changes, error) => + realmSubscription = realm.RegisterForNotifications(r => queryRealmSkins(), (sender, changes, error) => { // The first fire of this is a bit redundant due to the call below, // but this is safest in case the subscription is restored after a context recycle. diff --git a/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs b/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs index cfa20e0b87..30bb95ba72 100644 --- a/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs +++ b/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs @@ -56,11 +56,11 @@ namespace osu.Game.Rulesets.Configuration pendingWrites.Clear(); } - realm?.Write(realm => + realm?.Write(r => { foreach (var c in changed) { - var setting = realm.All().First(s => s.RulesetName == rulesetName && s.Variant == variant && s.Key == c.ToString()); + var setting = r.All().First(s => s.RulesetName == rulesetName && s.Variant == variant && s.Key == c.ToString()); setting.Value = ConfigStore[c].ToString(); } diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index 606bc65599..9af9ace7ad 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets { public class RulesetStore : IDisposable, IRulesetStore { - private readonly RealmAccess realm; + private readonly RealmAccess realmAccess; private const string ruleset_library_prefix = @"osu.Game.Rulesets"; @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets public RulesetStore(RealmAccess realm, Storage? storage = null) { - this.realm = realm; + realmAccess = realm; // On android in release configuration assemblies are loaded from the apk directly into memory. // We cannot read assemblies from cwd, so should check loaded assemblies instead. @@ -100,7 +100,7 @@ namespace osu.Game.Rulesets private void addMissingRulesets() { - realm.Write(realm => + realmAccess.Write(realm => { var rulesets = realm.All(); diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index b02abd2f7a..dff2c598c3 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -191,12 +191,12 @@ namespace osu.Game.Screens.Select base.LoadComplete(); subscriptionSets = realm.RegisterForNotifications(getBeatmapSets, beatmapSetsChanged); - subscriptionBeatmaps = realm.RegisterForNotifications(realm => realm.All().Where(b => !b.Hidden), beatmapsChanged); + subscriptionBeatmaps = realm.RegisterForNotifications(r => r.All().Where(b => !b.Hidden), beatmapsChanged); // Can't use main subscriptions because we can't lookup deleted indices. // https://github.com/realm/realm-dotnet/discussions/2634#discussioncomment-1605595. - subscriptionDeletedSets = realm.RegisterForNotifications(realm => realm.All().Where(s => s.DeletePending && !s.Protected), deletedBeatmapSetsChanged); - subscriptionHiddenBeatmaps = realm.RegisterForNotifications(realm => realm.All().Where(b => b.Hidden), beatmapsChanged); + subscriptionDeletedSets = realm.RegisterForNotifications(r => r.All().Where(s => s.DeletePending && !s.Protected), deletedBeatmapSetsChanged); + subscriptionHiddenBeatmaps = realm.RegisterForNotifications(r => r.All().Where(b => b.Hidden), beatmapsChanged); } private void deletedBeatmapSetsChanged(IRealmCollection sender, ChangeSet changes, Exception error) diff --git a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs index 021dfd06f7..e1f9c1b508 100644 --- a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs +++ b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs @@ -48,13 +48,13 @@ namespace osu.Game.Screens.Select.Carousel ruleset.BindValueChanged(_ => { scoreSubscription?.Dispose(); - scoreSubscription = realm.RegisterForNotifications(realm => - realm.All() - .Filter($"{nameof(ScoreInfo.User)}.{nameof(RealmUser.OnlineID)} == $0" - + $" && {nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} == $1" - + $" && {nameof(ScoreInfo.Ruleset)}.{nameof(RulesetInfo.ShortName)} == $2" - + $" && {nameof(ScoreInfo.DeletePending)} == false", api.LocalUser.Value.Id, beatmapInfo.ID, ruleset.Value.ShortName) - .OrderByDescending(s => s.TotalScore), + scoreSubscription = realm.RegisterForNotifications(r => + r.All() + .Filter($"{nameof(ScoreInfo.User)}.{nameof(RealmUser.OnlineID)} == $0" + + $" && {nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} == $1" + + $" && {nameof(ScoreInfo.Ruleset)}.{nameof(RulesetInfo.ShortName)} == $2" + + $" && {nameof(ScoreInfo.DeletePending)} == false", api.LocalUser.Value.Id, beatmapInfo.ID, ruleset.Value.ShortName) + .OrderByDescending(s => s.TotalScore), (items, changes, ___) => { Rank = items.FirstOrDefault()?.Rank; diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index b53aa6f43c..f25997650b 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -113,9 +113,9 @@ namespace osu.Game.Screens.Select.Leaderboards if (beatmapInfo == null) return; - scoreSubscription = realm.RegisterForNotifications(realm => - realm.All() - .Filter($"{nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} = $0", beatmapInfo.ID), + scoreSubscription = realm.RegisterForNotifications(r => + r.All() + .Filter($"{nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} = $0", beatmapInfo.ID), (_, changes, ___) => { if (!IsOnlineScope) diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 22fb8ce86f..344acb7a0f 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -87,12 +87,12 @@ namespace osu.Game.Skinning }; // Ensure the default entries are present. - realm.Write(realm => + realm.Write(r => { foreach (var skin in defaultSkins) { - if (realm.Find(skin.SkinInfo.ID) == null) - realm.Add(skin.SkinInfo.Value); + if (r.Find(skin.SkinInfo.ID) == null) + r.Add(skin.SkinInfo.Value); } }); diff --git a/osu.Game/Stores/RealmFileStore.cs b/osu.Game/Stores/RealmFileStore.cs index 5edc1be954..b5dd3d64e4 100644 --- a/osu.Game/Stores/RealmFileStore.cs +++ b/osu.Game/Stores/RealmFileStore.cs @@ -92,10 +92,10 @@ namespace osu.Game.Stores int removedFiles = 0; // can potentially be run asynchronously, although we will need to consider operation order for disk deletion vs realm removal. - realm.Write(realm => + realm.Write(r => { // TODO: consider using a realm native query to avoid iterating all files (https://github.com/realm/realm-dotnet/issues/2659#issuecomment-927823707) - var files = realm.All().ToList(); + var files = r.All().ToList(); foreach (var file in files) { @@ -108,7 +108,7 @@ namespace osu.Game.Stores { removedFiles++; Storage.Delete(file.GetStoragePath()); - realm.Remove(file); + r.Remove(file); } catch (Exception e) { From bbcc149e2e7cc01045374b7b25be85983658aa0e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Jan 2022 13:42:41 +0900 Subject: [PATCH 627/996] During import if files are found to be missing, ensure they are restored This is one step closer to sanity in terms of physical files. As per the comment I have left in place, we really should be checking file sizes or hashes, but to keep things simple and fast I've opted to just cover the "missing file" scenario for now. Ran into this when testing against a foreign `client.realm` by: - Noticing a beatmap doesn't load - Deleting said beatmap - Downloading via beatmap overlay - Beatmap is restored but still doesn't work Note that I've kept the logic where this will undelete an existing import rather than create one from fresh, as I think that is beneficial to the user (ie. it will still keep any linked scores on restore). --- .../Database/BeatmapImporterTests.cs | 32 +++++++++++++++++++ osu.Game/Stores/RealmArchiveModelImporter.cs | 10 ++++-- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index 227314cffd..abab8ae3c6 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -600,6 +600,38 @@ namespace osu.Game.Tests.Database }); } + [Test] + public void TestImportThenReimportAfterMissingFiles() + { + RunTestWithRealmAsync(async (realmFactory, storage) => + { + using var importer = new BeatmapModelManager(realmFactory, storage); + using var store = new RulesetStore(realmFactory, storage); + + var imported = await LoadOszIntoStore(importer, realmFactory.Context); + + deleteBeatmapSet(imported, realmFactory.Context); + + Assert.IsTrue(imported.DeletePending); + + // intentionally nuke all files + storage.DeleteDirectory("files"); + + Assert.That(imported.Files.All(f => !storage.GetStorageForDirectory("files").Exists(f.File.GetStoragePath()))); + + var importedSecondTime = await LoadOszIntoStore(importer, realmFactory.Context); + + // check the newly "imported" beatmap is actually just the restored previous import. since it matches hash. + Assert.IsTrue(imported.ID == importedSecondTime.ID); + Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID); + Assert.IsFalse(imported.DeletePending); + Assert.IsFalse(importedSecondTime.DeletePending); + + // check that the files now exist, even though they were deleted above. + Assert.That(importedSecondTime.Files.All(f => storage.GetStorageForDirectory("files").Exists(f.File.GetStoragePath()))); + }); + } + [Test] public void TestImportThenDeleteThenImportNonOptimisedPath() { diff --git a/osu.Game/Stores/RealmArchiveModelImporter.cs b/osu.Game/Stores/RealmArchiveModelImporter.cs index 3d8e9f2703..154135ea71 100644 --- a/osu.Game/Stores/RealmArchiveModelImporter.cs +++ b/osu.Game/Stores/RealmArchiveModelImporter.cs @@ -342,7 +342,8 @@ namespace osu.Game.Stores // note that this should really be checking filesizes on disk (of existing files) for some degree of sanity. // or alternatively doing a faster hash check. either of these require database changes and reprocessing of existing files. if (CanSkipImport(existing, item) && - getFilenames(existing.Files).SequenceEqual(getShortenedFilenames(archive).Select(p => p.shortened).OrderBy(f => f))) + getFilenames(existing.Files).SequenceEqual(getShortenedFilenames(archive).Select(p => p.shortened).OrderBy(f => f)) && + checkAllFilesExist(existing)) { LogForModel(item, @$"Found existing (optimised) {HumanisedModelName} for {item} (ID {existing.ID}) – skipping import."); @@ -459,7 +460,6 @@ namespace osu.Game.Stores if (!(prefix.EndsWith('/') || prefix.EndsWith('\\'))) prefix = string.Empty; - // import files to manager foreach (string file in reader.Filenames) yield return (file, file.Substring(prefix.Length).ToStandardisedPath()); } @@ -519,7 +519,11 @@ namespace osu.Game.Stores // for the best or worst, we copy and import files of a new import before checking whether // it is a duplicate. so to check if anything has changed, we can just compare all File IDs. getIDs(existing.Files).SequenceEqual(getIDs(import.Files)) && - getFilenames(existing.Files).SequenceEqual(getFilenames(import.Files)); + getFilenames(existing.Files).SequenceEqual(getFilenames(import.Files)) && + checkAllFilesExist(existing); + + private bool checkAllFilesExist(TModel model) => + model.Files.All(f => Files.Storage.Exists(f.File.GetStoragePath())); /// /// Whether this specified path should be removed after successful import. From df1297ade6fcdf95b9025252aae63cbebcd0ba0b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Jan 2022 13:50:39 +0900 Subject: [PATCH 628/996] 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 b296c114e9..4e5b9fdbb1 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 758575e74a..af5d8a5920 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 5925581e28..2bcdea61b3 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -60,7 +60,7 @@ - + @@ -83,7 +83,7 @@ - + From dd2caea694f0e2782c4c34b8dba3c165ef9136da Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Jan 2022 19:10:14 +0900 Subject: [PATCH 629/996] Update `GetSuitableHost` usages in line with new `HostOptions` --- osu.Desktop/Program.cs | 2 +- osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs | 3 ++- .../NonVisual/CustomTourneyDirectoryTest.cs | 5 +++-- osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs | 3 ++- osu.Game.Tournament.Tests/TournamentTestRunner.cs | 2 +- osu.Game/Tests/CleanRunHeadlessGameHost.cs | 6 +++++- osu.Game/Tests/VisualTestRunner.cs | 2 +- 7 files changed, 15 insertions(+), 8 deletions(-) diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index 7ec7d53a7e..ab9935a220 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -55,7 +55,7 @@ namespace osu.Desktop } } - using (DesktopGameHost host = Host.GetSuitableHost(gameName, true)) + using (DesktopGameHost host = Host.GetSuitableDesktopHost(gameName, new HostOptions { PortableInstallation = true })) { host.ExceptionThrown += handleException; diff --git a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs index 53e4ef07e7..ee746ba4d5 100644 --- a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs +++ b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs @@ -6,6 +6,7 @@ using System.IO; using System.Text; using System.Threading.Tasks; using NUnit.Framework; +using osu.Framework; using osu.Framework.Extensions; using osu.Framework.Platform; using osu.Framework.Testing; @@ -155,7 +156,7 @@ namespace osu.Game.Tests.Collections.IO } // Name matches the automatically chosen name from `CleanRunHeadlessGameHost` above, so we end up using the same storage location. - using (HeadlessGameHost host = new TestRunHeadlessGameHost(firstRunName)) + using (HeadlessGameHost host = new TestRunHeadlessGameHost(firstRunName, null)) { try { diff --git a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs index fc5d3b652f..36d0c6f0f6 100644 --- a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using NUnit.Framework; +using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Platform; using osu.Framework.Testing; @@ -37,7 +38,7 @@ namespace osu.Game.Tournament.Tests.NonVisual [Test] public void TestCustomDirectory() { - using (HeadlessGameHost host = new TestRunHeadlessGameHost(nameof(TestCustomDirectory))) // don't use clean run as we are writing a config file. + using (HeadlessGameHost host = new TestRunHeadlessGameHost(nameof(TestCustomDirectory), null)) // don't use clean run as we are writing a config file. { string osuDesktopStorage = Path.Combine(host.UserStoragePaths.First(), nameof(TestCustomDirectory)); const string custom_tournament = "custom"; @@ -68,7 +69,7 @@ namespace osu.Game.Tournament.Tests.NonVisual [Test] public void TestMigration() { - using (HeadlessGameHost host = new TestRunHeadlessGameHost(nameof(TestMigration))) // don't use clean run as we are writing test files for migration. + using (HeadlessGameHost host = new TestRunHeadlessGameHost(nameof(TestMigration), null)) // don't use clean run as we are writing test files for migration. { string osuRoot = Path.Combine(host.UserStoragePaths.First(), nameof(TestMigration)); string configFile = Path.Combine(osuRoot, "tournament.ini"); diff --git a/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs b/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs index 03252e3be6..11b686e3e8 100644 --- a/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using NUnit.Framework; +using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Platform; using osu.Framework.Testing; @@ -19,7 +20,7 @@ namespace osu.Game.Tournament.Tests.NonVisual public void CheckIPCLocation() { // don't use clean run because files are being written before osu! launches. - using (var host = new TestRunHeadlessGameHost(nameof(CheckIPCLocation))) + using (var host = new TestRunHeadlessGameHost(nameof(CheckIPCLocation), null)) { string basePath = Path.Combine(host.UserStoragePaths.First(), nameof(CheckIPCLocation)); diff --git a/osu.Game.Tournament.Tests/TournamentTestRunner.cs b/osu.Game.Tournament.Tests/TournamentTestRunner.cs index 1f63f7c545..23da37a6b7 100644 --- a/osu.Game.Tournament.Tests/TournamentTestRunner.cs +++ b/osu.Game.Tournament.Tests/TournamentTestRunner.cs @@ -12,7 +12,7 @@ namespace osu.Game.Tournament.Tests [STAThread] public static int Main(string[] args) { - using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true)) + using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu", new HostOptions { PortableInstallation = true })) { host.Run(new TournamentTestBrowser()); return 0; diff --git a/osu.Game/Tests/CleanRunHeadlessGameHost.cs b/osu.Game/Tests/CleanRunHeadlessGameHost.cs index 754c9044e8..bdb171c528 100644 --- a/osu.Game/Tests/CleanRunHeadlessGameHost.cs +++ b/osu.Game/Tests/CleanRunHeadlessGameHost.cs @@ -3,6 +3,7 @@ using System; using System.Runtime.CompilerServices; +using osu.Framework; using osu.Framework.Testing; namespace osu.Game.Tests @@ -20,7 +21,10 @@ namespace osu.Game.Tests /// 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 = @"") - : base($"{callingMethodName}-{Guid.NewGuid()}", bindIPC, realtime, bypassCleanup: bypassCleanup) + : base($"{callingMethodName}-{Guid.NewGuid()}", new HostOptions + { + BindIPC = bindIPC, + }, bypassCleanup: bypassCleanup, realtime: realtime) { } diff --git a/osu.Game/Tests/VisualTestRunner.cs b/osu.Game/Tests/VisualTestRunner.cs index d63b3d48b2..973801099e 100644 --- a/osu.Game/Tests/VisualTestRunner.cs +++ b/osu.Game/Tests/VisualTestRunner.cs @@ -12,7 +12,7 @@ namespace osu.Game.Tests [STAThread] public static int Main(string[] args) { - using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true)) + using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu", new HostOptions { PortableInstallation = true, })) { host.Run(new OsuTestBrowser()); return 0; From a5c76a9647c56b1677bd9105c93220ee7ad59441 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Jan 2022 13:56:47 +0900 Subject: [PATCH 630/996] Fix a few more cases of "context" terminology usage --- osu.Game/Database/RealmAccess.cs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 66b4edbe84..65e13cf542 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -30,7 +30,7 @@ using Realms.Exceptions; namespace osu.Game.Database { /// - /// A factory which provides both the main (update thread bound) realm context and creates contexts for async usage. + /// A factory which provides safe access to the realm storage backend. /// public class RealmAccess : IDisposable { @@ -57,9 +57,9 @@ namespace osu.Game.Database private const int schema_version = 13; /// - /// Lock object which is held during sections, blocking context creation during blocking periods. + /// Lock object which is held during sections, blocking realm retrieval during blocking periods. /// - private readonly SemaphoreSlim realmCreationLock = new SemaphoreSlim(1); + private readonly SemaphoreSlim realmRetrievalLock = new SemaphoreSlim(1); private readonly ThreadLocal currentThreadCanCreateRealmInstances = new ThreadLocal(); @@ -76,7 +76,7 @@ namespace osu.Game.Database /// /// Holds a map of functions registered via and a coinciding action which when triggered, - /// fires a change set event with an empty collection. This is used to inform subscribers when a realm context goes away, and ensure they don't use invalidated + /// fires a change set event with an empty collection. This is used to inform subscribers when the main realm instance gets recycled, and ensure they don't use invalidated /// managed realm objects from a previous firing. /// private readonly Dictionary, Action> notificationsResetMap = new Dictionary, Action>(); @@ -364,7 +364,7 @@ namespace osu.Game.Database { if (!currentThreadCanCreateRealmInstances.Value) { - realmCreationLock.Wait(); + realmRetrievalLock.Wait(); currentThreadCanCreateRealmInstances.Value = true; tookSemaphoreLock = true; } @@ -383,7 +383,7 @@ namespace osu.Game.Database { if (tookSemaphoreLock) { - realmCreationLock.Release(); + realmRetrievalLock.Release(); currentThreadCanCreateRealmInstances.Value = false; } } @@ -589,7 +589,7 @@ namespace osu.Game.Database try { - realmCreationLock.Wait(); + realmRetrievalLock.Wait(); lock (realmLock) { @@ -639,13 +639,13 @@ namespace osu.Game.Database } catch { - realmCreationLock.Release(); + realmRetrievalLock.Release(); throw; } return new InvokeOnDisposal(this, factory => { - factory.realmCreationLock.Release(); + factory.realmRetrievalLock.Release(); Logger.Log(@"Restoring realm operations.", LoggingTarget.Database); // Post back to the update thread to revive any subscriptions. @@ -667,9 +667,9 @@ namespace osu.Game.Database if (!isDisposed) { - // intentionally block context creation indefinitely. this ensures that nothing can start consuming a new context after disposal. - realmCreationLock.Wait(); - realmCreationLock.Dispose(); + // intentionally block realm retrieval indefinitely. this ensures that nothing can start consuming a new instance after disposal. + realmRetrievalLock.Wait(); + realmRetrievalLock.Dispose(); isDisposed = true; } From 8116806db3fa6b06e96d1f38eac77964a96435a2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Jan 2022 14:00:58 +0900 Subject: [PATCH 631/996] Add test coverage of calling `BlockAllOperations` a second time after timeout --- osu.Game.Tests/Database/GeneralUsageTests.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Tests/Database/GeneralUsageTests.cs b/osu.Game.Tests/Database/GeneralUsageTests.cs index 2533c832e6..8262ef18d4 100644 --- a/osu.Game.Tests/Database/GeneralUsageTests.cs +++ b/osu.Game.Tests/Database/GeneralUsageTests.cs @@ -95,6 +95,11 @@ namespace osu.Game.Tests.Database }); stopThreadedUsage.Set(); + + // Ensure we can block a second time after the usage has ended. + using (realm.BlockAllOperations()) + { + } }); } } From 86c844bd58317c2fc5d978a6142ea3ac153794a3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Jan 2022 14:15:29 +0900 Subject: [PATCH 632/996] Update remaining usages of `GetSuitableHost` in template projects --- .../osu.Game.Rulesets.EmptyFreeform.Tests/VisualTestRunner.cs | 2 +- .../osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs | 2 +- .../osu.Game.Rulesets.EmptyScrolling.Tests/VisualTestRunner.cs | 2 +- .../osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs | 2 +- osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs | 1 - .../NonVisual/CustomTourneyDirectoryTest.cs | 1 - osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs | 1 - 7 files changed, 4 insertions(+), 7 deletions(-) diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/VisualTestRunner.cs b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/VisualTestRunner.cs index 4f810ce17f..03ee7c9204 100644 --- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/VisualTestRunner.cs +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/VisualTestRunner.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.EmptyFreeform.Tests [STAThread] public static int Main(string[] args) { - using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true)) + using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu", new HostOptions { BindIPC = true })) { host.Run(new OsuTestBrowser()); return 0; diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs index fd6bd9b714..55c0cf6a3b 100644 --- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Pippidon.Tests [STAThread] public static int Main(string[] args) { - using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true)) + using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu", new HostOptions { BindIPC = true })) { host.Run(new OsuTestBrowser()); return 0; diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/VisualTestRunner.cs b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/VisualTestRunner.cs index 65cfb2bff4..b45505678c 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/VisualTestRunner.cs +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/VisualTestRunner.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.EmptyScrolling.Tests [STAThread] public static int Main(string[] args) { - using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true)) + using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu", new HostOptions { BindIPC = true })) { host.Run(new OsuTestBrowser()); return 0; diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs index fd6bd9b714..55c0cf6a3b 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Pippidon.Tests [STAThread] public static int Main(string[] args) { - using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true)) + using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu", new HostOptions { BindIPC = true })) { host.Run(new OsuTestBrowser()); return 0; diff --git a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs index ee746ba4d5..5cbede54f5 100644 --- a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs +++ b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs @@ -6,7 +6,6 @@ using System.IO; using System.Text; using System.Threading.Tasks; using NUnit.Framework; -using osu.Framework; using osu.Framework.Extensions; using osu.Framework.Platform; using osu.Framework.Testing; diff --git a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs index 36d0c6f0f6..26fb03bed4 100644 --- a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs @@ -4,7 +4,6 @@ using System.IO; using System.Linq; using NUnit.Framework; -using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Platform; using osu.Framework.Testing; diff --git a/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs b/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs index 11b686e3e8..80cc9be5c1 100644 --- a/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs @@ -4,7 +4,6 @@ using System.IO; using System.Linq; using NUnit.Framework; -using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Platform; using osu.Framework.Testing; From 5872dabf604f55a1feec63039404780a9d48133b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Jan 2022 14:16:15 +0900 Subject: [PATCH 633/996] Fix incorrect flag to options conversion --- osu.Desktop/Program.cs | 2 +- osu.Game.Tournament.Tests/TournamentTestRunner.cs | 2 +- osu.Game/Tests/VisualTestRunner.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index ab9935a220..b944068e78 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -55,7 +55,7 @@ namespace osu.Desktop } } - using (DesktopGameHost host = Host.GetSuitableDesktopHost(gameName, new HostOptions { PortableInstallation = true })) + using (DesktopGameHost host = Host.GetSuitableDesktopHost(gameName, new HostOptions { BindIPC = true })) { host.ExceptionThrown += handleException; diff --git a/osu.Game.Tournament.Tests/TournamentTestRunner.cs b/osu.Game.Tournament.Tests/TournamentTestRunner.cs index 23da37a6b7..229ab41a1e 100644 --- a/osu.Game.Tournament.Tests/TournamentTestRunner.cs +++ b/osu.Game.Tournament.Tests/TournamentTestRunner.cs @@ -12,7 +12,7 @@ namespace osu.Game.Tournament.Tests [STAThread] public static int Main(string[] args) { - using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu", new HostOptions { PortableInstallation = true })) + using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu", new HostOptions { BindIPC = true })) { host.Run(new TournamentTestBrowser()); return 0; diff --git a/osu.Game/Tests/VisualTestRunner.cs b/osu.Game/Tests/VisualTestRunner.cs index 973801099e..6aa75ec147 100644 --- a/osu.Game/Tests/VisualTestRunner.cs +++ b/osu.Game/Tests/VisualTestRunner.cs @@ -12,7 +12,7 @@ namespace osu.Game.Tests [STAThread] public static int Main(string[] args) { - using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu", new HostOptions { PortableInstallation = true, })) + using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu", new HostOptions { BindIPC = true, })) { host.Run(new OsuTestBrowser()); return 0; From 56d7d814658d1d202e405f237cf74d8a386d078b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Jan 2022 14:47:21 +0900 Subject: [PATCH 634/996] Fix broken test due to `SynchronizationContext` never running expected work --- osu.Game.Tests/Database/GeneralUsageTests.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game.Tests/Database/GeneralUsageTests.cs b/osu.Game.Tests/Database/GeneralUsageTests.cs index 8262ef18d4..dc0d42595b 100644 --- a/osu.Game.Tests/Database/GeneralUsageTests.cs +++ b/osu.Game.Tests/Database/GeneralUsageTests.cs @@ -87,6 +87,11 @@ namespace osu.Game.Tests.Database hasThreadedUsage.Wait(); + // Usually the host would run the synchronization context work per frame. + // For the sake of keeping this test simple (there's only one update invocation), + // let's replace it so we can ensure work is run immediately. + SynchronizationContext.SetSynchronizationContext(new ImmediateExecuteSynchronizationContext()); + Assert.Throws(() => { using (realm.BlockAllOperations()) @@ -102,5 +107,10 @@ namespace osu.Game.Tests.Database } }); } + + private class ImmediateExecuteSynchronizationContext : SynchronizationContext + { + public override void Post(SendOrPostCallback d, object? state) => d(state); + } } } From 5fb9b58c9b12c2929d07eaccfabf71528f259fe6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Jan 2022 20:23:10 +0900 Subject: [PATCH 635/996] Add tracking of total subscriptions --- osu.Game/Database/RealmAccess.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 65e13cf542..fe4c30d7dd 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -83,6 +83,8 @@ namespace osu.Game.Database private static readonly GlobalStatistic realm_instances_created = GlobalStatistics.Get(@"Realm", @"Instances (Created)"); + private static readonly GlobalStatistic total_subscriptions = GlobalStatistics.Get(@"Realm", @"Subscriptions"); + private readonly object realmLock = new object(); private Realm? updateRealm; @@ -289,6 +291,8 @@ namespace osu.Game.Database var syncContext = SynchronizationContext.Current; + total_subscriptions.Value++; + registerSubscription(action); // This token is returned to the consumer. @@ -309,6 +313,7 @@ namespace osu.Game.Database unsubscriptionAction?.Dispose(); customSubscriptionsResetMap.Remove(action); notificationsResetMap.Remove(action); + total_subscriptions.Value--; } } } From ae0fea8e2627f68f211c89a27a9bae04a651b0f3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Jan 2022 15:29:45 +0900 Subject: [PATCH 636/996] Fix compilation issues due to misnamed fild --- osu.Game.Tests/Database/BeatmapImporterTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index 8cb762ed12..b37d2be0bc 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -608,9 +608,9 @@ namespace osu.Game.Tests.Database using var importer = new BeatmapModelManager(realmFactory, storage); using var store = new RulesetStore(realmFactory, storage); - var imported = await LoadOszIntoStore(importer, realmFactory.Context); + var imported = await LoadOszIntoStore(importer, realmFactory.Realm); - deleteBeatmapSet(imported, realmFactory.Context); + deleteBeatmapSet(imported, realmFactory.Realm); Assert.IsTrue(imported.DeletePending); @@ -619,7 +619,7 @@ namespace osu.Game.Tests.Database Assert.That(imported.Files.All(f => !storage.GetStorageForDirectory("files").Exists(f.File.GetStoragePath()))); - var importedSecondTime = await LoadOszIntoStore(importer, realmFactory.Context); + var importedSecondTime = await LoadOszIntoStore(importer, realmFactory.Realm); // check the newly "imported" beatmap is actually just the restored previous import. since it matches hash. Assert.IsTrue(imported.ID == importedSecondTime.ID); From 778d2a71b484bb3220a09db15fc5fc632cb891e9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Jan 2022 15:23:51 +0900 Subject: [PATCH 637/996] Remove `Task` from the inner-most `Import` method in `RealmArchiveModelImporter` One of my pending work items for post-realm merge. The lowest-level import task is no longer asynchronous, as we don't want it to span multiple threads to allow easier interaction with realm. Removing the `Task` spec simplifies a heap of usages. Individual usages should decide whether they want to run the import asynchronously, by either using an alternative override or spooling up a thread themselves. --- .../Database/BeatmapImporterTests.cs | 4 ++-- ...eneOnlinePlayBeatmapAvailabilityTracker.cs | 10 ++++----- osu.Game.Tests/Scores/IO/ImportScoreTest.cs | 22 +++++++++---------- .../Gameplay/TestSceneReplayDownloadButton.cs | 2 +- .../Menus/TestSceneMusicActionHandling.cs | 2 +- .../TestSceneDrawableRoomPlaylist.cs | 3 +-- .../TestSceneMultiplayerMatchSongSelect.cs | 2 +- .../TestScenePlaylistsSongSelect.cs | 3 +-- .../Navigation/TestScenePresentBeatmap.cs | 3 +-- .../Navigation/TestScenePresentScore.cs | 5 ++--- .../TestScenePlaylistsRoomCreation.cs | 7 +++--- .../SongSelect/TestSceneBeatmapLeaderboard.cs | 2 +- .../TestSceneBeatmapRecommendations.cs | 3 +-- .../SongSelect/TestScenePlaySongSelect.cs | 13 +++++------ .../TestSceneDeleteLocalScore.cs | 2 +- osu.Game/Beatmaps/BeatmapManager.cs | 5 ++--- osu.Game/Database/IModelImporter.cs | 2 +- osu.Game/Scoring/ScoreManager.cs | 2 +- osu.Game/Screens/Play/Player.cs | 8 ++++--- osu.Game/Skinning/SkinManager.cs | 5 ++--- osu.Game/Stores/RealmArchiveModelImporter.cs | 14 +++++++----- 21 files changed, 57 insertions(+), 62 deletions(-) diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index b37d2be0bc..69dd2d930a 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -685,7 +685,7 @@ namespace osu.Game.Tests.Database [Test] public void TestImportWithDuplicateBeatmapIDs() { - RunTestWithRealmAsync(async (realm, storage) => + RunTestWithRealm((realm, storage) => { using var importer = new BeatmapModelManager(realm, storage); using var store = new RulesetStore(realm, storage); @@ -718,7 +718,7 @@ namespace osu.Game.Tests.Database } }; - var imported = await importer.Import(toImport); + var imported = importer.Import(toImport); Assert.NotNull(imported); Debug.Assert(imported != null); diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index 144cbe15c3..95c15367aa 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -91,7 +91,7 @@ namespace osu.Game.Tests.Online addAvailabilityCheckStep("state importing", BeatmapAvailability.Importing); AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true)); - AddUntilStep("wait for import", () => beatmaps.CurrentImportTask?.IsCompleted == true); + AddUntilStep("wait for import", () => beatmaps.CurrentImport != null); addAvailabilityCheckStep("state locally available", BeatmapAvailability.LocallyAvailable); } @@ -164,7 +164,7 @@ namespace osu.Game.Tests.Online { public TaskCompletionSource AllowImport = new TaskCompletionSource(); - public Task> CurrentImportTask { get; private set; } + public ILive CurrentImport { get; private set; } public TestBeatmapManager(Storage storage, RealmAccess realm, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore resources, GameHost host = null, WorkingBeatmap defaultBeatmap = null) : base(storage, realm, rulesets, api, audioManager, resources, host, defaultBeatmap) @@ -186,10 +186,10 @@ namespace osu.Game.Tests.Online this.testBeatmapManager = testBeatmapManager; } - public override async Task> Import(BeatmapSetInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) + public override ILive Import(BeatmapSetInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) { - await testBeatmapManager.AllowImport.Task.ConfigureAwait(false); - return await (testBeatmapManager.CurrentImportTask = base.Import(item, archive, lowPriority, cancellationToken)).ConfigureAwait(false); + testBeatmapManager.AllowImport.Task.WaitSafely(); + return (testBeatmapManager.CurrentImport = base.Import(item, archive, lowPriority, cancellationToken)); } } } diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs index dd12c94855..8de9f0a292 100644 --- a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs +++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Extensions; @@ -25,7 +24,7 @@ namespace osu.Game.Tests.Scores.IO public class ImportScoreTest : ImportTest { [Test] - public async Task TestBasicImport() + public void TestBasicImport() { using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { @@ -49,7 +48,7 @@ namespace osu.Game.Tests.Scores.IO BeatmapInfo = beatmap.Beatmaps.First() }; - var imported = await LoadScoreIntoOsu(osu, toImport); + var imported = LoadScoreIntoOsu(osu, toImport); Assert.AreEqual(toImport.Rank, imported.Rank); Assert.AreEqual(toImport.TotalScore, imported.TotalScore); @@ -67,7 +66,7 @@ namespace osu.Game.Tests.Scores.IO } [Test] - public async Task TestImportMods() + public void TestImportMods() { using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { @@ -85,7 +84,7 @@ namespace osu.Game.Tests.Scores.IO Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() }, }; - var imported = await LoadScoreIntoOsu(osu, toImport); + var imported = LoadScoreIntoOsu(osu, toImport); Assert.IsTrue(imported.Mods.Any(m => m is OsuModHardRock)); Assert.IsTrue(imported.Mods.Any(m => m is OsuModDoubleTime)); @@ -98,7 +97,7 @@ namespace osu.Game.Tests.Scores.IO } [Test] - public async Task TestImportStatistics() + public void TestImportStatistics() { using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { @@ -120,7 +119,7 @@ namespace osu.Game.Tests.Scores.IO } }; - var imported = await LoadScoreIntoOsu(osu, toImport); + var imported = LoadScoreIntoOsu(osu, toImport); Assert.AreEqual(toImport.Statistics[HitResult.Perfect], imported.Statistics[HitResult.Perfect]); Assert.AreEqual(toImport.Statistics[HitResult.Miss], imported.Statistics[HitResult.Miss]); @@ -133,7 +132,7 @@ namespace osu.Game.Tests.Scores.IO } [Test] - public async Task TestOnlineScoreIsAvailableLocally() + public void TestOnlineScoreIsAvailableLocally() { using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { @@ -143,7 +142,7 @@ namespace osu.Game.Tests.Scores.IO var beatmap = BeatmapImportHelper.LoadOszIntoOsu(osu, TestResources.GetQuickTestBeatmapForImport()).GetResultSafely(); - await LoadScoreIntoOsu(osu, new ScoreInfo + LoadScoreIntoOsu(osu, new ScoreInfo { User = new APIUser { Username = "Test user" }, BeatmapInfo = beatmap.Beatmaps.First(), @@ -168,13 +167,14 @@ namespace osu.Game.Tests.Scores.IO } } - public static async Task LoadScoreIntoOsu(OsuGameBase osu, ScoreInfo score, ArchiveReader archive = null) + public static ScoreInfo LoadScoreIntoOsu(OsuGameBase osu, ScoreInfo score, ArchiveReader archive = null) { // clone to avoid attaching the input score to realm. score = score.DeepClone(); var scoreManager = osu.Dependencies.Get(); - await scoreManager.Import(score, archive); + + scoreManager.Import(score, archive); return scoreManager.Query(_ => true); } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs index 8199389b36..a12171401a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs @@ -147,7 +147,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("state is not downloaded", () => downloadButton.State.Value == DownloadState.NotDownloaded); - AddStep("import score", () => imported = scoreManager.Import(getScoreInfo(true)).GetResultSafely()); + AddStep("import score", () => imported = scoreManager.Import(getScoreInfo(true))); AddUntilStep("state is available", () => downloadButton.State.Value == DownloadState.LocallyAvailable); diff --git a/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs b/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs index 3ebc64cd0b..10a82089b3 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs @@ -34,7 +34,7 @@ namespace osu.Game.Tests.Visual.Menus Queue<(IWorkingBeatmap working, TrackChangeDirection changeDirection)> trackChangeQueue = null; // ensure we have at least two beatmaps available to identify the direction the music controller navigated to. - AddRepeatStep("import beatmap", () => Game.BeatmapManager.Import(TestResources.CreateTestBeatmapSetInfo()).WaitSafely(), 5); + AddRepeatStep("import beatmap", () => Game.BeatmapManager.Import(TestResources.CreateTestBeatmapSetInfo()), 5); AddStep("import beatmap with track", () => { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index 30ac1302aa..92accb0cd1 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -8,7 +8,6 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; -using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Platform; @@ -158,7 +157,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Debug.Assert(beatmap.BeatmapSet != null); - AddStep("import beatmap", () => imported = manager.Import(beatmap.BeatmapSet).GetResultSafely()); + AddStep("import beatmap", () => imported = manager.Import(beatmap.BeatmapSet)); createPlaylistWithBeatmaps(() => imported.PerformRead(s => s.Beatmaps.Detach())); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index 7e3c9722cf..5465061891 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -83,7 +83,7 @@ namespace osu.Game.Tests.Visual.Multiplayer beatmapSetInfo.Beatmaps.Add(beatmap); } - manager.Import(beatmapSetInfo).WaitSafely(); + manager.Import(beatmapSetInfo); } public override void SetUpSteps() diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs index d7a0885c95..d933491ab6 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs @@ -6,7 +6,6 @@ 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.Utils; @@ -40,7 +39,7 @@ namespace osu.Game.Tests.Visual.Multiplayer var beatmapSet = TestResources.CreateTestBeatmapSetInfo(); - manager.Import(beatmapSet).WaitSafely(); + manager.Import(beatmapSet); } public override void SetUpSteps() diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs b/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs index f6c53e76c4..63226de750 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs @@ -4,7 +4,6 @@ using System; using System.Linq; using NUnit.Framework; -using osu.Framework.Extensions; using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Extensions; @@ -125,7 +124,7 @@ namespace osu.Game.Tests.Visual.Navigation Ruleset = ruleset ?? new OsuRuleset().RulesetInfo }, } - }).GetResultSafely()?.Value; + })?.Value; }); AddAssert($"import {i} succeeded", () => imported != null); diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs index 7bd8110374..7656bf79dc 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs @@ -4,7 +4,6 @@ using System; using System.Linq; using NUnit.Framework; -using osu.Framework.Extensions; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; @@ -60,7 +59,7 @@ namespace osu.Game.Tests.Visual.Navigation Ruleset = new OsuRuleset().RulesetInfo }, } - }).GetResultSafely()?.Value; + })?.Value; }); } @@ -135,7 +134,7 @@ namespace osu.Game.Tests.Visual.Navigation BeatmapInfo = beatmap.Beatmaps.First(), Ruleset = ruleset ?? new OsuRuleset().RulesetInfo, User = new GuestUser(), - }).GetResultSafely().Value; + }).Value; }); AddAssert($"import {i} succeeded", () => imported != null); diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs index d397b37d05..68225f6d64 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs @@ -8,7 +8,6 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; -using osu.Framework.Extensions; using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Testing; @@ -151,7 +150,7 @@ namespace osu.Game.Tests.Visual.Playlists Debug.Assert(modifiedBeatmap.BeatmapInfo.BeatmapSet != null); - manager.Import(modifiedBeatmap.BeatmapInfo.BeatmapSet).WaitSafely(); + manager.Import(modifiedBeatmap.BeatmapInfo.BeatmapSet); }); // Create the room using the real beatmap values. @@ -196,7 +195,7 @@ namespace osu.Game.Tests.Visual.Playlists Debug.Assert(originalBeatmap.BeatmapInfo.BeatmapSet != null); - manager.Import(originalBeatmap.BeatmapInfo.BeatmapSet).WaitSafely(); + manager.Import(originalBeatmap.BeatmapInfo.BeatmapSet); }); AddUntilStep("match has correct beatmap", () => realHash == match.Beatmap.Value.BeatmapInfo.MD5Hash); @@ -219,7 +218,7 @@ namespace osu.Game.Tests.Visual.Playlists Debug.Assert(beatmap.BeatmapInfo.BeatmapSet != null); - importedBeatmap = manager.Import(beatmap.BeatmapInfo.BeatmapSet).GetResultSafely()?.Value.Detach(); + importedBeatmap = manager.Import(beatmap.BeatmapInfo.BeatmapSet)?.Value.Detach(); }); private class TestPlaylistsRoomSubScreen : PlaylistsRoomSubScreen diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 5e977d075d..e31be1d51a 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -180,7 +180,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep(@"Load new scores via manager", () => { foreach (var score in generateSampleScores(beatmapInfo())) - scoreManager.Import(score).WaitSafely(); + scoreManager.Import(score); }); } diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs index b7bc0c37e1..940d001c5b 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; -using osu.Framework.Extensions; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Extensions; @@ -184,7 +183,7 @@ namespace osu.Game.Tests.Visual.SongSelect beatmap.DifficultyName = $"SR{i + 1}"; } - return Game.BeatmapManager.Import(beatmapSet).GetResultSafely()?.Value; + return Game.BeatmapManager.Import(beatmapSet)?.Value; } private bool ensureAllBeatmapSetsImported(IEnumerable beatmapSets) => beatmapSets.All(set => set != null); diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 0dc4556ed9..630171a4d0 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -8,7 +8,6 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; -using osu.Framework.Extensions; using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Testing; @@ -260,7 +259,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("import multi-ruleset map", () => { var usableRulesets = rulesets.AvailableRulesets.Where(r => r.OnlineID != 2).ToArray(); - manager.Import(TestResources.CreateTestBeatmapSetInfo(rulesets: usableRulesets)).WaitSafely(); + manager.Import(TestResources.CreateTestBeatmapSetInfo(rulesets: usableRulesets)); }); } else @@ -675,7 +674,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("import multi-ruleset map", () => { var usableRulesets = rulesets.AvailableRulesets.Where(r => r.OnlineID != 2).ToArray(); - manager.Import(TestResources.CreateTestBeatmapSetInfo(3, usableRulesets)).WaitSafely(); + manager.Import(TestResources.CreateTestBeatmapSetInfo(3, usableRulesets)); }); int previousSetID = 0; @@ -715,7 +714,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("import multi-ruleset map", () => { var usableRulesets = rulesets.AvailableRulesets.Where(r => r.OnlineID != 2).ToArray(); - manager.Import(TestResources.CreateTestBeatmapSetInfo(3, usableRulesets)).WaitSafely(); + manager.Import(TestResources.CreateTestBeatmapSetInfo(3, usableRulesets)); }); DrawableCarouselBeatmapSet set = null; @@ -764,7 +763,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("import huge difficulty count map", () => { var usableRulesets = rulesets.AvailableRulesets.Where(r => r.OnlineID != 2).ToArray(); - imported = manager.Import(TestResources.CreateTestBeatmapSetInfo(50, usableRulesets)).GetResultSafely()?.Value; + imported = manager.Import(TestResources.CreateTestBeatmapSetInfo(50, usableRulesets))?.Value; }); AddStep("select the first beatmap of import", () => Beatmap.Value = manager.GetWorkingBeatmap(imported.Beatmaps.First())); @@ -873,7 +872,7 @@ namespace osu.Game.Tests.Visual.SongSelect private void addRulesetImportStep(int id) => AddStep($"import test map for ruleset {id}", () => importForRuleset(id)); - private void importForRuleset(int id) => manager.Import(TestResources.CreateTestBeatmapSetInfo(3, rulesets.AvailableRulesets.Where(r => r.OnlineID == id).ToArray())).WaitSafely(); + private void importForRuleset(int id) => manager.Import(TestResources.CreateTestBeatmapSetInfo(3, rulesets.AvailableRulesets.Where(r => r.OnlineID == id).ToArray())); private void checkMusicPlaying(bool playing) => AddUntilStep($"music {(playing ? "" : "not ")}playing", () => music.IsPlaying == playing); @@ -903,7 +902,7 @@ namespace osu.Game.Tests.Visual.SongSelect var usableRulesets = rulesets.AvailableRulesets.Where(r => r.OnlineID != 2).ToArray(); for (int i = 0; i < 10; i++) - manager.Import(TestResources.CreateTestBeatmapSetInfo(difficultyCountPerSet, usableRulesets)).WaitSafely(); + manager.Import(TestResources.CreateTestBeatmapSetInfo(difficultyCountPerSet, usableRulesets)); }); } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index 55031a9699..4826d2fb33 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -112,7 +112,7 @@ namespace osu.Game.Tests.Visual.UserInterface Ruleset = new OsuRuleset().RulesetInfo, }; - importedScores.Add(scoreManager.Import(score).GetResultSafely().Value); + importedScores.Add(scoreManager.Import(score).Value); } }); diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 4488301fe5..a1c1982f00 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -10,7 +10,6 @@ using System.Threading; using System.Threading.Tasks; using osu.Framework.Audio; using osu.Framework.Audio.Track; -using osu.Framework.Extensions; using osu.Framework.IO.Stores; using osu.Framework.Platform; using osu.Framework.Testing; @@ -105,7 +104,7 @@ namespace osu.Game.Beatmaps foreach (BeatmapInfo b in beatmapSet.Beatmaps) b.BeatmapSet = beatmapSet; - var imported = beatmapModelManager.Import(beatmapSet).GetResultSafely(); + var imported = beatmapModelManager.Import(beatmapSet); if (imported == null) throw new InvalidOperationException("Failed to import new beatmap"); @@ -295,7 +294,7 @@ namespace osu.Game.Beatmaps return beatmapModelManager.Import(archive, lowPriority, cancellationToken); } - public Task?> Import(BeatmapSetInfo item, ArchiveReader? archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) + public ILive? Import(BeatmapSetInfo item, ArchiveReader? archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) { return beatmapModelManager.Import(item, archive, lowPriority, cancellationToken); } diff --git a/osu.Game/Database/IModelImporter.cs b/osu.Game/Database/IModelImporter.cs index d00cfb2035..3047a1d30a 100644 --- a/osu.Game/Database/IModelImporter.cs +++ b/osu.Game/Database/IModelImporter.cs @@ -45,7 +45,7 @@ namespace osu.Game.Database /// An optional archive to use for model population. /// Whether this is a low priority import. /// An optional cancellation token. - Task?> Import(TModel item, ArchiveReader? archive = null, bool lowPriority = false, CancellationToken cancellationToken = default); + ILive? Import(TModel item, ArchiveReader? archive = null, bool lowPriority = false, CancellationToken cancellationToken = default); /// /// A user displayable name for the model type associated with this manager. diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index bb26bd3b04..3a842a048a 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -308,7 +308,7 @@ namespace osu.Game.Scoring return scoreModelManager.Import(archive, lowPriority, cancellationToken); } - public Task> Import(ScoreInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) + public ILive Import(ScoreInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) { return scoreModelManager.Import(item, archive, lowPriority, cancellationToken); } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 4d3201cd27..cfca2d0a3d 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -1024,11 +1024,11 @@ namespace osu.Game.Screens.Play /// /// The to import. /// The imported score. - protected virtual async Task ImportScore(Score score) + protected virtual Task ImportScore(Score score) { // Replays are already populated and present in the game's database, so should not be re-imported. if (DrawableRuleset.ReplayScore != null) - return; + return Task.CompletedTask; LegacyByteArrayReader replayReader; @@ -1048,7 +1048,7 @@ namespace osu.Game.Screens.Play // conflicts across various systems (ie. solo and multiplayer). importableScore.OnlineID = -1; - var imported = await scoreManager.Import(importableScore, replayReader).ConfigureAwait(false); + var imported = scoreManager.Import(importableScore, replayReader); imported.PerformRead(s => { @@ -1056,6 +1056,8 @@ namespace osu.Game.Screens.Play score.ScoreInfo.Hash = s.Hash; score.ScoreInfo.ID = s.ID; }); + + return Task.CompletedTask; } /// diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 344acb7a0f..47c7bc060a 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -11,7 +11,6 @@ using JetBrains.Annotations; using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; -using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.OpenGL.Textures; using osu.Framework.Graphics.Textures; @@ -151,7 +150,7 @@ namespace osu.Game.Skinning Name = s.Name + @" (modified)", Creator = s.Creator, InstantiationInfo = s.InstantiationInfo, - }).GetResultSafely(); + }); if (result != null) { @@ -278,7 +277,7 @@ namespace osu.Game.Skinning return skinModelManager.Import(archive, lowPriority, cancellationToken); } - public Task> Import(SkinInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) + public ILive Import(SkinInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) { return skinModelManager.Import(item, archive, lowPriority, cancellationToken); } diff --git a/osu.Game/Stores/RealmArchiveModelImporter.cs b/osu.Game/Stores/RealmArchiveModelImporter.cs index b0f676ecf0..43c1c7c888 100644 --- a/osu.Game/Stores/RealmArchiveModelImporter.cs +++ b/osu.Game/Stores/RealmArchiveModelImporter.cs @@ -250,8 +250,10 @@ namespace osu.Game.Stores return null; } - var scheduledImport = Task.Factory.StartNew(async () => await Import(model, archive, lowPriority, cancellationToken).ConfigureAwait(false), - cancellationToken, TaskCreationOptions.HideScheduler, lowPriority ? import_scheduler_low_priority : import_scheduler).Unwrap(); + var scheduledImport = Task.Factory.StartNew(() => Import(model, archive, lowPriority, cancellationToken), + cancellationToken, + TaskCreationOptions.HideScheduler, + lowPriority ? import_scheduler_low_priority : import_scheduler); return await scheduledImport.ConfigureAwait(false); } @@ -318,7 +320,7 @@ namespace osu.Game.Stores /// An optional archive to use for model population. /// Whether this is a low priority import. /// An optional cancellation token. - public virtual Task?> Import(TModel item, ArchiveReader? archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) + public virtual ILive? Import(TModel item, ArchiveReader? archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) { return Realm.Run(realm => { @@ -353,7 +355,7 @@ namespace osu.Game.Stores transaction.Commit(); } - return Task.FromResult((ILive?)existing.ToLive(Realm)); + return existing.ToLive(Realm); } LogForModel(item, @"Found existing (optimised) but failed pre-check."); @@ -388,7 +390,7 @@ namespace osu.Game.Stores existing.DeletePending = false; transaction.Commit(); - return Task.FromResult((ILive?)existing.ToLive(Realm)); + return existing.ToLive(Realm); } LogForModel(item, @"Found existing but failed re-use check."); @@ -414,7 +416,7 @@ namespace osu.Game.Stores throw; } - return Task.FromResult((ILive?)item.ToLive(Realm)); + return (ILive?)item.ToLive(Realm); }); } From fc58b202b1d93b98d4d90b22741a5502591d5c3b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Jan 2022 15:45:23 +0900 Subject: [PATCH 638/996] Fix crash when trying to migrate collection database that doesn't exist --- osu.Game/OsuGameBase.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 7a6a126900..09ca4e450d 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -208,8 +208,13 @@ namespace osu.Game realm.CreateBackup($"client.{migration}.realm"); using (var source = Storage.GetStream("collection.db")) - using (var destination = Storage.GetStream($"collection.{migration}.db", FileAccess.Write, FileMode.CreateNew)) - source.CopyTo(destination); + { + if (source != null) + { + using (var destination = Storage.GetStream($"collection.{migration}.db", FileAccess.Write, FileMode.CreateNew)) + source.CopyTo(destination); + } + } } dependencies.CacheAs(Storage); From 1bb1366c9f1c7e3d6d0b43f0bdf7d7f0ce3fe551 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Jan 2022 16:26:06 +0900 Subject: [PATCH 639/996] Fix notification reset events potentially arriving out of order if a block operation times out --- osu.Game/Database/RealmAccess.cs | 40 +++++++++++++++----------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index e6e3c9ee41..bf88313663 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -334,25 +334,6 @@ namespace osu.Game.Database } } - /// - /// Unregister all subscriptions when the realm instance is to be recycled. - /// Subscriptions will still remain and will be re-subscribed when the realm instance returns. - /// - private void unregisterAllSubscriptions() - { - lock (realmLock) - { - foreach (var action in notificationsResetMap.Values) - action(); - - foreach (var action in customSubscriptionsResetMap) - { - action.Value?.Dispose(); - customSubscriptionsResetMap[action.Key] = null; - } - } - } - private Realm getRealmInstance() { if (isDisposed) @@ -605,9 +586,16 @@ namespace osu.Game.Database throw new InvalidOperationException(@$"{nameof(BlockAllOperations)} must be called from the update thread."); syncContext = SynchronizationContext.Current; - } - unregisterAllSubscriptions(); + // Before disposing the update context, clean up all subscriptions. + // Note that in the case of realm notification subscriptions, this is not really required (they will be cleaned up by disposal). + // In the case of custom subscriptions, we want them to fire before the update realm is disposed in case they do any follow-up work. + foreach (var action in customSubscriptionsResetMap) + { + action.Value?.Dispose(); + customSubscriptionsResetMap[action.Key] = null; + } + } Logger.Log(@"Blocking realm operations.", LoggingTarget.Database); @@ -615,6 +603,16 @@ namespace osu.Game.Database updateRealm = null; } + // In order to ensure events arrive in the correct order, these *must* be fired post disposal of the update realm, + // and must be posted to the synchronization context. + // This is because realm may fire event callbacks between the `unregisterAllSubscriptions` and `updateRealm.Dispose` + // calls above. + syncContext?.Post(_ => + { + foreach (var action in notificationsResetMap.Values) + action(); + }, null); + const int sleep_length = 200; int timeout = 5000; From 5a9524a74e945b6db0d51cdfa71574a63e6ee832 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 24 Jan 2022 02:51:13 +0300 Subject: [PATCH 640/996] Decrease default timeline zoom to "6 seconds visible" range --- osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index 265f56534f..35a0282611 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -141,7 +141,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { MaxZoom = getZoomLevelForVisibleMilliseconds(500); MinZoom = getZoomLevelForVisibleMilliseconds(10000); - Zoom = getZoomLevelForVisibleMilliseconds(2000); + Zoom = getZoomLevelForVisibleMilliseconds(6000); } }, true); } From 1f9cf00db89f7a80027e665d86a3cfd8e1891b31 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Jan 2022 16:44:44 +0900 Subject: [PATCH 641/996] Fix `DatabasedKeyBindingContainer` re-querying realm on receiving notification --- .../Bindings/DatabasedKeyBindingContainer.cs | 41 ++++++++++--------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs index 3e4a9759a3..ba129b93e5 100644 --- a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs +++ b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs @@ -8,6 +8,7 @@ using osu.Framework.Allocation; using osu.Framework.Input.Bindings; using osu.Game.Database; using osu.Game.Rulesets; +using Realms; namespace osu.Game.Input.Bindings { @@ -46,41 +47,36 @@ namespace osu.Game.Input.Bindings throw new InvalidOperationException($"{nameof(variant)} can not be null when a non-null {nameof(ruleset)} is provided."); } - private IQueryable queryRealmKeyBindings() - { - string rulesetName = ruleset?.ShortName; - return realm.Realm.All() - .Where(b => b.RulesetName == rulesetName && b.Variant == variant); - } - protected override void LoadComplete() { - realmSubscription = realm.RegisterForNotifications(r => queryRealmKeyBindings(), (sender, changes, error) => + realmSubscription = realm.RegisterForNotifications(queryRealmKeyBindings, (sender, changes, error) => { // The first fire of this is a bit redundant as this is being called in base.LoadComplete, // but this is safest in case the subscription is restored after a context recycle. - ReloadMappings(); + reloadMappings(sender.AsQueryable()); }); base.LoadComplete(); } - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); + protected override void ReloadMappings() => reloadMappings(queryRealmKeyBindings(realm.Realm)); - realmSubscription?.Dispose(); + private IQueryable queryRealmKeyBindings(Realm realm) + { + string rulesetName = ruleset?.ShortName; + return realm.All() + .Where(b => b.RulesetName == rulesetName && b.Variant == variant); } - protected override void ReloadMappings() + private void reloadMappings(IQueryable realmKeyBindings) { var defaults = DefaultKeyBindings.ToList(); - List newBindings = queryRealmKeyBindings().Detach() - // this ordering is important to ensure that we read entries from the database in the order - // enforced by DefaultKeyBindings. allow for song select to handle actions that may otherwise - // have been eaten by the music controller due to query order. - .OrderBy(b => defaults.FindIndex(d => (int)d.Action == b.ActionInt)).ToList(); + List newBindings = realmKeyBindings.Detach() + // this ordering is important to ensure that we read entries from the database in the order + // enforced by DefaultKeyBindings. allow for song select to handle actions that may otherwise + // have been eaten by the music controller due to query order. + .OrderBy(b => defaults.FindIndex(d => (int)d.Action == b.ActionInt)).ToList(); // In the case no bindings were found in the database, presume this usage is for a non-databased ruleset. // This actually should never be required and can be removed if it is ever deemed to cause a problem. @@ -91,5 +87,12 @@ namespace osu.Game.Input.Bindings else KeyBindings = newBindings; } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + realmSubscription?.Dispose(); + } } } From 958cfde60834a0e5a9342c3dfc7f59893e52942d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 23:45:06 +0900 Subject: [PATCH 642/996] Stop detaching and exposing beatmaps from `MusicController` --- osu.Game/Overlays/MusicController.cs | 14 ++++++-------- osu.Game/Overlays/NowPlayingOverlay.cs | 1 - 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 9dc55d24dd..335abb53dc 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -30,8 +30,6 @@ namespace osu.Game.Overlays [Resolved] private BeatmapManager beatmaps { get; set; } - public IBindableList BeatmapSets => beatmapSets; - /// /// Point in time after which the current track will be restarted on triggering a "previous track" action. /// @@ -88,12 +86,12 @@ namespace osu.Game.Overlays { beatmapSets.Clear(); foreach (var s in sender) - beatmapSets.Add(s.Detach()); + beatmapSets.Add(s); return; } foreach (int i in changes.InsertedIndices) - beatmapSets.Insert(i, sender[i].Detach()); + beatmapSets.Insert(i, sender[i]); foreach (int i in changes.DeletedIndices.OrderByDescending(i => i)) beatmapSets.RemoveAt(i); @@ -240,7 +238,7 @@ namespace osu.Game.Overlays queuedDirection = TrackChangeDirection.Prev; - var playable = BeatmapSets.TakeWhile(i => !i.Equals(current.BeatmapSetInfo)).LastOrDefault() ?? BeatmapSets.LastOrDefault(); + var playable = beatmapSets.TakeWhile(i => !i.Equals(current.BeatmapSetInfo)).LastOrDefault() ?? beatmapSets.LastOrDefault(); if (playable != null) { @@ -271,7 +269,7 @@ namespace osu.Game.Overlays queuedDirection = TrackChangeDirection.Next; - var playableSet = BeatmapSets.SkipWhile(i => i.ID != current.BeatmapSetInfo.ID).ElementAtOrDefault(1) ?? BeatmapSets.FirstOrDefault(); + var playableSet = beatmapSets.SkipWhile(i => i.ID != current.BeatmapSetInfo.ID).ElementAtOrDefault(1) ?? beatmapSets.FirstOrDefault(); var playableBeatmap = playableSet?.Beatmaps.FirstOrDefault(); if (playableBeatmap != null) @@ -322,8 +320,8 @@ namespace osu.Game.Overlays else { // figure out the best direction based on order in playlist. - int last = BeatmapSets.TakeWhile(b => !b.Equals(current.BeatmapSetInfo)).Count(); - int next = newWorking == null ? -1 : BeatmapSets.TakeWhile(b => !b.Equals(newWorking.BeatmapSetInfo)).Count(); + int last = beatmapSets.TakeWhile(b => !b.Equals(current.BeatmapSetInfo)).Count(); + int next = newWorking == null ? -1 : beatmapSets.TakeWhile(b => !b.Equals(newWorking.BeatmapSetInfo)).Count(); direction = last > next ? TrackChangeDirection.Prev : TrackChangeDirection.Next; } diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs index 4dd23c0008..4617a91885 100644 --- a/osu.Game/Overlays/NowPlayingOverlay.cs +++ b/osu.Game/Overlays/NowPlayingOverlay.cs @@ -197,7 +197,6 @@ namespace osu.Game.Overlays { dragContainer.Add(playlist); - playlist.BeatmapSets.BindTo(musicController.BeatmapSets); playlist.State.BindValueChanged(s => playlistButton.FadeColour(s.NewValue == Visibility.Visible ? colours.Yellow : Color4.White, 200, Easing.OutQuint), true); togglePlaylist(); From 8a4f3a7ce093b02dbbfa655146a30308214330d3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 22 Jan 2022 01:18:21 +0900 Subject: [PATCH 643/996] Reimplement subscription logic in `PlaylistOverlay` directly --- osu.Game/Overlays/Music/PlaylistOverlay.cs | 39 ++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs index 78b2d58dae..245886743a 100644 --- a/osu.Game/Overlays/Music/PlaylistOverlay.cs +++ b/osu.Game/Overlays/Music/PlaylistOverlay.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 osu.Framework.Allocation; using osu.Framework.Bindables; @@ -10,9 +11,11 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; +using osu.Game.Database; using osu.Game.Graphics; using osuTK; using osuTK.Graphics; +using Realms; namespace osu.Game.Overlays.Music { @@ -30,6 +33,15 @@ namespace osu.Game.Overlays.Music [Resolved] private BeatmapManager beatmaps { get; set; } + [Resolved] + private RealmContextFactory realmFactory { get; set; } + + private IDisposable beatmapSubscription; + + private IQueryable availableBeatmaps => realmFactory.Context + .All() + .Where(s => !s.DeletePending); + private FilterControl filter; private Playlist list; @@ -91,10 +103,31 @@ namespace osu.Game.Overlays.Music { base.LoadComplete(); + // tests might bind externally, in which case we don't want to involve realm. + if (beatmapSets.Count == 0) + beatmapSubscription = realmFactory.RegisterForNotifications(realm => availableBeatmaps, beatmapsChanged); + list.Items.BindTo(beatmapSets); beatmap.BindValueChanged(working => list.SelectedSet.Value = working.NewValue.BeatmapSetInfo, true); } + private void beatmapsChanged(IRealmCollection sender, ChangeSet changes, Exception error) + { + if (changes == null) + { + beatmapSets.Clear(); + // must use AddRange to avoid RearrangeableList sort overhead per add op. + beatmapSets.AddRange(sender); + return; + } + + foreach (int i in changes.InsertedIndices) + beatmapSets.Insert(i, sender[i]); + + foreach (int i in changes.DeletedIndices.OrderByDescending(i => i)) + beatmapSets.RemoveAt(i); + } + protected override void PopIn() { filter.Search.HoldFocus = true; @@ -123,5 +156,11 @@ namespace osu.Game.Overlays.Music beatmap.Value = beatmaps.GetWorkingBeatmap(set.Beatmaps.First()); beatmap.Value.Track.Restart(); } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + beatmapSubscription?.Dispose(); + } } } From ace2bd2208eead324a2ad8753b4c33bb6e1d02ea Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 22 Jan 2022 01:18:29 +0900 Subject: [PATCH 644/996] Apply some initial optimisations to `PlaylistItem` --- osu.Game/Overlays/Music/PlaylistItem.cs | 44 +++++++++++-------------- 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/osu.Game/Overlays/Music/PlaylistItem.cs b/osu.Game/Overlays/Music/PlaylistItem.cs index 04c12b8cd7..b3b039148b 100644 --- a/osu.Game/Overlays/Music/PlaylistItem.cs +++ b/osu.Game/Overlays/Music/PlaylistItem.cs @@ -32,8 +32,6 @@ namespace osu.Game.Overlays.Music : base(item) { Padding = new MarginPadding { Left = 5 }; - - FilterTerms = item.Metadata.GetSearchableTerms(); } [BackgroundDependencyLoader] @@ -46,6 +44,25 @@ namespace osu.Game.Overlays.Music { base.LoadComplete(); + var metadata = Model.Metadata; + + var title = new RomanisableString(metadata.TitleUnicode, metadata.Title); + var artist = new RomanisableString(metadata.ArtistUnicode, metadata.Artist); + + titlePart = text.AddText(title, sprite => sprite.Font = OsuFont.GetFont(weight: FontWeight.Regular)); + + updateSelectionState(true); + titlePart.DrawablePartsRecreated += _ => updateSelectionState(true); + + text.AddText(@" "); // to separate the title from the artist. + + text.AddText(artist, sprite => + { + sprite.Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold); + sprite.Colour = colours.Gray9; + sprite.Padding = new MarginPadding { Top = 1 }; + }); + SelectedSet.BindValueChanged(set => { if (set.OldValue?.Equals(Model) != true && set.NewValue?.Equals(Model) != true) @@ -67,27 +84,6 @@ namespace osu.Game.Overlays.Music AutoSizeAxes = Axes.Y, }; - protected override void LoadAsyncComplete() - { - base.LoadAsyncComplete(); - - var title = new RomanisableString(Model.Metadata.TitleUnicode, Model.Metadata.Title); - var artist = new RomanisableString(Model.Metadata.ArtistUnicode, Model.Metadata.Artist); - - titlePart = text.AddText(title, sprite => sprite.Font = OsuFont.GetFont(weight: FontWeight.Regular)); - updateSelectionState(true); - titlePart.DrawablePartsRecreated += _ => updateSelectionState(true); - - text.AddText(@" "); // to separate the title from the artist. - - text.AddText(artist, sprite => - { - sprite.Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold); - sprite.Colour = colours.Gray9; - sprite.Padding = new MarginPadding { Top = 1 }; - }); - } - protected override bool OnClick(ClickEvent e) { RequestSelection?.Invoke(Model); @@ -109,7 +105,7 @@ namespace osu.Game.Overlays.Music } } - public IEnumerable FilterTerms { get; } + public IEnumerable FilterTerms => Model.Metadata.GetSearchableTerms(); private bool matchingFilter = true; From 2a786f9ec0302f3ba39561ae5a5d56a0eafa7fe5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 22 Jan 2022 01:49:07 +0900 Subject: [PATCH 645/996] Load text only after it comes on screen (and tidy up selection handling logic) --- osu.Game/Overlays/Music/PlaylistItem.cs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/Music/PlaylistItem.cs b/osu.Game/Overlays/Music/PlaylistItem.cs index b3b039148b..b31a03a64d 100644 --- a/osu.Game/Overlays/Music/PlaylistItem.cs +++ b/osu.Game/Overlays/Music/PlaylistItem.cs @@ -50,12 +50,9 @@ namespace osu.Game.Overlays.Music var artist = new RomanisableString(metadata.ArtistUnicode, metadata.Artist); titlePart = text.AddText(title, sprite => sprite.Font = OsuFont.GetFont(weight: FontWeight.Regular)); - - updateSelectionState(true); titlePart.DrawablePartsRecreated += _ => updateSelectionState(true); text.AddText(@" "); // to separate the title from the artist. - text.AddText(artist, sprite => { sprite.Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold); @@ -65,24 +62,31 @@ namespace osu.Game.Overlays.Music SelectedSet.BindValueChanged(set => { - if (set.OldValue?.Equals(Model) != true && set.NewValue?.Equals(Model) != true) + bool newSelected = set.NewValue?.Equals(Model) == true; + + if (newSelected == selected) return; + selected = newSelected; updateSelectionState(false); - }, true); + }); + + updateSelectionState(true); } + private bool selected; + private void updateSelectionState(bool instant) { foreach (Drawable s in titlePart.Drawables) - s.FadeColour(SelectedSet.Value?.Equals(Model) == true ? colours.Yellow : Color4.White, instant ? 0 : FADE_DURATION); + s.FadeColour(selected ? colours.Yellow : Color4.White, instant ? 0 : FADE_DURATION); } - protected override Drawable CreateContent() => text = new OsuTextFlowContainer + protected override Drawable CreateContent() => new DelayedLoadWrapper(text = new OsuTextFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - }; + }); protected override bool OnClick(ClickEvent e) { From 83b0e4572a057c520723c3319d7227c1ccf8951f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 22 Jan 2022 02:39:01 +0900 Subject: [PATCH 646/996] Fix test failures --- osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs index 62f3b63780..7131c19471 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs @@ -68,7 +68,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("drag to 5th", () => { var item = this.ChildrenOfType().ElementAt(4); - InputManager.MoveMouseTo(item.ScreenSpaceDrawQuad.Centre); + InputManager.MoveMouseTo(item.ScreenSpaceDrawQuad.BottomLeft); }); AddAssert("song 1 is 5th", () => beatmapSets[4].Equals(first)); From 1a776a95877907b7d9304ffa6ac4a529558f1b79 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 22 Jan 2022 04:27:07 +0900 Subject: [PATCH 647/996] Completely remove subscription from `MusicController` --- osu.Game/Overlays/Music/PlaylistOverlay.cs | 10 ++-- osu.Game/Overlays/MusicController.cs | 68 ++++------------------ 2 files changed, 16 insertions(+), 62 deletions(-) diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs index 245886743a..6adcd2a6d6 100644 --- a/osu.Game/Overlays/Music/PlaylistOverlay.cs +++ b/osu.Game/Overlays/Music/PlaylistOverlay.cs @@ -34,13 +34,13 @@ namespace osu.Game.Overlays.Music private BeatmapManager beatmaps { get; set; } [Resolved] - private RealmContextFactory realmFactory { get; set; } + private RealmAccess realm { get; set; } private IDisposable beatmapSubscription; - private IQueryable availableBeatmaps => realmFactory.Context - .All() - .Where(s => !s.DeletePending); + private IQueryable availableBeatmaps => realm.Realm + .All() + .Where(s => !s.DeletePending); private FilterControl filter; private Playlist list; @@ -105,7 +105,7 @@ namespace osu.Game.Overlays.Music // tests might bind externally, in which case we don't want to involve realm. if (beatmapSets.Count == 0) - beatmapSubscription = realmFactory.RegisterForNotifications(realm => availableBeatmaps, beatmapsChanged); + beatmapSubscription = realm.RegisterForNotifications(realm => availableBeatmaps, beatmapsChanged); list.Items.BindTo(beatmapSets); beatmap.BindValueChanged(working => list.SelectedSet.Value = working.NewValue.BeatmapSetInfo, true); diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 335abb53dc..5fc0da8891 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -16,7 +16,6 @@ using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Rulesets.Mods; -using Realms; namespace osu.Game.Overlays { @@ -25,8 +24,6 @@ namespace osu.Game.Overlays /// public class MusicController : CompositeDrawable { - private IDisposable beatmapSubscription; - [Resolved] private BeatmapManager beatmaps { get; set; } @@ -35,8 +32,6 @@ namespace osu.Game.Overlays /// private const double restart_cutoff_point = 5000; - private readonly BindableList beatmapSets = new BindableList(); - /// /// Whether the user has requested the track to be paused. Use to determine whether the track is still playing. /// @@ -69,50 +64,11 @@ namespace osu.Game.Overlays mods.BindValueChanged(_ => ResetTrackAdjustments(), true); } - private IQueryable queryRealmBeatmapSets() => - realm.Realm - .All() - .Where(s => !s.DeletePending); - - protected override void LoadComplete() - { - base.LoadComplete(); - beatmapSubscription = realm.RegisterForNotifications(r => queryRealmBeatmapSets(), beatmapsChanged); - } - - private void beatmapsChanged(IRealmCollection sender, ChangeSet changes, Exception error) - { - if (changes == null) - { - beatmapSets.Clear(); - foreach (var s in sender) - beatmapSets.Add(s); - return; - } - - foreach (int i in changes.InsertedIndices) - beatmapSets.Insert(i, sender[i]); - - foreach (int i in changes.DeletedIndices.OrderByDescending(i => i)) - beatmapSets.RemoveAt(i); - } - /// /// Forcefully reload the current 's track from disk. /// public void ReloadCurrentTrack() => changeTrack(); - /// - /// Change the position of a in the current playlist. - /// - /// The beatmap to move. - /// The new position. - public void ChangeBeatmapSetPosition(BeatmapSetInfo beatmapSetInfo, int index) - { - beatmapSets.Remove(beatmapSetInfo); - beatmapSets.Insert(index, beatmapSetInfo); - } - /// /// Returns whether the beatmap track is playing. /// @@ -238,11 +194,12 @@ namespace osu.Game.Overlays queuedDirection = TrackChangeDirection.Prev; - var playable = beatmapSets.TakeWhile(i => !i.Equals(current.BeatmapSetInfo)).LastOrDefault() ?? beatmapSets.LastOrDefault(); + var playableSet = getBeatmapSets().AsEnumerable().TakeWhile(i => !i.Equals(current.BeatmapSetInfo)).LastOrDefault() + ?? getBeatmapSets().LastOrDefault(); - if (playable != null) + if (playableSet != null) { - changeBeatmap(beatmaps.GetWorkingBeatmap(playable.Beatmaps.First())); + changeBeatmap(beatmaps.GetWorkingBeatmap(playableSet.Beatmaps.First())); restartTrack(); return PreviousTrackResult.Previous; } @@ -269,7 +226,9 @@ namespace osu.Game.Overlays queuedDirection = TrackChangeDirection.Next; - var playableSet = beatmapSets.SkipWhile(i => i.ID != current.BeatmapSetInfo.ID).ElementAtOrDefault(1) ?? beatmapSets.FirstOrDefault(); + var playableSet = getBeatmapSets().AsEnumerable().SkipWhile(i => !i.Equals(current.BeatmapSetInfo)).ElementAtOrDefault(1) + ?? getBeatmapSets().FirstOrDefault(); + var playableBeatmap = playableSet?.Beatmaps.FirstOrDefault(); if (playableBeatmap != null) @@ -293,6 +252,8 @@ namespace osu.Game.Overlays private TrackChangeDirection? queuedDirection; + private IQueryable getBeatmapSets() => realm.Realm.All().Where(s => !s.DeletePending); + private void beatmapChanged(ValueChangedEvent beatmap) => changeBeatmap(beatmap.NewValue); private void changeBeatmap(WorkingBeatmap newWorking) @@ -320,8 +281,8 @@ namespace osu.Game.Overlays else { // figure out the best direction based on order in playlist. - int last = beatmapSets.TakeWhile(b => !b.Equals(current.BeatmapSetInfo)).Count(); - int next = newWorking == null ? -1 : beatmapSets.TakeWhile(b => !b.Equals(newWorking.BeatmapSetInfo)).Count(); + int last = getBeatmapSets().AsEnumerable().TakeWhile(b => !b.Equals(current.BeatmapSetInfo)).Count(); + int next = newWorking == null ? -1 : getBeatmapSets().AsEnumerable().TakeWhile(b => !b.Equals(newWorking.BeatmapSetInfo)).Count(); direction = last > next ? TrackChangeDirection.Prev : TrackChangeDirection.Next; } @@ -435,13 +396,6 @@ namespace osu.Game.Overlays mod.ApplyToTrack(CurrentTrack); } } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - beatmapSubscription?.Dispose(); - } } public enum TrackChangeDirection From ffd7877a1e8b6b822fd5f5922111b3d1ae7c027b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Jan 2022 17:41:22 +0900 Subject: [PATCH 648/996] Remove synchronization context hacks in realm tests --- osu.Game.Tests/Database/GeneralUsageTests.cs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/osu.Game.Tests/Database/GeneralUsageTests.cs b/osu.Game.Tests/Database/GeneralUsageTests.cs index dc0d42595b..8262ef18d4 100644 --- a/osu.Game.Tests/Database/GeneralUsageTests.cs +++ b/osu.Game.Tests/Database/GeneralUsageTests.cs @@ -87,11 +87,6 @@ namespace osu.Game.Tests.Database hasThreadedUsage.Wait(); - // Usually the host would run the synchronization context work per frame. - // For the sake of keeping this test simple (there's only one update invocation), - // let's replace it so we can ensure work is run immediately. - SynchronizationContext.SetSynchronizationContext(new ImmediateExecuteSynchronizationContext()); - Assert.Throws(() => { using (realm.BlockAllOperations()) @@ -107,10 +102,5 @@ namespace osu.Game.Tests.Database } }); } - - private class ImmediateExecuteSynchronizationContext : SynchronizationContext - { - public override void Post(SendOrPostCallback d, object? state) => d(state); - } } } From 90a7dd771107f0de718a1fefc286c2d946ee99c8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Jan 2022 14:30:00 +0900 Subject: [PATCH 649/996] In gameplay bindings test, ensure a selection is made before attempting to enter gameplay --- .../Visual/Navigation/TestSceneChangeAndUseGameplayBindings.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneChangeAndUseGameplayBindings.cs b/osu.Game.Tests/Visual/Navigation/TestSceneChangeAndUseGameplayBindings.cs index bfcefdbbfe..6165c9d9b1 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneChangeAndUseGameplayBindings.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneChangeAndUseGameplayBindings.cs @@ -50,8 +50,11 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("close settings", () => Game.Settings.Hide()); AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely()); + PushAndConfirm(() => new PlaySongSelect()); + AddUntilStep("wait for selection", () => !Game.Beatmap.IsDefault); + AddStep("enter gameplay", () => InputManager.Key(Key.Enter)); AddUntilStep("wait for gameplay", () => player?.IsBreakTime.Value == false); From c0ed30801686e2a25486fb2a80a9ec2534e2b522 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Jan 2022 14:30:32 +0900 Subject: [PATCH 650/996] Use more correct method of deletion in `TestScenePlaySongSelect` --- osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 630171a4d0..458c6130c7 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -64,13 +64,13 @@ namespace osu.Game.Tests.Visual.SongSelect { base.SetUpSteps(); - AddStep("delete all beatmaps", () => + AddStep("reset defaults", () => { Ruleset.Value = new OsuRuleset().RulesetInfo; - manager?.Delete(manager.GetAllUsableBeatmapSets()); - Beatmap.SetDefault(); }); + + AddStep("delete all beatmaps", () => manager?.Delete()); } [Test] From b2b6672095d10a8da9a005dd87d072212893e454 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 25 Jan 2022 11:43:05 +0300 Subject: [PATCH 651/996] Add failing test asserts --- 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 bf3b46c6f7..96f815621c 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs @@ -3,6 +3,7 @@ using System.Linq; using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Input; using osu.Framework.Testing; using osu.Game.Beatmaps.ControlPoints; @@ -40,6 +41,7 @@ namespace osu.Game.Tests.Visual.Editing AddStep("Enter compose mode", () => InputManager.Key(Key.F1)); AddUntilStep("Wait for compose mode load", () => editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); + AddStep("Set beat divisor", () => editor.Dependencies.Get().Value = 16); AddStep("Set overall difficulty", () => editorBeatmap.Difficulty.OverallDifficulty = 7); AddStep("Set artist and title", () => { @@ -88,6 +90,7 @@ namespace osu.Game.Tests.Visual.Editing private void checkMutations() { AddAssert("Beatmap contains single hitcircle", () => editorBeatmap.HitObjects.Count == 1); + AddAssert("Beatmap has correct beat divisor", () => editorBeatmap.BeatmapInfo.BeatDivisor == 16); AddAssert("Beatmap has correct overall difficulty", () => editorBeatmap.Difficulty.OverallDifficulty == 7); 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"); From f7f58b06a1bd107100d3ed0ee23c65bb0925112c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 25 Jan 2022 11:43:16 +0300 Subject: [PATCH 652/996] Fix beat divisor not saving in editor --- osu.Game/Screens/Edit/Editor.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 8c4b458534..b42f629aad 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -158,9 +158,6 @@ namespace osu.Game.Screens.Edit return; } - beatDivisor.Value = playableBeatmap.BeatmapInfo.BeatDivisor; - beatDivisor.BindValueChanged(divisor => playableBeatmap.BeatmapInfo.BeatDivisor = divisor.NewValue); - // Todo: should probably be done at a DrawableRuleset level to share logic with Player. clock = new EditorClock(playableBeatmap, beatDivisor) { IsCoupled = false }; clock.ChangeSource(loadableBeatmap.Track); @@ -178,6 +175,9 @@ namespace osu.Game.Screens.Edit changeHandler = new EditorChangeHandler(editorBeatmap); dependencies.CacheAs(changeHandler); + beatDivisor.Value = editorBeatmap.BeatmapInfo.BeatDivisor; + beatDivisor.BindValueChanged(divisor => editorBeatmap.BeatmapInfo.BeatDivisor = divisor.NewValue); + updateLastSavedHash(); Schedule(() => From a93873e8ca0f9290c5be236b363337d71042824f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 25 Jan 2022 13:01:30 +0300 Subject: [PATCH 653/996] Recreate test beatmap of `EditorTestScene` on set up --- osu.Game/Tests/Visual/EditorTestScene.cs | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/osu.Game/Tests/Visual/EditorTestScene.cs b/osu.Game/Tests/Visual/EditorTestScene.cs index 6f751b1736..f7d62a8694 100644 --- a/osu.Game/Tests/Visual/EditorTestScene.cs +++ b/osu.Game/Tests/Visual/EditorTestScene.cs @@ -43,28 +43,16 @@ namespace osu.Game.Tests.Visual }; private TestBeatmapManager testBeatmapManager; - private WorkingBeatmap working; [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio, RulesetStore rulesets) { Add(logo); - working = CreateWorkingBeatmap(Ruleset.Value); - if (IsolateSavingFromDatabase) Dependencies.CacheAs(testBeatmapManager = new TestBeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); } - protected override void LoadComplete() - { - base.LoadComplete(); - - Beatmap.Value = working; - if (testBeatmapManager != null) - testBeatmapManager.TestBeatmap = working; - } - protected virtual bool EditorComponentsReady => Editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true && Editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true; @@ -78,6 +66,11 @@ namespace osu.Game.Tests.Visual protected virtual void LoadEditor() { + Beatmap.Value = CreateWorkingBeatmap(Ruleset.Value); + + if (testBeatmapManager != null) + testBeatmapManager.TestBeatmap = Beatmap.Value; + LoadScreen(editorLoader = new TestEditorLoader()); } From 4a9f4eecba45e66428de23094f1d1387199bb450 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Jan 2022 20:49:52 +0900 Subject: [PATCH 654/996] Use blocking calls to `SynchronizationContext` to guarantee order of execution --- osu.Game/Database/RealmAccess.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index bf88313663..ffaae9b4ae 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -607,7 +607,7 @@ namespace osu.Game.Database // and must be posted to the synchronization context. // This is because realm may fire event callbacks between the `unregisterAllSubscriptions` and `updateRealm.Dispose` // calls above. - syncContext?.Post(_ => + syncContext?.Send(_ => { foreach (var action in notificationsResetMap.Values) action(); @@ -647,8 +647,14 @@ namespace osu.Game.Database { Logger.Log(@"Restoring realm operations.", LoggingTarget.Database); realmRetrievalLock.Release(); + // Post back to the update thread to revive any subscriptions. - syncContext?.Post(_ => ensureUpdateRealm(), null); + // 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); + else + syncContext?.Post(_ => ensureUpdateRealm(), null); } } From 6c69df815a83f718d59354744321ce99ca5b644a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 25 Jan 2022 15:25:28 +0300 Subject: [PATCH 655/996] Update editor test scenes to set working beatmap properly --- .../Visual/Editing/TestSceneDifficultySwitching.cs | 8 +++----- .../Visual/Editing/TestSceneEditorBeatmapCreation.cs | 7 ++----- .../Visual/Editing/TestSceneEditorTestGameplay.cs | 5 ++++- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs b/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs index 243bb71e26..cf6488f721 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs @@ -13,6 +13,7 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.UI; using osu.Game.Screens.Edit; +using osu.Game.Storyboards; using osu.Game.Tests.Beatmaps.IO; namespace osu.Game.Tests.Visual.Editing @@ -37,11 +38,8 @@ namespace osu.Game.Tests.Visual.Editing base.SetUpSteps(); } - protected override void LoadEditor() - { - Beatmap.Value = beatmaps.GetWorkingBeatmap(importedBeatmapSet.Beatmaps.First()); - base.LoadEditor(); - } + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) + => beatmaps.GetWorkingBeatmap(importedBeatmapSet.Beatmaps.First()); [Test] public void TestBasicSwitch() diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index 2386446e96..e3fb44534b 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -13,6 +13,7 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Setup; +using osu.Game.Storyboards; using osu.Game.Tests.Resources; using SharpCompress.Archives; using SharpCompress.Archives.Zip; @@ -39,11 +40,7 @@ namespace osu.Game.Tests.Visual.Editing AddStep("make new beatmap unique", () => EditorBeatmap.Metadata.Title = Guid.NewGuid().ToString()); } - protected override void LoadEditor() - { - Beatmap.Value = new DummyWorkingBeatmap(Audio, null); - base.LoadEditor(); - } + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) => new DummyWorkingBeatmap(Audio, null); [Test] public void TestCreateNewBeatmap() diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs index bb630e5d5c..79afc8cf27 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs @@ -17,6 +17,7 @@ using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Components.Timelines.Summary; using osu.Game.Screens.Edit.GameplayTest; using osu.Game.Screens.Play; +using osu.Game.Storyboards; using osu.Game.Tests.Beatmaps.IO; using osuTK.Graphics; using osuTK.Input; @@ -43,9 +44,11 @@ namespace osu.Game.Tests.Visual.Editing base.SetUpSteps(); } + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) + => beatmaps.GetWorkingBeatmap(importedBeatmapSet.Beatmaps.First(b => b.RulesetID == 0)); + protected override void LoadEditor() { - Beatmap.Value = beatmaps.GetWorkingBeatmap(importedBeatmapSet.Beatmaps.First(b => b.RulesetID == 0)); SelectedMods.Value = new[] { new ModCinema() }; base.LoadEditor(); } From cdef67ccd0840053192a151b9d75f303e44ecb07 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Jan 2022 23:38:46 +0900 Subject: [PATCH 656/996] Log posted notifications To help with test failures and the likes. --- osu.Game/Overlays/NotificationOverlay.cs | 3 +++ osu.Game/Overlays/Notifications/Notification.cs | 3 +++ osu.Game/Overlays/Notifications/ProgressNotification.cs | 2 +- osu.Game/Overlays/Notifications/SimpleNotification.cs | 2 +- 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs index 8809dec642..e4e3931048 100644 --- a/osu.Game/Overlays/NotificationOverlay.cs +++ b/osu.Game/Overlays/NotificationOverlay.cs @@ -12,6 +12,7 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Localisation; +using osu.Framework.Logging; using osu.Framework.Threading; using osu.Game.Graphics; using osu.Game.Localisation; @@ -118,6 +119,8 @@ namespace osu.Game.Overlays { ++runningDepth; + Logger.Log($"⚠️ {notification.Text}"); + notification.Closed += notificationClosed; if (notification is IHasCompletionTarget hasCompletionTarget) diff --git a/osu.Game/Overlays/Notifications/Notification.cs b/osu.Game/Overlays/Notifications/Notification.cs index 44203e8ee7..ec6e9e09b3 100644 --- a/osu.Game/Overlays/Notifications/Notification.cs +++ b/osu.Game/Overlays/Notifications/Notification.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osuTK; @@ -25,6 +26,8 @@ namespace osu.Game.Overlays.Notifications /// public event Action Closed; + public abstract LocalisableString Text { get; set; } + /// /// Whether this notification should forcefully display itself. /// diff --git a/osu.Game/Overlays/Notifications/ProgressNotification.cs b/osu.Game/Overlays/Notifications/ProgressNotification.cs index 5b74bff817..4735fcb7c1 100644 --- a/osu.Game/Overlays/Notifications/ProgressNotification.cs +++ b/osu.Game/Overlays/Notifications/ProgressNotification.cs @@ -25,7 +25,7 @@ namespace osu.Game.Overlays.Notifications private LocalisableString text; - public LocalisableString Text + public override LocalisableString Text { get => text; set diff --git a/osu.Game/Overlays/Notifications/SimpleNotification.cs b/osu.Game/Overlays/Notifications/SimpleNotification.cs index c32e40ffc8..b9a1cc6d90 100644 --- a/osu.Game/Overlays/Notifications/SimpleNotification.cs +++ b/osu.Game/Overlays/Notifications/SimpleNotification.cs @@ -18,7 +18,7 @@ namespace osu.Game.Overlays.Notifications { private LocalisableString text; - public LocalisableString Text + public override LocalisableString Text { get => text; set From d1cbdf63f09afb381961cc646782d2c01610f80d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 25 Jan 2022 10:43:43 +0300 Subject: [PATCH 657/996] Add support for reading/saving timeline zoom in editor --- .../Compose/Components/Timeline/Timeline.cs | 29 ++++++++++++++++--- .../Timeline/ZoomableScrollContainer.cs | 9 ++++++ 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index 35a0282611..5722174490 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -24,7 +24,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [Cached] public class Timeline : ZoomableScrollContainer, IPositionSnapProvider { + private const float timeline_height = 72; + private const float timeline_expanded_height = 94; + private readonly Drawable userContent; + public readonly Bindable WaveformVisible = new Bindable(); public readonly Bindable ControlPointsVisible = new Bindable(); @@ -58,8 +62,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private Track track; - private const float timeline_height = 72; - private const float timeline_expanded_height = 94; + /// + /// The timeline zoom level at a 1x zoom scale. + /// + private float defaultTimelineZoom; + + private readonly Bindable timelineZoomScale = new BindableDouble(1.0); public Timeline(Drawable userContent) { @@ -84,7 +92,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private Bindable waveformOpacity; [BackgroundDependencyLoader] - private void load(IBindable beatmap, OsuColour colours, OsuConfigManager config) + private void load(IBindable beatmap, EditorBeatmap editorBeatmap, OsuColour colours, OsuConfigManager config) { CentreMarker centreMarker; @@ -141,9 +149,16 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { MaxZoom = getZoomLevelForVisibleMilliseconds(500); MinZoom = getZoomLevelForVisibleMilliseconds(10000); - Zoom = getZoomLevelForVisibleMilliseconds(6000); + defaultTimelineZoom = getZoomLevelForVisibleMilliseconds(6000); } }, true); + + timelineZoomScale.Value = editorBeatmap.BeatmapInfo.TimelineZoom; + timelineZoomScale.BindValueChanged(scale => + { + Zoom = (float)(defaultTimelineZoom * scale.NewValue); + editorBeatmap.BeatmapInfo.TimelineZoom = scale.NewValue; + }, true); } protected override void LoadComplete() @@ -201,6 +216,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline return base.OnScroll(e); } + protected override void OnZoomChange() + { + base.OnZoomChange(); + timelineZoomScale.Value = Zoom / defaultTimelineZoom; + } + protected override void UpdateAfterChildren() { base.UpdateAfterChildren(); diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs index f10eb0d284..6dea99f1c8 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs @@ -136,11 +136,20 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { zoomTarget = Math.Clamp(newZoom, MinZoom, MaxZoom); transformZoomTo(zoomTarget, focusPoint, ZoomDuration, ZoomEasing); + + OnZoomChange(); } private void transformZoomTo(float newZoom, float focusPoint, double duration = 0, Easing easing = Easing.None) => this.TransformTo(this.PopulateTransform(new TransformZoom(focusPoint, zoomedContent.DrawWidth, Current), newZoom, duration, easing)); + /// + /// Invoked when the zoom target has changed. + /// + protected virtual void OnZoomChange() + { + } + private class TransformZoom : Transform { /// From ad18bc4983254b389c5b80f707ee8610197a5ceb Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 25 Jan 2022 17:02:23 +0300 Subject: [PATCH 658/996] Update timeline selection test scene with zoom changes --- .../Editing/TestSceneTimelineSelection.cs | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimelineSelection.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimelineSelection.cs index 2544b6c2a1..81ab4712ab 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTimelineSelection.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimelineSelection.cs @@ -47,25 +47,25 @@ namespace osu.Game.Tests.Visual.Editing AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects = new[] { - new HitCircle { StartTime = 100 }, - new HitCircle { StartTime = 200, Position = new Vector2(100) }, - new HitCircle { StartTime = 300, Position = new Vector2(200) }, - new HitCircle { StartTime = 400, Position = new Vector2(300) }, + new HitCircle { StartTime = 500 }, + new HitCircle { StartTime = 1000, Position = new Vector2(100) }, + new HitCircle { StartTime = 1500, Position = new Vector2(200) }, + new HitCircle { StartTime = 2000, Position = new Vector2(300) }, })); AddStep("select objects", () => EditorBeatmap.SelectedHitObjects.AddRange(addedObjects)); AddStep("nudge forwards", () => InputManager.Key(Key.K)); - AddAssert("objects moved forwards in time", () => addedObjects[0].StartTime > 100); + AddAssert("objects moved forwards in time", () => addedObjects[0].StartTime > 500); AddStep("nudge backwards", () => InputManager.Key(Key.J)); - AddAssert("objects reverted to original position", () => addedObjects[0].StartTime == 100); + AddAssert("objects reverted to original position", () => addedObjects[0].StartTime == 500); } [Test] public void TestBasicSelect() { - var addedObject = new HitCircle { StartTime = 100 }; + var addedObject = new HitCircle { StartTime = 500 }; AddStep("add hitobject", () => EditorBeatmap.Add(addedObject)); moveMouseToObject(() => addedObject); @@ -75,7 +75,7 @@ namespace osu.Game.Tests.Visual.Editing var addedObject2 = new HitCircle { - StartTime = 200, + StartTime = 1000, Position = new Vector2(100), }; @@ -92,10 +92,10 @@ namespace osu.Game.Tests.Visual.Editing { var addedObjects = new[] { - new HitCircle { StartTime = 100 }, - new HitCircle { StartTime = 200, Position = new Vector2(100) }, - new HitCircle { StartTime = 300, Position = new Vector2(200) }, - new HitCircle { StartTime = 400, Position = new Vector2(300) }, + new HitCircle { StartTime = 500 }, + new HitCircle { StartTime = 1000, Position = new Vector2(100) }, + new HitCircle { StartTime = 1500, Position = new Vector2(200) }, + new HitCircle { StartTime = 2000, Position = new Vector2(300) }, }; AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects)); @@ -125,7 +125,7 @@ namespace osu.Game.Tests.Visual.Editing [Test] public void TestBasicDeselect() { - var addedObject = new HitCircle { StartTime = 100 }; + var addedObject = new HitCircle { StartTime = 500 }; AddStep("add hitobject", () => EditorBeatmap.Add(addedObject)); moveMouseToObject(() => addedObject); @@ -166,11 +166,11 @@ namespace osu.Game.Tests.Visual.Editing { var addedObjects = new[] { - new HitCircle { StartTime = 100 }, - new HitCircle { StartTime = 200, Position = new Vector2(100) }, - new HitCircle { StartTime = 300, Position = new Vector2(200) }, - new HitCircle { StartTime = 400, Position = new Vector2(300) }, - new HitCircle { StartTime = 500, Position = new Vector2(400) }, + new HitCircle { StartTime = 500 }, + new HitCircle { StartTime = 1000, Position = new Vector2(100) }, + new HitCircle { StartTime = 1500, Position = new Vector2(200) }, + new HitCircle { StartTime = 2000, Position = new Vector2(300) }, + new HitCircle { StartTime = 2500, Position = new Vector2(400) }, }; AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects)); @@ -236,10 +236,10 @@ namespace osu.Game.Tests.Visual.Editing { var addedObjects = new[] { - new HitCircle { StartTime = 100 }, - new HitCircle { StartTime = 200, Position = new Vector2(100) }, - new HitCircle { StartTime = 300, Position = new Vector2(200) }, - new HitCircle { StartTime = 400, Position = new Vector2(300) }, + new HitCircle { StartTime = 500 }, + new HitCircle { StartTime = 1000, Position = new Vector2(100) }, + new HitCircle { StartTime = 1500, Position = new Vector2(200) }, + new HitCircle { StartTime = 2000, Position = new Vector2(300) }, }; AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects)); From 4169e5592ee973349fc5f39e1778b948ac722338 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 25 Jan 2022 19:36:19 +0300 Subject: [PATCH 659/996] Reword event handler name and update xmldoc --- .../Screens/Edit/Compose/Components/Timeline/Timeline.cs | 4 ++-- .../Compose/Components/Timeline/ZoomableScrollContainer.cs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index 5722174490..51cca4ceff 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -216,9 +216,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline return base.OnScroll(e); } - protected override void OnZoomChange() + protected override void OnZoomChanged() { - base.OnZoomChange(); + base.OnZoomChanged(); timelineZoomScale.Value = Zoom / defaultTimelineZoom; } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs index 6dea99f1c8..35d103ddf1 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs @@ -137,16 +137,16 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline zoomTarget = Math.Clamp(newZoom, MinZoom, MaxZoom); transformZoomTo(zoomTarget, focusPoint, ZoomDuration, ZoomEasing); - OnZoomChange(); + OnZoomChanged(); } private void transformZoomTo(float newZoom, float focusPoint, double duration = 0, Easing easing = Easing.None) => this.TransformTo(this.PopulateTransform(new TransformZoom(focusPoint, zoomedContent.DrawWidth, Current), newZoom, duration, easing)); /// - /// Invoked when the zoom target has changed. + /// Invoked when has changed. /// - protected virtual void OnZoomChange() + protected virtual void OnZoomChanged() { } From 5085eb68018d1ae069cce9e754d5e6af326d3a06 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 03:37:11 +0900 Subject: [PATCH 660/996] Ensure gameplay starts by dismissing any notifications in `TestSceneChangeAndUseGameplayBindings` --- .../TestSceneChangeAndUseGameplayBindings.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneChangeAndUseGameplayBindings.cs b/osu.Game.Tests/Visual/Navigation/TestSceneChangeAndUseGameplayBindings.cs index 6165c9d9b1..347b4b6c54 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneChangeAndUseGameplayBindings.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneChangeAndUseGameplayBindings.cs @@ -57,6 +57,13 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("enter gameplay", () => InputManager.Key(Key.Enter)); + AddUntilStep("wait for player", () => + { + // dismiss any notifications that may appear (ie. muted notification). + clickMouseInCentre(); + return player != null; + }); + AddUntilStep("wait for gameplay", () => player?.IsBreakTime.Value == false); AddStep("press 'z'", () => InputManager.Key(Key.Z)); @@ -66,6 +73,12 @@ namespace osu.Game.Tests.Visual.Navigation AddAssert("key counter did increase", () => keyCounter.CountPresses == 1); } + private void clickMouseInCentre() + { + InputManager.MoveMouseTo(Game.ScreenSpaceDrawQuad.Centre); + InputManager.Click(MouseButton.Left); + } + private KeyBindingsSubsection osuBindingSubsection => keyBindingPanel .ChildrenOfType() .FirstOrDefault(s => s.Ruleset.ShortName == "osu"); From 64914c45a4d7528713b8d06d3e7d54c2335db2cb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 11:53:50 +0900 Subject: [PATCH 661/996] Remove unnecessary realm query helper method --- osu.Game/Overlays/Music/PlaylistOverlay.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs index 6adcd2a6d6..ffddf168ed 100644 --- a/osu.Game/Overlays/Music/PlaylistOverlay.cs +++ b/osu.Game/Overlays/Music/PlaylistOverlay.cs @@ -38,10 +38,6 @@ namespace osu.Game.Overlays.Music private IDisposable beatmapSubscription; - private IQueryable availableBeatmaps => realm.Realm - .All() - .Where(s => !s.DeletePending); - private FilterControl filter; private Playlist list; @@ -105,7 +101,7 @@ namespace osu.Game.Overlays.Music // tests might bind externally, in which case we don't want to involve realm. if (beatmapSets.Count == 0) - beatmapSubscription = realm.RegisterForNotifications(realm => availableBeatmaps, beatmapsChanged); + beatmapSubscription = realm.RegisterForNotifications(r => r.All().Where(s => !s.DeletePending), beatmapsChanged); list.Items.BindTo(beatmapSets); beatmap.BindValueChanged(working => list.SelectedSet.Value = working.NewValue.BeatmapSetInfo, true); From dda513dd08f4aad11b847fef0da0ef4e669f35b0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 12:19:05 +0900 Subject: [PATCH 662/996] Change `PlaylistOverlay` to use `ILive` --- .../UserInterface/TestScenePlaylistOverlay.cs | 9 +-- osu.Game/Collections/BeatmapCollection.cs | 2 +- .../Collections/CollectionFilterDropdown.cs | 4 +- osu.Game/Overlays/Music/Playlist.cs | 17 ++--- osu.Game/Overlays/Music/PlaylistItem.cs | 66 ++++++++++--------- osu.Game/Overlays/Music/PlaylistOverlay.cs | 42 +++++++----- 6 files changed, 76 insertions(+), 64 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs index 7131c19471..1a3e38ddd7 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Game.Beatmaps; +using osu.Game.Database; using osu.Game.Graphics.Containers; using osu.Game.Overlays.Music; using osu.Game.Tests.Resources; @@ -18,11 +19,11 @@ namespace osu.Game.Tests.Visual.UserInterface { public class TestScenePlaylistOverlay : OsuManualInputManagerTestScene { - private readonly BindableList beatmapSets = new BindableList(); + private readonly BindableList> beatmapSets = new BindableList>(); private PlaylistOverlay playlistOverlay; - private BeatmapSetInfo first; + private ILive first; [SetUp] public void Setup() => Schedule(() => @@ -45,7 +46,7 @@ namespace osu.Game.Tests.Visual.UserInterface for (int i = 0; i < 100; i++) { - beatmapSets.Add(TestResources.CreateTestBeatmapSetInfo()); + beatmapSets.Add(TestResources.CreateTestBeatmapSetInfo().ToLiveUnmanaged()); } first = beatmapSets.First(); @@ -60,7 +61,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("hold 1st item handle", () => { - var handle = this.ChildrenOfType.PlaylistItemHandle>().First(); + var handle = this.ChildrenOfType>.PlaylistItemHandle>().First(); InputManager.MoveMouseTo(handle.ScreenSpaceDrawQuad.Centre); InputManager.PressButton(MouseButton.Left); }); diff --git a/osu.Game/Collections/BeatmapCollection.cs b/osu.Game/Collections/BeatmapCollection.cs index 1a739f824f..7e4b15ecf9 100644 --- a/osu.Game/Collections/BeatmapCollection.cs +++ b/osu.Game/Collections/BeatmapCollection.cs @@ -25,7 +25,7 @@ namespace osu.Game.Collections /// /// The beatmaps contained by the collection. /// - public readonly BindableList Beatmaps = new BindableList(); + public readonly BindableList Beatmaps = new BindableList(); /// /// The date when this collection was last modified. diff --git a/osu.Game/Collections/CollectionFilterDropdown.cs b/osu.Game/Collections/CollectionFilterDropdown.cs index 77bda00107..c46ba8e06e 100644 --- a/osu.Game/Collections/CollectionFilterDropdown.cs +++ b/osu.Game/Collections/CollectionFilterDropdown.cs @@ -38,7 +38,7 @@ namespace osu.Game.Collections } private readonly IBindableList collections = new BindableList(); - private readonly IBindableList beatmaps = new BindableList(); + private readonly IBindableList beatmaps = new BindableList(); private readonly BindableList filters = new BindableList(); [Resolved(CanBeNull = true)] @@ -196,7 +196,7 @@ namespace osu.Game.Collections private IBindable beatmap { get; set; } [CanBeNull] - private readonly BindableList collectionBeatmaps; + private readonly BindableList collectionBeatmaps; [NotNull] private readonly Bindable collectionName; diff --git a/osu.Game/Overlays/Music/Playlist.cs b/osu.Game/Overlays/Music/Playlist.cs index 0b15a3a1bc..c86146ff25 100644 --- a/osu.Game/Overlays/Music/Playlist.cs +++ b/osu.Game/Overlays/Music/Playlist.cs @@ -7,16 +7,17 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; +using osu.Game.Database; using osu.Game.Graphics.Containers; using osuTK; namespace osu.Game.Overlays.Music { - public class Playlist : OsuRearrangeableListContainer + public class Playlist : OsuRearrangeableListContainer> { - public Action RequestSelection; + public Action> RequestSelection; - public readonly Bindable SelectedSet = new Bindable(); + public readonly Bindable> SelectedSet = new Bindable>(); public new MarginPadding Padding { @@ -26,23 +27,23 @@ namespace osu.Game.Overlays.Music public void Filter(FilterCriteria criteria) { - var items = (SearchContainer>)ListContainer; + var items = (SearchContainer>>)ListContainer; foreach (var item in items.OfType()) - item.InSelectedCollection = criteria.Collection?.Beatmaps.Any(b => item.Model.Equals(b.BeatmapSet)) ?? true; + item.InSelectedCollection = criteria.Collection?.Beatmaps.Any(b => item.Model.ID == b.BeatmapSet?.ID) ?? true; items.SearchTerm = criteria.SearchText; } - public BeatmapSetInfo FirstVisibleSet => Items.FirstOrDefault(i => ((PlaylistItem)ItemMap[i]).MatchingFilter); + public ILive FirstVisibleSet => Items.FirstOrDefault(i => ((PlaylistItem)ItemMap[i]).MatchingFilter); - protected override OsuRearrangeableListItem CreateOsuDrawable(BeatmapSetInfo item) => new PlaylistItem(item) + protected override OsuRearrangeableListItem> CreateOsuDrawable(ILive item) => new PlaylistItem(item) { SelectedSet = { BindTarget = SelectedSet }, RequestSelection = set => RequestSelection?.Invoke(set) }; - protected override FillFlowContainer> CreateListFillFlowContainer() => new SearchContainer> + protected override FillFlowContainer>> CreateListFillFlowContainer() => new SearchContainer>> { Spacing = new Vector2(0, 3), LayoutDuration = 200, diff --git a/osu.Game/Overlays/Music/PlaylistItem.cs b/osu.Game/Overlays/Music/PlaylistItem.cs index b31a03a64d..3f82580bfb 100644 --- a/osu.Game/Overlays/Music/PlaylistItem.cs +++ b/osu.Game/Overlays/Music/PlaylistItem.cs @@ -10,17 +10,18 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Beatmaps; +using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osuTK.Graphics; namespace osu.Game.Overlays.Music { - public class PlaylistItem : OsuRearrangeableListItem, IFilterable + public class PlaylistItem : OsuRearrangeableListItem>, IFilterable { - public readonly Bindable SelectedSet = new Bindable(); + public readonly Bindable> SelectedSet = new Bindable>(); - public Action RequestSelection; + public Action> RequestSelection; private TextFlowContainer text; private ITextPart titlePart; @@ -28,7 +29,7 @@ namespace osu.Game.Overlays.Music [Resolved] private OsuColour colours { get; set; } - public PlaylistItem(BeatmapSetInfo item) + public PlaylistItem(ILive item) : base(item) { Padding = new MarginPadding { Left = 5 }; @@ -44,34 +45,37 @@ namespace osu.Game.Overlays.Music { base.LoadComplete(); - var metadata = Model.Metadata; - - var title = new RomanisableString(metadata.TitleUnicode, metadata.Title); - var artist = new RomanisableString(metadata.ArtistUnicode, metadata.Artist); - - titlePart = text.AddText(title, sprite => sprite.Font = OsuFont.GetFont(weight: FontWeight.Regular)); - titlePart.DrawablePartsRecreated += _ => updateSelectionState(true); - - text.AddText(@" "); // to separate the title from the artist. - text.AddText(artist, sprite => + Model.PerformRead(m => { - sprite.Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold); - sprite.Colour = colours.Gray9; - sprite.Padding = new MarginPadding { Top = 1 }; + var metadata = m.Metadata; + + var title = new RomanisableString(metadata.TitleUnicode, metadata.Title); + var artist = new RomanisableString(metadata.ArtistUnicode, metadata.Artist); + + titlePart = text.AddText(title, sprite => sprite.Font = OsuFont.GetFont(weight: FontWeight.Regular)); + titlePart.DrawablePartsRecreated += _ => updateSelectionState(true); + + text.AddText(@" "); // to separate the title from the artist. + text.AddText(artist, sprite => + { + sprite.Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold); + sprite.Colour = colours.Gray9; + sprite.Padding = new MarginPadding { Top = 1 }; + }); + + SelectedSet.BindValueChanged(set => + { + bool newSelected = set.NewValue?.Equals(Model) == true; + + if (newSelected == selected) + return; + + selected = newSelected; + updateSelectionState(false); + }); + + updateSelectionState(true); }); - - SelectedSet.BindValueChanged(set => - { - bool newSelected = set.NewValue?.Equals(Model) == true; - - if (newSelected == selected) - return; - - selected = newSelected; - updateSelectionState(false); - }); - - updateSelectionState(true); } private bool selected; @@ -109,7 +113,7 @@ namespace osu.Game.Overlays.Music } } - public IEnumerable FilterTerms => Model.Metadata.GetSearchableTerms(); + public IEnumerable FilterTerms => Model.PerformRead(m => m.Metadata.GetSearchableTerms()); private bool matchingFilter = true; diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs index ffddf168ed..4b10bb779e 100644 --- a/osu.Game/Overlays/Music/PlaylistOverlay.cs +++ b/osu.Game/Overlays/Music/PlaylistOverlay.cs @@ -24,9 +24,9 @@ namespace osu.Game.Overlays.Music private const float transition_duration = 600; private const float playlist_height = 510; - public IBindableList BeatmapSets => beatmapSets; + public IBindableList> BeatmapSets => beatmapSets; - private readonly BindableList beatmapSets = new BindableList(); + private readonly BindableList> beatmapSets = new BindableList>(); private readonly Bindable beatmap = new Bindable(); @@ -85,13 +85,16 @@ namespace osu.Game.Overlays.Music filter.Search.OnCommit += (sender, newText) => { - BeatmapInfo toSelect = list.FirstVisibleSet?.Beatmaps.FirstOrDefault(); - - if (toSelect != null) + list.FirstVisibleSet.PerformRead(set => { - beatmap.Value = beatmaps.GetWorkingBeatmap(toSelect); - beatmap.Value.Track.Restart(); - } + BeatmapInfo toSelect = set.Beatmaps.FirstOrDefault(); + + if (toSelect != null) + { + beatmap.Value = beatmaps.GetWorkingBeatmap(toSelect); + beatmap.Value.Track.Restart(); + } + }); }; } @@ -104,7 +107,7 @@ namespace osu.Game.Overlays.Music beatmapSubscription = realm.RegisterForNotifications(r => r.All().Where(s => !s.DeletePending), beatmapsChanged); list.Items.BindTo(beatmapSets); - beatmap.BindValueChanged(working => list.SelectedSet.Value = working.NewValue.BeatmapSetInfo, true); + beatmap.BindValueChanged(working => list.SelectedSet.Value = working.NewValue.BeatmapSetInfo.ToLive(realm), true); } private void beatmapsChanged(IRealmCollection sender, ChangeSet changes, Exception error) @@ -113,12 +116,12 @@ namespace osu.Game.Overlays.Music { beatmapSets.Clear(); // must use AddRange to avoid RearrangeableList sort overhead per add op. - beatmapSets.AddRange(sender); + beatmapSets.AddRange(sender.ToLive(realm)); return; } foreach (int i in changes.InsertedIndices) - beatmapSets.Insert(i, sender[i]); + beatmapSets.Insert(i, sender[i].ToLive(realm)); foreach (int i in changes.DeletedIndices.OrderByDescending(i => i)) beatmapSets.RemoveAt(i); @@ -141,16 +144,19 @@ namespace osu.Game.Overlays.Music this.FadeOut(transition_duration); } - private void itemSelected(BeatmapSetInfo set) + private void itemSelected(ILive beatmapSet) { - if (set.Equals((beatmap.Value?.BeatmapSetInfo))) + beatmapSet.PerformRead(set => { - beatmap.Value?.Track.Seek(0); - return; - } + if (set.Equals((beatmap.Value?.BeatmapSetInfo))) + { + beatmap.Value?.Track.Seek(0); + return; + } - beatmap.Value = beatmaps.GetWorkingBeatmap(set.Beatmaps.First()); - beatmap.Value.Track.Restart(); + beatmap.Value = beatmaps.GetWorkingBeatmap(set.Beatmaps.First()); + beatmap.Value.Track.Restart(); + }); } protected override void Dispose(bool isDisposing) From d76822b6857dbad12737662352157d1bb4b6ff69 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 17 Jan 2022 18:32:57 +0900 Subject: [PATCH 663/996] Avoid creating realm contexts or refetching when accessing `RealmLive` from the update thread --- osu.Game/Database/RealmLive.cs | 38 +++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/RealmLive.cs b/osu.Game/Database/RealmLive.cs index f06ba1eff9..c572d52cc6 100644 --- a/osu.Game/Database/RealmLive.cs +++ b/osu.Game/Database/RealmLive.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics; using osu.Framework.Development; using Realms; @@ -22,7 +23,9 @@ namespace osu.Game.Database /// /// The original live data used to create this instance. /// - private readonly T data; + private T data; + + private bool dataIsFromUpdateThread; private readonly RealmAccess realm; @@ -37,6 +40,7 @@ namespace osu.Game.Database this.realm = realm; ID = data.ID; + dataIsFromUpdateThread = ThreadSafety.IsUpdateThread; } /// @@ -51,7 +55,17 @@ namespace osu.Game.Database return; } - realm.Run(r => perform(retrieveFromID(r, ID))); + realm.Run(r => + { + if (ThreadSafety.IsUpdateThread) + { + ensureDataIsFromUpdateThread(); + perform(data); + return; + } + + perform(retrieveFromID(r, ID)); + }); } /// @@ -63,6 +77,12 @@ namespace osu.Game.Database if (!IsManaged) return perform(data); + if (ThreadSafety.IsUpdateThread) + { + ensureDataIsFromUpdateThread(); + return perform(data); + } + return realm.Run(r => { var returnData = perform(retrieveFromID(r, ID)); @@ -101,10 +121,22 @@ namespace osu.Game.Database if (!ThreadSafety.IsUpdateThread) throw new InvalidOperationException($"Can't use {nameof(Value)} on managed objects from non-update threads"); - return realm.Realm.Find(ID); + ensureDataIsFromUpdateThread(); + return data; } } + private void ensureDataIsFromUpdateThread() + { + Debug.Assert(ThreadSafety.IsUpdateThread); + + if (dataIsFromUpdateThread) + return; + + dataIsFromUpdateThread = true; + data = retrieveFromID(realm.Realm, ID); + } + private T retrieveFromID(Realm realm, Guid id) { var found = realm.Find(ID); From 56b06f34f01818063f18cf5d7d1c952f328f7dc9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 02:10:14 +0900 Subject: [PATCH 664/996] Fix `RealmLive` not refetching if update thread context was closed at some point --- osu.Game/Database/RealmLive.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmLive.cs b/osu.Game/Database/RealmLive.cs index c572d52cc6..d420c3b5c7 100644 --- a/osu.Game/Database/RealmLive.cs +++ b/osu.Game/Database/RealmLive.cs @@ -130,7 +130,7 @@ namespace osu.Game.Database { Debug.Assert(ThreadSafety.IsUpdateThread); - if (dataIsFromUpdateThread) + if (dataIsFromUpdateThread && !data.Realm.IsClosed) return; dataIsFromUpdateThread = true; From c7947b34890aa64297cc22139ec9f6b683c81492 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 12:42:24 +0900 Subject: [PATCH 665/996] Add statistics for `Live` usage --- osu.Game/Database/RealmLive.cs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmLive.cs b/osu.Game/Database/RealmLive.cs index d420c3b5c7..76a17e860f 100644 --- a/osu.Game/Database/RealmLive.cs +++ b/osu.Game/Database/RealmLive.cs @@ -4,6 +4,7 @@ using System; using System.Diagnostics; using osu.Framework.Development; +using osu.Framework.Statistics; using Realms; #nullable enable @@ -14,7 +15,7 @@ namespace osu.Game.Database /// Provides a method of working with realm objects over longer application lifetimes. /// /// The underlying object type. - public class RealmLive : ILive where T : RealmObject, IHasGuidPrimaryKey + public class RealmLive : RealmLive, ILive where T : RealmObject, IHasGuidPrimaryKey { public Guid ID { get; } @@ -65,6 +66,7 @@ namespace osu.Game.Database } perform(retrieveFromID(r, ID)); + USAGE_ASYNC.Value++; }); } @@ -86,6 +88,7 @@ namespace osu.Game.Database return realm.Run(r => { var returnData = perform(retrieveFromID(r, ID)); + USAGE_ASYNC.Value++; if (returnData is RealmObjectBase realmObject && realmObject.IsManaged) throw new InvalidOperationException(@$"Managed realm objects should not exit the scope of {nameof(PerformRead)}."); @@ -108,6 +111,7 @@ namespace osu.Game.Database var transaction = t.Realm.BeginWrite(); perform(t); transaction.Commit(); + WRITES.Value++; }); } @@ -131,10 +135,14 @@ namespace osu.Game.Database Debug.Assert(ThreadSafety.IsUpdateThread); if (dataIsFromUpdateThread && !data.Realm.IsClosed) + { + USAGE_UPDATE_IMMEDIATE.Value++; return; + } dataIsFromUpdateThread = true; data = retrieveFromID(realm.Realm, ID); + USAGE_UPDATE_REFETCH.Value++; } private T retrieveFromID(Realm realm, Guid id) @@ -157,4 +165,12 @@ namespace osu.Game.Database public override string ToString() => PerformRead(i => i.ToString()); } + + public class RealmLive + { + protected static readonly GlobalStatistic WRITES = GlobalStatistics.Get(@"Realm", @"Live writes"); + protected static readonly GlobalStatistic USAGE_UPDATE_IMMEDIATE = GlobalStatistics.Get(@"Realm", @"Live update read (fast)"); + protected static readonly GlobalStatistic USAGE_UPDATE_REFETCH = GlobalStatistics.Get(@"Realm", @"Live update read (slow)"); + protected static readonly GlobalStatistic USAGE_ASYNC = GlobalStatistics.Get(@"Realm", @"Live async read"); + } } From 7ca73f7e6def6c7ae3447ba79fc0b475984c9b74 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 12:51:09 +0900 Subject: [PATCH 666/996] Don't auto-unblock realm when user has manually pressed unblock button --- .../Overlays/Settings/Sections/DebugSettings/MemorySettings.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs b/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs index 3b94cae171..f26326a220 100644 --- a/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs +++ b/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs @@ -73,6 +73,9 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings void unblock() { + if (token == null) + return; + token?.Dispose(); token = null; From d37c3c463e5b82aaa7084e1e748ead6651eb2ba9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 13:29:12 +0900 Subject: [PATCH 667/996] Move statistics to static class --- osu.Game/Database/RealmLive.cs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/osu.Game/Database/RealmLive.cs b/osu.Game/Database/RealmLive.cs index 76a17e860f..13b9bc2704 100644 --- a/osu.Game/Database/RealmLive.cs +++ b/osu.Game/Database/RealmLive.cs @@ -15,7 +15,7 @@ namespace osu.Game.Database /// Provides a method of working with realm objects over longer application lifetimes. /// /// The underlying object type. - public class RealmLive : RealmLive, ILive where T : RealmObject, IHasGuidPrimaryKey + public class RealmLive : ILive where T : RealmObject, IHasGuidPrimaryKey { public Guid ID { get; } @@ -66,7 +66,7 @@ namespace osu.Game.Database } perform(retrieveFromID(r, ID)); - USAGE_ASYNC.Value++; + RealmLiveStatistics.USAGE_ASYNC.Value++; }); } @@ -88,7 +88,7 @@ namespace osu.Game.Database return realm.Run(r => { var returnData = perform(retrieveFromID(r, ID)); - USAGE_ASYNC.Value++; + RealmLiveStatistics.USAGE_ASYNC.Value++; if (returnData is RealmObjectBase realmObject && realmObject.IsManaged) throw new InvalidOperationException(@$"Managed realm objects should not exit the scope of {nameof(PerformRead)}."); @@ -111,7 +111,7 @@ namespace osu.Game.Database var transaction = t.Realm.BeginWrite(); perform(t); transaction.Commit(); - WRITES.Value++; + RealmLiveStatistics.WRITES.Value++; }); } @@ -136,13 +136,13 @@ namespace osu.Game.Database if (dataIsFromUpdateThread && !data.Realm.IsClosed) { - USAGE_UPDATE_IMMEDIATE.Value++; + RealmLiveStatistics.USAGE_UPDATE_IMMEDIATE.Value++; return; } dataIsFromUpdateThread = true; data = retrieveFromID(realm.Realm, ID); - USAGE_UPDATE_REFETCH.Value++; + RealmLiveStatistics.USAGE_UPDATE_REFETCH.Value++; } private T retrieveFromID(Realm realm, Guid id) @@ -166,11 +166,11 @@ namespace osu.Game.Database public override string ToString() => PerformRead(i => i.ToString()); } - public class RealmLive + internal static class RealmLiveStatistics { - protected static readonly GlobalStatistic WRITES = GlobalStatistics.Get(@"Realm", @"Live writes"); - protected static readonly GlobalStatistic USAGE_UPDATE_IMMEDIATE = GlobalStatistics.Get(@"Realm", @"Live update read (fast)"); - protected static readonly GlobalStatistic USAGE_UPDATE_REFETCH = GlobalStatistics.Get(@"Realm", @"Live update read (slow)"); - protected static readonly GlobalStatistic USAGE_ASYNC = GlobalStatistics.Get(@"Realm", @"Live async read"); + public static readonly GlobalStatistic WRITES = GlobalStatistics.Get(@"Realm", @"Live writes"); + public static readonly GlobalStatistic USAGE_UPDATE_IMMEDIATE = GlobalStatistics.Get(@"Realm", @"Live update read (fast)"); + public static readonly GlobalStatistic USAGE_UPDATE_REFETCH = GlobalStatistics.Get(@"Realm", @"Live update read (slow)"); + public static readonly GlobalStatistic USAGE_ASYNC = GlobalStatistics.Get(@"Realm", @"Live async read"); } } From cd71ec0edd6576384418b45683ae3615dbdca669 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 13:37:33 +0900 Subject: [PATCH 668/996] Remove `ILive<>` interface (and use `abstract Live<>` instead) --- .../Database/BeatmapImporterTests.cs | 8 +++--- osu.Game.Tests/Database/RealmLiveTests.cs | 16 +++++------ ...eneOnlinePlayBeatmapAvailabilityTracker.cs | 4 +-- osu.Game.Tests/Skins/IO/ImportSkinTest.cs | 8 +++--- .../Gameplay/TestSceneReplayDownloadButton.cs | 2 +- .../TestSceneDrawableRoomPlaylist.cs | 2 +- osu.Game/Beatmaps/BeatmapManager.cs | 14 +++++----- osu.Game/Database/IModelImporter.cs | 10 +++---- osu.Game/Database/IPostImports.cs | 4 +-- osu.Game/Database/LegacyModelImporter.cs | 2 +- osu.Game/Database/{ILive.cs => Live.cs} | 27 +++++++++++++------ osu.Game/Database/RealmLive.cs | 20 +++++--------- osu.Game/Database/RealmLiveUnmanaged.cs | 27 ++++++++----------- osu.Game/Database/RealmObjectExtensions.cs | 12 ++++----- osu.Game/OsuGame.cs | 4 +-- .../Overlays/Settings/Sections/SkinSection.cs | 14 +++++----- osu.Game/Scoring/ScoreManager.cs | 10 +++---- osu.Game/Skinning/Skin.cs | 2 +- osu.Game/Skinning/SkinManager.cs | 14 +++++----- osu.Game/Stores/RealmArchiveModelImporter.cs | 16 +++++------ 20 files changed, 108 insertions(+), 108 deletions(-) rename osu.Game/Database/{ILive.cs => Live.cs} (65%) diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index 69dd2d930a..2c7d0211a0 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -43,7 +43,7 @@ namespace osu.Game.Tests.Database using (var importer = new BeatmapModelManager(realm, storage)) using (new RulesetStore(realm, storage)) { - ILive? beatmapSet; + Live? beatmapSet; using (var reader = new ZipArchiveReader(TestResources.GetTestBeatmapStream())) beatmapSet = await importer.Import(reader); @@ -87,7 +87,7 @@ namespace osu.Game.Tests.Database using (var importer = new BeatmapModelManager(realm, storage)) using (new RulesetStore(realm, storage)) { - ILive? beatmapSet; + Live? beatmapSet; using (var reader = new ZipArchiveReader(TestResources.GetTestBeatmapStream())) beatmapSet = await importer.Import(reader); @@ -144,7 +144,7 @@ namespace osu.Game.Tests.Database using (var importer = new BeatmapModelManager(realm, storage)) using (new RulesetStore(realm, storage)) { - ILive? imported; + Live? imported; using (var reader = new ZipArchiveReader(TestResources.GetTestBeatmapStream())) imported = await importer.Import(reader); @@ -219,7 +219,7 @@ namespace osu.Game.Tests.Database string? tempPath = TestResources.GetTestBeatmapForImport(); - ILive? importedSet; + Live? importedSet; using (var stream = File.OpenRead(tempPath)) { diff --git a/osu.Game.Tests/Database/RealmLiveTests.cs b/osu.Game.Tests/Database/RealmLiveTests.cs index 2e3f708f79..3f81b36378 100644 --- a/osu.Game.Tests/Database/RealmLiveTests.cs +++ b/osu.Game.Tests/Database/RealmLiveTests.cs @@ -23,9 +23,9 @@ namespace osu.Game.Tests.Database { RunTestWithRealm((realm, _) => { - ILive beatmap = realm.Run(r => r.Write(_ => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()))).ToLive(realm)); + Live beatmap = realm.Run(r => r.Write(_ => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()))).ToLive(realm)); - ILive beatmap2 = realm.Run(r => r.All().First().ToLive(realm)); + Live beatmap2 = realm.Run(r => r.All().First().ToLive(realm)); Assert.AreEqual(beatmap, beatmap2); }); @@ -38,7 +38,7 @@ namespace osu.Game.Tests.Database { var beatmap = new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()); - ILive? liveBeatmap = null; + Live? liveBeatmap = null; realm.Run(r => { @@ -100,7 +100,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealm((realm, _) => { - ILive? liveBeatmap = null; + Live? liveBeatmap = null; Task.Factory.StartNew(() => { realm.Run(threadContext => @@ -129,7 +129,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealm((realm, _) => { - ILive? liveBeatmap = null; + Live? liveBeatmap = null; Task.Factory.StartNew(() => { realm.Run(threadContext => @@ -170,7 +170,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealm((realm, _) => { - ILive? liveBeatmap = null; + Live? liveBeatmap = null; Task.Factory.StartNew(() => { @@ -209,7 +209,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealm((realm, _) => { - ILive? liveBeatmap = null; + Live? liveBeatmap = null; Task.Factory.StartNew(() => { realm.Run(threadContext => @@ -242,7 +242,7 @@ namespace osu.Game.Tests.Database realm.RegisterCustomSubscription(outerRealm => { outerRealm.All().QueryAsyncWithNotifications(gotChange); - ILive? liveBeatmap = null; + Live? liveBeatmap = null; Task.Factory.StartNew(() => { diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index 95c15367aa..f9161816e7 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -164,7 +164,7 @@ namespace osu.Game.Tests.Online { public TaskCompletionSource AllowImport = new TaskCompletionSource(); - public ILive CurrentImport { get; private set; } + public Live CurrentImport { get; private set; } public TestBeatmapManager(Storage storage, RealmAccess realm, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore resources, GameHost host = null, WorkingBeatmap defaultBeatmap = null) : base(storage, realm, rulesets, api, audioManager, resources, host, defaultBeatmap) @@ -186,7 +186,7 @@ namespace osu.Game.Tests.Online this.testBeatmapManager = testBeatmapManager; } - public override ILive Import(BeatmapSetInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) + public override Live Import(BeatmapSetInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) { testBeatmapManager.AllowImport.Task.WaitSafely(); return (testBeatmapManager.CurrentImport = base.Import(item, archive, lowPriority, cancellationToken)); diff --git a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs index 3f063264e0..9b0facd625 100644 --- a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs +++ b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs @@ -235,7 +235,7 @@ namespace osu.Game.Tests.Skins.IO #endregion - private void assertCorrectMetadata(ILive import1, string name, string creator, OsuGameBase osu) + private void assertCorrectMetadata(Live import1, string name, string creator, OsuGameBase osu) { import1.PerformRead(i => { @@ -250,7 +250,7 @@ namespace osu.Game.Tests.Skins.IO }); } - private void assertImportedBoth(ILive import1, ILive import2) + private void assertImportedBoth(Live import1, Live import2) { import1.PerformRead(i1 => import2.PerformRead(i2 => { @@ -260,7 +260,7 @@ namespace osu.Game.Tests.Skins.IO })); } - private void assertImportedOnce(ILive import1, ILive import2) + private void assertImportedOnce(Live import1, Live import2) { import1.PerformRead(i1 => import2.PerformRead(i2 => { @@ -334,7 +334,7 @@ namespace osu.Game.Tests.Skins.IO } } - private async Task> loadSkinIntoOsu(OsuGameBase osu, ArchiveReader archive = null) + private async Task> loadSkinIntoOsu(OsuGameBase osu, ArchiveReader archive = null) { var skinManager = osu.Dependencies.Get(); return await skinManager.Import(archive); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs index a12171401a..8b7e1c4e58 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs @@ -132,7 +132,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestScoreImportThenDelete() { - ILive imported = null; + Live imported = null; AddStep("create button without replay", () => { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index 92accb0cd1..5c8c90e166 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -153,7 +153,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public void TestDownloadButtonHiddenWhenBeatmapExists() { var beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo; - ILive imported = null; + Live imported = null; Debug.Assert(beatmap.BeatmapSet != null); diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index a1c1982f00..414b7cd12b 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -182,7 +182,7 @@ namespace osu.Game.Beatmaps /// /// The query. /// The first result for the provided query, or null if no results were found. - public ILive? QueryBeatmapSet(Expression> query) + public Live? QueryBeatmapSet(Expression> query) { return realm.Run(r => r.All().FirstOrDefault(query)?.ToLive(realm)); } @@ -279,22 +279,22 @@ namespace osu.Game.Beatmaps return beatmapModelManager.Import(tasks); } - public Task>> Import(ProgressNotification notification, params ImportTask[] tasks) + public Task>> Import(ProgressNotification notification, params ImportTask[] tasks) { return beatmapModelManager.Import(notification, tasks); } - public Task?> Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default) + public Task?> Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default) { return beatmapModelManager.Import(task, lowPriority, cancellationToken); } - public Task?> Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default) + public Task?> Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default) { return beatmapModelManager.Import(archive, lowPriority, cancellationToken); } - public ILive? Import(BeatmapSetInfo item, ArchiveReader? archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) + public Live? Import(BeatmapSetInfo item, ArchiveReader? archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) { return beatmapModelManager.Import(item, archive, lowPriority, cancellationToken); } @@ -323,7 +323,7 @@ namespace osu.Game.Beatmaps return workingBeatmapCache.GetWorkingBeatmap(importedBeatmap); } - public WorkingBeatmap GetWorkingBeatmap(ILive? importedBeatmap) + public WorkingBeatmap GetWorkingBeatmap(Live? importedBeatmap) { WorkingBeatmap working = workingBeatmapCache.GetWorkingBeatmap(null); @@ -367,7 +367,7 @@ namespace osu.Game.Beatmaps #region Implementation of IPostImports - public Action>>? PostImport + public Action>>? PostImport { set => beatmapModelManager.PostImport = value; } diff --git a/osu.Game/Database/IModelImporter.cs b/osu.Game/Database/IModelImporter.cs index 3047a1d30a..90df13477e 100644 --- a/osu.Game/Database/IModelImporter.cs +++ b/osu.Game/Database/IModelImporter.cs @@ -16,9 +16,9 @@ namespace osu.Game.Database /// /// The model type. public interface IModelImporter : IPostNotifications, IPostImports, ICanAcceptFiles - where TModel : class + where TModel : class, IHasGuidPrimaryKey { - Task>> Import(ProgressNotification notification, params ImportTask[] tasks); + Task>> Import(ProgressNotification notification, params ImportTask[] tasks); /// /// Import one from the filesystem and delete the file on success. @@ -28,7 +28,7 @@ namespace osu.Game.Database /// Whether this is a low priority import. /// An optional cancellation token. /// The imported model, if successful. - Task?> Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default); + Task?> Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default); /// /// Silently import an item from an . @@ -36,7 +36,7 @@ namespace osu.Game.Database /// The archive to be imported. /// Whether this is a low priority import. /// An optional cancellation token. - Task?> Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default); + Task?> Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default); /// /// Silently import an item from a . @@ -45,7 +45,7 @@ namespace osu.Game.Database /// An optional archive to use for model population. /// Whether this is a low priority import. /// An optional cancellation token. - ILive? Import(TModel item, ArchiveReader? archive = null, bool lowPriority = false, CancellationToken cancellationToken = default); + Live? Import(TModel item, ArchiveReader? archive = null, bool lowPriority = false, CancellationToken cancellationToken = default); /// /// A user displayable name for the model type associated with this manager. diff --git a/osu.Game/Database/IPostImports.cs b/osu.Game/Database/IPostImports.cs index adb3a7108d..6f047098da 100644 --- a/osu.Game/Database/IPostImports.cs +++ b/osu.Game/Database/IPostImports.cs @@ -9,11 +9,11 @@ using System.Collections.Generic; namespace osu.Game.Database { public interface IPostImports - where TModel : class + where TModel : class, IHasGuidPrimaryKey { /// /// Fired when the user requests to view the resulting import. /// - public Action>>? PostImport { set; } + public Action>>? PostImport { set; } } } diff --git a/osu.Game/Database/LegacyModelImporter.cs b/osu.Game/Database/LegacyModelImporter.cs index dacb7327ea..d85fb5aab2 100644 --- a/osu.Game/Database/LegacyModelImporter.cs +++ b/osu.Game/Database/LegacyModelImporter.cs @@ -14,7 +14,7 @@ namespace osu.Game.Database /// A class which handles importing legacy user data of a single type from osu-stable. /// public abstract class LegacyModelImporter - where TModel : class + where TModel : class, IHasGuidPrimaryKey { /// /// The relative path from osu-stable's data directory to import items from. diff --git a/osu.Game/Database/ILive.cs b/osu.Game/Database/Live.cs similarity index 65% rename from osu.Game/Database/ILive.cs rename to osu.Game/Database/Live.cs index 3011754bc1..6256902e17 100644 --- a/osu.Game/Database/ILive.cs +++ b/osu.Game/Database/Live.cs @@ -3,39 +3,41 @@ using System; +#nullable enable + namespace osu.Game.Database { /// /// A wrapper to provide access to database backed classes in a thread-safe manner. /// /// The databased type. - public interface ILive : IEquatable> - where T : class // TODO: Add IHasGuidPrimaryKey once we don't need EF support any more. + public abstract class Live : IEquatable> + where T : class, IHasGuidPrimaryKey { - Guid ID { get; } + public Guid ID { get; } /// /// Perform a read operation on this live object. /// /// The action to perform. - void PerformRead(Action perform); + public abstract void PerformRead(Action perform); /// /// Perform a read operation on this live object. /// /// The action to perform. - TReturn PerformRead(Func perform); + public abstract TReturn PerformRead(Func perform); /// /// Perform a write operation on this live object. /// /// The action to perform. - void PerformWrite(Action perform); + public abstract void PerformWrite(Action perform); /// /// Whether this instance is tracking data which is managed by the database backing. /// - bool IsManaged { get; } + public abstract bool IsManaged { get; } /// /// Resolve the value of this instance on the update thread. @@ -43,6 +45,15 @@ namespace osu.Game.Database /// /// After resolving, the data should not be passed between threads. /// - T Value { get; } + public abstract T Value { get; } + + protected Live(Guid id) + { + ID = id; + } + + public bool Equals(Live? other) => ID == other?.ID; + + public override string ToString() => PerformRead(i => i.ToString()); } } diff --git a/osu.Game/Database/RealmLive.cs b/osu.Game/Database/RealmLive.cs index 13b9bc2704..186e801425 100644 --- a/osu.Game/Database/RealmLive.cs +++ b/osu.Game/Database/RealmLive.cs @@ -15,11 +15,9 @@ namespace osu.Game.Database /// Provides a method of working with realm objects over longer application lifetimes. /// /// The underlying object type. - public class RealmLive : ILive where T : RealmObject, IHasGuidPrimaryKey + public class RealmLive : Live where T : RealmObject, IHasGuidPrimaryKey { - public Guid ID { get; } - - public bool IsManaged => data.IsManaged; + public override bool IsManaged => data.IsManaged; /// /// The original live data used to create this instance. @@ -36,11 +34,11 @@ namespace osu.Game.Database /// The realm data. /// The realm factory the data was sourced from. May be null for an unmanaged object. public RealmLive(T data, RealmAccess realm) + : base(data.ID) { this.data = data; this.realm = realm; - ID = data.ID; dataIsFromUpdateThread = ThreadSafety.IsUpdateThread; } @@ -48,7 +46,7 @@ namespace osu.Game.Database /// Perform a read operation on this live object. /// /// The action to perform. - public void PerformRead(Action perform) + public override void PerformRead(Action perform) { if (!IsManaged) { @@ -74,7 +72,7 @@ namespace osu.Game.Database /// Perform a read operation on this live object. /// /// The action to perform. - public TReturn PerformRead(Func perform) + public override TReturn PerformRead(Func perform) { if (!IsManaged) return perform(data); @@ -101,7 +99,7 @@ namespace osu.Game.Database /// Perform a write operation on this live object. /// /// The action to perform. - public void PerformWrite(Action perform) + public override void PerformWrite(Action perform) { if (!IsManaged) throw new InvalidOperationException(@"Can't perform writes on a non-managed underlying value"); @@ -115,7 +113,7 @@ namespace osu.Game.Database }); } - public T Value + public override T Value { get { @@ -160,10 +158,6 @@ namespace osu.Game.Database return found; } - - public bool Equals(ILive? other) => ID == other?.ID; - - public override string ToString() => PerformRead(i => i.ToString()); } internal static class RealmLiveStatistics diff --git a/osu.Game/Database/RealmLiveUnmanaged.cs b/osu.Game/Database/RealmLiveUnmanaged.cs index 97f2faa656..1080f3b8c7 100644 --- a/osu.Game/Database/RealmLiveUnmanaged.cs +++ b/osu.Game/Database/RealmLiveUnmanaged.cs @@ -13,13 +13,19 @@ namespace osu.Game.Database /// Usually used for testing purposes where the instance is never required to be managed. /// /// The underlying object type. - public class RealmLiveUnmanaged : ILive where T : RealmObjectBase, IHasGuidPrimaryKey + public class RealmLiveUnmanaged : Live where T : RealmObjectBase, IHasGuidPrimaryKey { + /// + /// The original live data used to create this instance. + /// + public override T Value { get; } + /// /// Construct a new instance of live realm data. /// /// The realm data. public RealmLiveUnmanaged(T data) + : base(data.ID) { if (data.IsManaged) throw new InvalidOperationException($"Cannot use {nameof(RealmLiveUnmanaged)} with managed instances"); @@ -27,23 +33,12 @@ namespace osu.Game.Database Value = data; } - public bool Equals(ILive? other) => ID == other?.ID; + public override void PerformRead(Action perform) => perform(Value); - public override string ToString() => Value.ToString(); + public override TReturn PerformRead(Func perform) => perform(Value); - public Guid ID => Value.ID; + public override void PerformWrite(Action perform) => throw new InvalidOperationException(@"Can't perform writes on a non-managed underlying value"); - public void PerformRead(Action perform) => perform(Value); - - public TReturn PerformRead(Func perform) => perform(Value); - - public void PerformWrite(Action perform) => throw new InvalidOperationException(@"Can't perform writes on a non-managed underlying value"); - - public bool IsManaged => false; - - /// - /// The original live data used to create this instance. - /// - public T Value { get; } + public override bool IsManaged => false; } } diff --git a/osu.Game/Database/RealmObjectExtensions.cs b/osu.Game/Database/RealmObjectExtensions.cs index d4f8978ac5..dba8633f53 100644 --- a/osu.Game/Database/RealmObjectExtensions.cs +++ b/osu.Game/Database/RealmObjectExtensions.cs @@ -204,25 +204,25 @@ namespace osu.Game.Database private static void copyChangesToRealm(T source, T destination) where T : RealmObjectBase => write_mapper.Map(source, destination); - public static List> ToLiveUnmanaged(this IEnumerable realmList) + public static List> ToLiveUnmanaged(this IEnumerable realmList) where T : RealmObject, IHasGuidPrimaryKey { - return realmList.Select(l => new RealmLiveUnmanaged(l)).Cast>().ToList(); + return realmList.Select(l => new RealmLiveUnmanaged(l)).Cast>().ToList(); } - public static ILive ToLiveUnmanaged(this T realmObject) + public static Live ToLiveUnmanaged(this T realmObject) where T : RealmObject, IHasGuidPrimaryKey { return new RealmLiveUnmanaged(realmObject); } - public static List> ToLive(this IEnumerable realmList, RealmAccess realm) + public static List> ToLive(this IEnumerable realmList, RealmAccess realm) where T : RealmObject, IHasGuidPrimaryKey { - return realmList.Select(l => new RealmLive(l, realm)).Cast>().ToList(); + return realmList.Select(l => new RealmLive(l, realm)).Cast>().ToList(); } - public static ILive ToLive(this T realmObject, RealmAccess realm) + public static Live ToLive(this T realmObject, RealmAccess realm) where T : RealmObject, IHasGuidPrimaryKey { return new RealmLive(realmObject, realm); diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 5b3abc54d3..c2e1b25d94 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -249,7 +249,7 @@ namespace osu.Game SkinManager.CurrentSkinInfo.ValueChanged += skin => configSkin.Value = skin.NewValue.ID.ToString(); configSkin.ValueChanged += skinId => { - ILive skinInfo = null; + Live skinInfo = null; if (Guid.TryParse(skinId.NewValue, out var guid)) skinInfo = SkinManager.Query(s => s.ID == guid); @@ -439,7 +439,7 @@ namespace osu.Game /// public void PresentBeatmap(IBeatmapSetInfo beatmap, Predicate difficultyCriteria = null) { - ILive databasedSet = null; + Live databasedSet = null; if (beatmap.OnlineID > 0) databasedSet = BeatmapManager.QueryBeatmapSet(s => s.OnlineID == beatmap.OnlineID); diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index 8ab296c0a8..0846c023c1 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -32,16 +32,16 @@ namespace osu.Game.Overlays.Settings.Sections Icon = FontAwesome.Solid.PaintBrush }; - private readonly Bindable> dropdownBindable = new Bindable> { Default = DefaultSkin.CreateInfo().ToLiveUnmanaged() }; + private readonly Bindable> dropdownBindable = new Bindable> { Default = DefaultSkin.CreateInfo().ToLiveUnmanaged() }; private readonly Bindable configBindable = new Bindable(); - private static readonly ILive random_skin_info = new SkinInfo + private static readonly Live random_skin_info = new SkinInfo { ID = SkinInfo.RANDOM_SKIN, Name = "", }.ToLiveUnmanaged(); - private List> skinItems; + private List> skinItems; [Resolved] private SkinManager skins { get; set; } @@ -118,7 +118,7 @@ namespace osu.Game.Overlays.Settings.Sections private void updateSelectedSkinFromConfig() { - ILive skin = null; + Live skin = null; if (Guid.TryParse(configBindable.Value, out var configId)) skin = skinDropdown.Items.FirstOrDefault(s => s.ID == configId); @@ -144,13 +144,13 @@ namespace osu.Game.Overlays.Settings.Sections realmSubscription?.Dispose(); } - private class SkinSettingsDropdown : SettingsDropdown> + private class SkinSettingsDropdown : SettingsDropdown> { - protected override OsuDropdown> CreateDropdown() => new SkinDropdownControl(); + protected override OsuDropdown> CreateDropdown() => new SkinDropdownControl(); private class SkinDropdownControl : DropdownControl { - protected override LocalisableString GenerateItemText(ILive item) => item.ToString(); + protected override LocalisableString GenerateItemText(Live item) => item.ToString(); } } diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 3a842a048a..8f665224ee 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -293,22 +293,22 @@ namespace osu.Game.Scoring public IEnumerable HandledExtensions => scoreModelManager.HandledExtensions; - public Task>> Import(ProgressNotification notification, params ImportTask[] tasks) + public Task>> Import(ProgressNotification notification, params ImportTask[] tasks) { return scoreModelManager.Import(notification, tasks); } - public Task> Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default) + public Task> Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default) { return scoreModelManager.Import(task, lowPriority, cancellationToken); } - public Task> Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default) + public Task> Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default) { return scoreModelManager.Import(archive, lowPriority, cancellationToken); } - public ILive Import(ScoreInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) + public Live Import(ScoreInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) { return scoreModelManager.Import(item, archive, lowPriority, cancellationToken); } @@ -322,7 +322,7 @@ namespace osu.Game.Scoring #region Implementation of IPresentImports - public Action>> PostImport + public Action>> PostImport { set => scoreModelManager.PostImport = value; } diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index 3685a26e26..931bdfed48 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -24,7 +24,7 @@ namespace osu.Game.Skinning { public abstract class Skin : IDisposable, ISkin { - public readonly ILive SkinInfo; + public readonly Live SkinInfo; private readonly IStorageResourceProvider resources; public SkinConfiguration Configuration { get; set; } diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 47c7bc060a..06bd0abc9f 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -47,7 +47,7 @@ namespace osu.Game.Skinning public readonly Bindable CurrentSkin = new Bindable(); - public readonly Bindable> CurrentSkinInfo = new Bindable>(Skinning.DefaultSkin.CreateInfo().ToLiveUnmanaged()) + public readonly Bindable> CurrentSkinInfo = new Bindable>(Skinning.DefaultSkin.CreateInfo().ToLiveUnmanaged()) { Default = Skinning.DefaultSkin.CreateInfo().ToLiveUnmanaged() }; @@ -176,7 +176,7 @@ namespace osu.Game.Skinning /// /// The query. /// The first result for the provided query, or null if no results were found. - public ILive Query(Expression> query) + public Live Query(Expression> query) { return realm.Run(r => r.All().FirstOrDefault(query)?.ToLive(realm)); } @@ -245,7 +245,7 @@ namespace osu.Game.Skinning set => skinModelManager.PostNotification = value; } - public Action>> PostImport + public Action>> PostImport { set => skinModelManager.PostImport = value; } @@ -262,22 +262,22 @@ namespace osu.Game.Skinning public IEnumerable HandledExtensions => skinModelManager.HandledExtensions; - public Task>> Import(ProgressNotification notification, params ImportTask[] tasks) + public Task>> Import(ProgressNotification notification, params ImportTask[] tasks) { return skinModelManager.Import(notification, tasks); } - public Task> Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default) + public Task> Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default) { return skinModelManager.Import(task, lowPriority, cancellationToken); } - public Task> Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default) + public Task> Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default) { return skinModelManager.Import(archive, lowPriority, cancellationToken); } - public ILive Import(SkinInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) + public Live Import(SkinInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) { return skinModelManager.Import(item, archive, lowPriority, cancellationToken); } diff --git a/osu.Game/Stores/RealmArchiveModelImporter.cs b/osu.Game/Stores/RealmArchiveModelImporter.cs index 43c1c7c888..3011bc0320 100644 --- a/osu.Game/Stores/RealmArchiveModelImporter.cs +++ b/osu.Game/Stores/RealmArchiveModelImporter.cs @@ -64,7 +64,7 @@ namespace osu.Game.Stores /// /// Fired when the user requests to view the resulting import. /// - public Action>>? PostImport { get; set; } + public Action>>? PostImport { get; set; } /// /// Set an endpoint for notifications to be posted to. @@ -104,7 +104,7 @@ namespace osu.Game.Stores return Import(notification, tasks); } - public async Task>> Import(ProgressNotification notification, params ImportTask[] tasks) + public async Task>> Import(ProgressNotification notification, params ImportTask[] tasks) { if (tasks.Length == 0) { @@ -118,7 +118,7 @@ namespace osu.Game.Stores int current = 0; - var imported = new List>(); + var imported = new List>(); bool isLowPriorityImport = tasks.Length > low_priority_import_batch_size; @@ -196,11 +196,11 @@ namespace osu.Game.Stores /// Whether this is a low priority import. /// An optional cancellation token. /// The imported model, if successful. - public async Task?> Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default) + public async Task?> Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); - ILive? import; + Live? import; using (ArchiveReader reader = task.GetReader()) import = await Import(reader, lowPriority, cancellationToken).ConfigureAwait(false); @@ -227,7 +227,7 @@ namespace osu.Game.Stores /// The archive to be imported. /// Whether this is a low priority import. /// An optional cancellation token. - public async Task?> Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default) + public async Task?> Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); @@ -320,7 +320,7 @@ namespace osu.Game.Stores /// An optional archive to use for model population. /// Whether this is a low priority import. /// An optional cancellation token. - public virtual ILive? Import(TModel item, ArchiveReader? archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) + public virtual Live? Import(TModel item, ArchiveReader? archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) { return Realm.Run(realm => { @@ -416,7 +416,7 @@ namespace osu.Game.Stores throw; } - return (ILive?)item.ToLive(Realm); + return (Live?)item.ToLive(Realm); }); } From 064468faada4ba6af341296f634978e774bcec03 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 25 Jan 2022 11:31:05 +0300 Subject: [PATCH 669/996] Refactor editor saving test scene for scalability --- .../Visual/Editing/TestSceneEditorSaving.cs | 152 ++++++++++-------- osu.Game/Beatmaps/BeatmapInfo.cs | 2 +- .../Tests/Visual/EditorSavingTestScene.cs | 67 ++++++++ 3 files changed, 154 insertions(+), 67 deletions(-) create mode 100644 osu.Game/Tests/Visual/EditorSavingTestScene.cs diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs index 96f815621c..58daab1ce2 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs @@ -4,97 +4,117 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; -using osu.Framework.Input; using osu.Framework.Testing; +using osu.Framework.Utils; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit; -using osu.Game.Screens.Edit.Setup; -using osu.Game.Screens.Menu; -using osu.Game.Screens.Select; +using osu.Game.Screens.Edit.Compose.Components.Timeline; using osuTK.Input; namespace osu.Game.Tests.Visual.Editing { - public class TestSceneEditorSaving : OsuGameTestScene + public class TestSceneEditorSaving : EditorSavingTestScene { - private Editor editor => Game.ChildrenOfType().FirstOrDefault(); - - private EditorBeatmap editorBeatmap => (EditorBeatmap)editor.Dependencies.Get(typeof(EditorBeatmap)); - - /// - /// Tests the general expected flow of creating a new beatmap, saving it, then loading it back from song select. - /// [Test] - public void TestNewBeatmapSaveThenLoad() + public void TestMetadata() { - AddStep("set default beatmap", () => Game.Beatmap.SetDefault()); - - PushAndConfirm(() => new EditorLoader()); - - AddUntilStep("wait for editor load", () => editor?.IsLoaded == true); - - AddUntilStep("wait for metadata screen load", () => editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); - - // We intentionally switch away from the metadata screen, else there is a feedback loop with the textbox handling which causes metadata changes below to get overwritten. - - AddStep("Enter compose mode", () => InputManager.Key(Key.F1)); - AddUntilStep("Wait for compose mode load", () => editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); - - AddStep("Set beat divisor", () => editor.Dependencies.Get().Value = 16); - AddStep("Set overall difficulty", () => editorBeatmap.Difficulty.OverallDifficulty = 7); AddStep("Set artist and title", () => { - editorBeatmap.BeatmapInfo.Metadata.Artist = "artist"; - editorBeatmap.BeatmapInfo.Metadata.Title = "title"; + EditorBeatmap.BeatmapInfo.Metadata.Artist = "artist"; + EditorBeatmap.BeatmapInfo.Metadata.Title = "title"; }); - AddStep("Set author", () => editorBeatmap.BeatmapInfo.Metadata.Author.Username = "author"); - AddStep("Set difficulty name", () => editorBeatmap.BeatmapInfo.DifficultyName = "difficulty"); + AddStep("Set author", () => EditorBeatmap.BeatmapInfo.Metadata.Author.Username = "author"); + AddStep("Set difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = "difficulty"); - AddStep("Add timing point", () => editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint())); + SaveEditor(); + 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"); + AddAssert("Beatmap has correct .osu file path", () => EditorBeatmap.BeatmapInfo.Path == "artist - title (author) [difficulty].osu"); + + ReloadEditorToSameBeatmap(); + + AddAssert("Beatmap still has correct metadata", () => EditorBeatmap.BeatmapInfo.Metadata.Artist == "artist" && EditorBeatmap.BeatmapInfo.Metadata.Title == "title"); + AddAssert("Beatmap still has correct author", () => EditorBeatmap.BeatmapInfo.Metadata.Author.Username == "author"); + AddAssert("Beatmap still has correct difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName == "difficulty"); + AddAssert("Beatmap still has correct .osu file path", () => EditorBeatmap.BeatmapInfo.Path == "artist - title (author) [difficulty].osu"); + } + + [Test] + public void TestConfiguration() + { + double originalTimelineZoom = 0; + double changedTimelineZoom = 0; + + AddStep("Set beat divisor", () => Editor.Dependencies.Get().Value = 16); + AddStep("Set timeline zoom", () => + { + originalTimelineZoom = EditorBeatmap.BeatmapInfo.TimelineZoom; + + var timeline = Editor.ChildrenOfType().Single(); + InputManager.MoveMouseTo(timeline); + InputManager.PressKey(Key.AltLeft); + InputManager.ScrollVerticalBy(15f); + InputManager.ReleaseKey(Key.AltLeft); + }); + + AddAssert("Ensure timeline zoom changed", () => + { + changedTimelineZoom = EditorBeatmap.BeatmapInfo.TimelineZoom; + return !Precision.AlmostEquals(changedTimelineZoom, originalTimelineZoom); + }); + + SaveEditor(); + + AddAssert("Beatmap has correct beat divisor", () => EditorBeatmap.BeatmapInfo.BeatDivisor == 16); + AddAssert("Beatmap has correct timeline zoom", () => EditorBeatmap.BeatmapInfo.TimelineZoom == changedTimelineZoom); + + ReloadEditorToSameBeatmap(); + + AddAssert("Beatmap still has correct beat divisor", () => EditorBeatmap.BeatmapInfo.BeatDivisor == 16); + AddAssert("Beatmap still has correct timeline zoom", () => EditorBeatmap.BeatmapInfo.TimelineZoom == changedTimelineZoom); + } + + [Test] + public void TestDifficulty() + { + AddStep("Set overall difficulty", () => EditorBeatmap.Difficulty.OverallDifficulty = 7); + + SaveEditor(); + + AddAssert("Beatmap has correct overall difficulty", () => EditorBeatmap.Difficulty.OverallDifficulty == 7); + + ReloadEditorToSameBeatmap(); + + AddAssert("Beatmap still has correct overall difficulty", () => EditorBeatmap.Difficulty.OverallDifficulty == 7); + } + + [Test] + public void TestHitObjectPlacement() + { + AddStep("Add timing point", () => EditorBeatmap.ControlPointInfo.Add(500, new TimingControlPoint())); AddStep("Change to placement mode", () => InputManager.Key(Key.Number2)); AddStep("Move to playfield", () => InputManager.MoveMouseTo(Game.ScreenSpaceDrawQuad.Centre)); AddStep("Place single hitcircle", () => InputManager.Click(MouseButton.Left)); - checkMutations(); + SaveEditor(); + + AddAssert("Beatmap has correct timing point", () => EditorBeatmap.ControlPointInfo.TimingPoints.Single().Time == 500); // After placement these must be non-default as defaults are read-only. AddAssert("Placed object has non-default control points", () => - editorBeatmap.HitObjects[0].SampleControlPoint != SampleControlPoint.DEFAULT && - editorBeatmap.HitObjects[0].DifficultyControlPoint != DifficultyControlPoint.DEFAULT); + EditorBeatmap.HitObjects[0].SampleControlPoint != SampleControlPoint.DEFAULT && + EditorBeatmap.HitObjects[0].DifficultyControlPoint != DifficultyControlPoint.DEFAULT); - AddStep("Save", () => InputManager.Keys(PlatformAction.Save)); + ReloadEditorToSameBeatmap(); - checkMutations(); - AddAssert("Beatmap has correct .osu file path", () => editorBeatmap.BeatmapInfo.Path == "artist - title (author) [difficulty].osu"); + AddAssert("Beatmap still has correct timing point", () => EditorBeatmap.ControlPointInfo.TimingPoints.Single().Time == 500); - AddStep("Exit", () => InputManager.Key(Key.Escape)); - - AddUntilStep("Wait for main menu", () => Game.ScreenStack.CurrentScreen is MainMenu); - - Screens.Select.SongSelect songSelect = null; - - PushAndConfirm(() => songSelect = new PlaySongSelect()); - AddUntilStep("wait for carousel load", () => songSelect.BeatmapSetsLoaded); - - AddUntilStep("Wait for beatmap selected", () => !Game.Beatmap.IsDefault); - AddStep("Open options", () => InputManager.Key(Key.F3)); - AddStep("Enter editor", () => InputManager.Key(Key.Number5)); - - AddUntilStep("Wait for editor load", () => editor != null); - - checkMutations(); - } - - private void checkMutations() - { - AddAssert("Beatmap contains single hitcircle", () => editorBeatmap.HitObjects.Count == 1); - AddAssert("Beatmap has correct beat divisor", () => editorBeatmap.BeatmapInfo.BeatDivisor == 16); - AddAssert("Beatmap has correct overall difficulty", () => editorBeatmap.Difficulty.OverallDifficulty == 7); - 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"); + // After placement these must be non-default as defaults are read-only. + AddAssert("Placed object still has non-default control points", () => + EditorBeatmap.HitObjects[0].SampleControlPoint != SampleControlPoint.DEFAULT && + EditorBeatmap.HitObjects[0].DifficultyControlPoint != DifficultyControlPoint.DEFAULT); } } } diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 96254295a6..4e1a34ddbf 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -111,7 +111,7 @@ namespace osu.Game.Beatmaps public int GridSize { get; set; } - public double TimelineZoom { get; set; } + public double TimelineZoom { get; set; } = 1.0; [Ignored] public CountdownType Countdown { get; set; } = CountdownType.Normal; diff --git a/osu.Game/Tests/Visual/EditorSavingTestScene.cs b/osu.Game/Tests/Visual/EditorSavingTestScene.cs new file mode 100644 index 0000000000..72b5d076a5 --- /dev/null +++ b/osu.Game/Tests/Visual/EditorSavingTestScene.cs @@ -0,0 +1,67 @@ +// 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 osu.Framework.Input; +using osu.Framework.Testing; +using osu.Game.Rulesets.Edit; +using osu.Game.Screens.Edit; +using osu.Game.Screens.Edit.Setup; +using osu.Game.Screens.Menu; +using osu.Game.Screens.Select; +using osuTK.Input; + +namespace osu.Game.Tests.Visual +{ + /// + /// Tests the general expected flow of creating a new beatmap, saving it, then loading it back from song select. + /// + public class EditorSavingTestScene : OsuGameTestScene + { + protected Editor Editor => Game.ChildrenOfType().FirstOrDefault(); + + protected EditorBeatmap EditorBeatmap => (EditorBeatmap)Editor.Dependencies.Get(typeof(EditorBeatmap)); + + [SetUpSteps] + public override void SetUpSteps() + { + base.SetUpSteps(); + + AddStep("set default beatmap", () => Game.Beatmap.SetDefault()); + + PushAndConfirm(() => new EditorLoader()); + + AddUntilStep("wait for editor load", () => Editor?.IsLoaded == true); + + AddUntilStep("wait for metadata screen load", () => Editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); + + // We intentionally switch away from the metadata screen, else there is a feedback loop with the textbox handling which causes metadata changes below to get overwritten. + + AddStep("Enter compose mode", () => InputManager.Key(Key.F1)); + AddUntilStep("Wait for compose mode load", () => Editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); + } + + protected void SaveEditor() + { + AddStep("Save", () => InputManager.Keys(PlatformAction.Save)); + } + + protected void ReloadEditorToSameBeatmap() + { + AddStep("Exit", () => InputManager.Key(Key.Escape)); + + AddUntilStep("Wait for main menu", () => Game.ScreenStack.CurrentScreen is MainMenu); + + SongSelect songSelect = null; + + PushAndConfirm(() => songSelect = new PlaySongSelect()); + AddUntilStep("wait for carousel load", () => songSelect.BeatmapSetsLoaded); + + AddUntilStep("Wait for beatmap selected", () => !Game.Beatmap.IsDefault); + AddStep("Open options", () => InputManager.Key(Key.F3)); + AddStep("Enter editor", () => InputManager.Key(Key.Number5)); + + AddUntilStep("Wait for editor load", () => Editor != null); + } + } +} From de0a7d8501c5c3853755c49cf289a6be666c77a9 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 25 Jan 2022 12:29:41 +0300 Subject: [PATCH 670/996] Migrate taiko editor saving test scene to `EditorSavingTestScene` --- .../Editor/TestSceneEditorSaving.cs | 91 ------------------- .../Editor/TestSceneTaikoEditorSaving.cs | 38 ++++++++ 2 files changed, 38 insertions(+), 91 deletions(-) delete mode 100644 osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneEditorSaving.cs create mode 100644 osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoEditorSaving.cs diff --git a/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneEditorSaving.cs b/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneEditorSaving.cs deleted file mode 100644 index 42ab84714a..0000000000 --- a/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneEditorSaving.cs +++ /dev/null @@ -1,91 +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.Linq; -using NUnit.Framework; -using osu.Framework.Input; -using osu.Framework.Testing; -using osu.Framework.Utils; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Taiko.Beatmaps; -using osu.Game.Screens.Edit; -using osu.Game.Screens.Edit.Setup; -using osu.Game.Screens.Menu; -using osu.Game.Screens.Select; -using osu.Game.Tests.Visual; -using osuTK.Input; - -namespace osu.Game.Rulesets.Taiko.Tests.Editor -{ - public class TestSceneEditorSaving : OsuGameTestScene - { - private Screens.Edit.Editor editor => Game.ChildrenOfType().FirstOrDefault(); - - private EditorBeatmap editorBeatmap => (EditorBeatmap)editor.Dependencies.Get(typeof(EditorBeatmap)); - - /// - /// Tests the general expected flow of creating a new beatmap, saving it, then loading it back from song select. - /// Emphasis is placed on , since taiko has special handling for it to keep compatibility with stable. - /// - [Test] - public void TestNewBeatmapSaveThenLoad() - { - AddStep("set default beatmap", () => Game.Beatmap.SetDefault()); - AddStep("set taiko ruleset", () => Ruleset.Value = new TaikoRuleset().RulesetInfo); - - PushAndConfirm(() => new EditorLoader()); - - AddUntilStep("wait for editor load", () => editor?.IsLoaded == true); - - AddUntilStep("wait for metadata screen load", () => editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); - - // We intentionally switch away from the metadata screen, else there is a feedback loop with the textbox handling which causes metadata changes below to get overwritten. - - AddStep("Enter compose mode", () => InputManager.Key(Key.F1)); - AddUntilStep("Wait for compose mode load", () => editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); - - AddStep("Set slider multiplier", () => editorBeatmap.Difficulty.SliderMultiplier = 2); - AddStep("Set artist and title", () => - { - editorBeatmap.BeatmapInfo.Metadata.Artist = "artist"; - editorBeatmap.BeatmapInfo.Metadata.Title = "title"; - }); - AddStep("Set difficulty name", () => editorBeatmap.BeatmapInfo.DifficultyName = "difficulty"); - - checkMutations(); - - AddStep("Save", () => InputManager.Keys(PlatformAction.Save)); - - checkMutations(); - - AddStep("Exit", () => InputManager.Key(Key.Escape)); - - AddUntilStep("Wait for main menu", () => Game.ScreenStack.CurrentScreen is MainMenu); - - PushAndConfirm(() => new PlaySongSelect()); - - AddUntilStep("Wait for beatmap selected", () => !Game.Beatmap.IsDefault); - AddStep("Open options", () => InputManager.Key(Key.F3)); - AddStep("Enter editor", () => InputManager.Key(Key.Number5)); - - AddUntilStep("Wait for editor load", () => editor != null); - - checkMutations(); - } - - private void checkMutations() - { - AddAssert("Beatmap has correct slider multiplier", () => - { - // we can only assert value correctness on TaikoMultiplierAppliedDifficulty, because that is the final difficulty converted taiko beatmaps use. - // therefore, ensure that we have that difficulty type by calling .CopyFrom(), which is a no-op if the type is already correct. - var taikoDifficulty = new TaikoBeatmapConverter.TaikoMultiplierAppliedDifficulty(); - taikoDifficulty.CopyFrom(editorBeatmap.Difficulty); - return Precision.AlmostEquals(taikoDifficulty.SliderMultiplier, 2); - }); - AddAssert("Beatmap has correct metadata", () => editorBeatmap.BeatmapInfo.Metadata.Artist == "artist" && editorBeatmap.BeatmapInfo.Metadata.Title == "title"); - AddAssert("Beatmap has correct difficulty name", () => editorBeatmap.BeatmapInfo.DifficultyName == "difficulty"); - } - } -} diff --git a/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoEditorSaving.cs b/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoEditorSaving.cs new file mode 100644 index 0000000000..33c2ba532e --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoEditorSaving.cs @@ -0,0 +1,38 @@ +// 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.Framework.Utils; +using osu.Game.Rulesets.Taiko.Beatmaps; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Taiko.Tests.Editor +{ + public class TestSceneTaikoEditorSaving : EditorSavingTestScene + { + protected override Ruleset CreateRuleset() => new TaikoRuleset(); + + [Test] + public void TestTaikoSliderMultiplier() + { + AddStep("Set slider multiplier", () => EditorBeatmap.Difficulty.SliderMultiplier = 2); + + SaveEditor(); + + AddAssert("Beatmap has correct slider multiplier", assertTaikoSliderMulitplier); + + ReloadEditorToSameBeatmap(); + + AddAssert("Beatmap still has correct slider multiplier", assertTaikoSliderMulitplier); + + bool assertTaikoSliderMulitplier() + { + // we can only assert value correctness on TaikoMultiplierAppliedDifficulty, because that is the final difficulty converted taiko beatmaps use. + // therefore, ensure that we have that difficulty type by calling .CopyFrom(), which is a no-op if the type is already correct. + var taikoDifficulty = new TaikoBeatmapConverter.TaikoMultiplierAppliedDifficulty(); + taikoDifficulty.CopyFrom(EditorBeatmap.Difficulty); + return Precision.AlmostEquals(taikoDifficulty.SliderMultiplier, 2); + } + } + } +} From 3491b77c8cc95ce752a25434e00f130227dad182 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 14:25:55 +0900 Subject: [PATCH 671/996] Fix `ScoreInfo.RealmUser` not getting deep cloned correctly I'm still not at all happy with the play-to-results flow (with multiple clones), but this will have to do for now. --- osu.Game.Tests/NonVisual/ScoreInfoTest.cs | 4 ++++ osu.Game/Scoring/ScoreInfo.cs | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/osu.Game.Tests/NonVisual/ScoreInfoTest.cs b/osu.Game.Tests/NonVisual/ScoreInfoTest.cs index e0acc6d8db..836c978ba2 100644 --- a/osu.Game.Tests/NonVisual/ScoreInfoTest.cs +++ b/osu.Game.Tests/NonVisual/ScoreInfoTest.cs @@ -26,12 +26,16 @@ namespace osu.Game.Tests.NonVisual score.Statistics[HitResult.Good]++; score.Rank = ScoreRank.X; + score.RealmUser.Username = "test"; Assert.That(scoreCopy.Statistics[HitResult.Good], Is.EqualTo(10)); Assert.That(score.Statistics[HitResult.Good], Is.EqualTo(11)); Assert.That(scoreCopy.Rank, Is.EqualTo(ScoreRank.B)); Assert.That(score.Rank, Is.EqualTo(ScoreRank.X)); + + Assert.That(scoreCopy.RealmUser.Username, Is.EqualTo("test")); + Assert.That(score.Rank, Is.Empty); } [Test] diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index a28e16450f..4de1d580dc 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -133,6 +133,11 @@ namespace osu.Game.Scoring var clone = (ScoreInfo)this.Detach().MemberwiseClone(); clone.Statistics = new Dictionary(clone.Statistics); + clone.RealmUser = new RealmUser + { + OnlineID = RealmUser.OnlineID, + Username = RealmUser.Username, + }; return clone; } From 9532454e2a0d691652f40b7e15c3bd3503d9c9b4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 15:12:01 +0900 Subject: [PATCH 672/996] Remove `ILive` remnants --- .../UserInterface/TestScenePlaylistOverlay.cs | 6 +++--- osu.Game/Overlays/Music/Playlist.cs | 14 +++++++------- osu.Game/Overlays/Music/PlaylistItem.cs | 8 ++++---- osu.Game/Overlays/Music/PlaylistOverlay.cs | 6 +++--- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs index 1a3e38ddd7..09e5bc849e 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs @@ -19,11 +19,11 @@ namespace osu.Game.Tests.Visual.UserInterface { public class TestScenePlaylistOverlay : OsuManualInputManagerTestScene { - private readonly BindableList> beatmapSets = new BindableList>(); + private readonly BindableList> beatmapSets = new BindableList>(); private PlaylistOverlay playlistOverlay; - private ILive first; + private Live first; [SetUp] public void Setup() => Schedule(() => @@ -61,7 +61,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("hold 1st item handle", () => { - var handle = this.ChildrenOfType>.PlaylistItemHandle>().First(); + var handle = this.ChildrenOfType>.PlaylistItemHandle>().First(); InputManager.MoveMouseTo(handle.ScreenSpaceDrawQuad.Centre); InputManager.PressButton(MouseButton.Left); }); diff --git a/osu.Game/Overlays/Music/Playlist.cs b/osu.Game/Overlays/Music/Playlist.cs index c86146ff25..24d867141c 100644 --- a/osu.Game/Overlays/Music/Playlist.cs +++ b/osu.Game/Overlays/Music/Playlist.cs @@ -13,11 +13,11 @@ using osuTK; namespace osu.Game.Overlays.Music { - public class Playlist : OsuRearrangeableListContainer> + public class Playlist : OsuRearrangeableListContainer> { - public Action> RequestSelection; + public Action> RequestSelection; - public readonly Bindable> SelectedSet = new Bindable>(); + public readonly Bindable> SelectedSet = new Bindable>(); public new MarginPadding Padding { @@ -27,7 +27,7 @@ namespace osu.Game.Overlays.Music public void Filter(FilterCriteria criteria) { - var items = (SearchContainer>>)ListContainer; + var items = (SearchContainer>>)ListContainer; foreach (var item in items.OfType()) item.InSelectedCollection = criteria.Collection?.Beatmaps.Any(b => item.Model.ID == b.BeatmapSet?.ID) ?? true; @@ -35,15 +35,15 @@ namespace osu.Game.Overlays.Music items.SearchTerm = criteria.SearchText; } - public ILive FirstVisibleSet => Items.FirstOrDefault(i => ((PlaylistItem)ItemMap[i]).MatchingFilter); + public Live FirstVisibleSet => Items.FirstOrDefault(i => ((PlaylistItem)ItemMap[i]).MatchingFilter); - protected override OsuRearrangeableListItem> CreateOsuDrawable(ILive item) => new PlaylistItem(item) + protected override OsuRearrangeableListItem> CreateOsuDrawable(Live item) => new PlaylistItem(item) { SelectedSet = { BindTarget = SelectedSet }, RequestSelection = set => RequestSelection?.Invoke(set) }; - protected override FillFlowContainer>> CreateListFillFlowContainer() => new SearchContainer>> + protected override FillFlowContainer>> CreateListFillFlowContainer() => new SearchContainer>> { Spacing = new Vector2(0, 3), LayoutDuration = 200, diff --git a/osu.Game/Overlays/Music/PlaylistItem.cs b/osu.Game/Overlays/Music/PlaylistItem.cs index 3f82580bfb..f081cc0503 100644 --- a/osu.Game/Overlays/Music/PlaylistItem.cs +++ b/osu.Game/Overlays/Music/PlaylistItem.cs @@ -17,11 +17,11 @@ using osuTK.Graphics; namespace osu.Game.Overlays.Music { - public class PlaylistItem : OsuRearrangeableListItem>, IFilterable + public class PlaylistItem : OsuRearrangeableListItem>, IFilterable { - public readonly Bindable> SelectedSet = new Bindable>(); + public readonly Bindable> SelectedSet = new Bindable>(); - public Action> RequestSelection; + public Action> RequestSelection; private TextFlowContainer text; private ITextPart titlePart; @@ -29,7 +29,7 @@ namespace osu.Game.Overlays.Music [Resolved] private OsuColour colours { get; set; } - public PlaylistItem(ILive item) + public PlaylistItem(Live item) : base(item) { Padding = new MarginPadding { Left = 5 }; diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs index 4b10bb779e..7b705a905b 100644 --- a/osu.Game/Overlays/Music/PlaylistOverlay.cs +++ b/osu.Game/Overlays/Music/PlaylistOverlay.cs @@ -24,9 +24,9 @@ namespace osu.Game.Overlays.Music private const float transition_duration = 600; private const float playlist_height = 510; - public IBindableList> BeatmapSets => beatmapSets; + public IBindableList> BeatmapSets => beatmapSets; - private readonly BindableList> beatmapSets = new BindableList>(); + private readonly BindableList> beatmapSets = new BindableList>(); private readonly Bindable beatmap = new Bindable(); @@ -144,7 +144,7 @@ namespace osu.Game.Overlays.Music this.FadeOut(transition_duration); } - private void itemSelected(ILive beatmapSet) + private void itemSelected(Live beatmapSet) { beatmapSet.PerformRead(set => { From d0a2818847d85fe68e99a3cc5feb11ca4602e130 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 15:14:43 +0900 Subject: [PATCH 673/996] Fix incorrect testing --- osu.Game.Tests/NonVisual/ScoreInfoTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/NonVisual/ScoreInfoTest.cs b/osu.Game.Tests/NonVisual/ScoreInfoTest.cs index 836c978ba2..41b08a9e98 100644 --- a/osu.Game.Tests/NonVisual/ScoreInfoTest.cs +++ b/osu.Game.Tests/NonVisual/ScoreInfoTest.cs @@ -34,8 +34,8 @@ namespace osu.Game.Tests.NonVisual Assert.That(scoreCopy.Rank, Is.EqualTo(ScoreRank.B)); Assert.That(score.Rank, Is.EqualTo(ScoreRank.X)); - Assert.That(scoreCopy.RealmUser.Username, Is.EqualTo("test")); - Assert.That(score.Rank, Is.Empty); + Assert.That(scoreCopy.RealmUser.Username, Is.Empty); + Assert.That(score.RealmUser.Username, Is.EqualTo("test")); } [Test] From 267a7bd21fa709fc941d3620fe2c57c48e5bbe4a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 15:14:49 +0900 Subject: [PATCH 674/996] Give `RealmUser.Username` a better default value --- osu.Game/Models/RealmUser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Models/RealmUser.cs b/osu.Game/Models/RealmUser.cs index ff35528827..5fccff597c 100644 --- a/osu.Game/Models/RealmUser.cs +++ b/osu.Game/Models/RealmUser.cs @@ -11,7 +11,7 @@ namespace osu.Game.Models { public int OnlineID { get; set; } = 1; - public string Username { get; set; } + public string Username { get; set; } = string.Empty; public bool IsBot => false; From 4fe3d83fc40a176edf7ba9a1e3785c0fb57d24f4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 15:21:14 +0900 Subject: [PATCH 675/996] 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 4e5b9fdbb1..f85a96f819 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 af5d8a5920..aa6fb93aa0 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 2bcdea61b3..fbb4688588 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -60,7 +60,7 @@ - + @@ -83,7 +83,7 @@ - + From 5ea781faef9a4fe2de3be7a72ce84ad2306ecf23 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 15:24:53 +0900 Subject: [PATCH 676/996] `Send` unsubscribe actions to synchronization context for consistency and safety --- osu.Game/Database/RealmAccess.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 3866138d40..3572446aef 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -300,7 +300,7 @@ namespace osu.Game.Database return new InvokeOnDisposal(() => { if (ThreadSafety.IsUpdateThread) - unsubscribe(); + syncContext.Send(_ => unsubscribe(), null); else syncContext.Post(_ => unsubscribe(), null); From 24bcba64183a7bf404eca503a3c31a24762bfdac Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 15:57:05 +0900 Subject: [PATCH 677/996] Move final result set firing to before the update realm is disposed Without this, if any registered callback attempts to access `RealmAccess.Realm` when handling the empty set callback, it will deadlock the game. --- osu.Game/Database/RealmAccess.cs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 3572446aef..1a40176c1d 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -604,20 +604,18 @@ namespace osu.Game.Database Logger.Log(@"Blocking realm operations.", LoggingTarget.Database); + // Force a flush of any pending callbacks in the synchronization context. + // We want to ensure that the empty set callbacks are the last thing to arrive. + syncContext?.Send(_ => + { + foreach (var action in notificationsResetMap.Values) + action(); + }, null); + updateRealm?.Dispose(); updateRealm = null; } - // In order to ensure events arrive in the correct order, these *must* be fired post disposal of the update realm, - // and must be posted to the synchronization context. - // This is because realm may fire event callbacks between the `unregisterAllSubscriptions` and `updateRealm.Dispose` - // calls above. - syncContext?.Send(_ => - { - foreach (var action in notificationsResetMap.Values) - action(); - }, null); - const int sleep_length = 200; int timeout = 5000; From 11f0f3c17de6986c7aa6f6a3915b239bf2b5543e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 16:21:24 +0900 Subject: [PATCH 678/996] Revert "Move final result set firing to before the update realm is disposed" This reverts commit 24bcba64183a7bf404eca503a3c31a24762bfdac. --- osu.Game/Database/RealmAccess.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 1a40176c1d..3572446aef 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -604,18 +604,20 @@ namespace osu.Game.Database Logger.Log(@"Blocking realm operations.", LoggingTarget.Database); - // Force a flush of any pending callbacks in the synchronization context. - // We want to ensure that the empty set callbacks are the last thing to arrive. - syncContext?.Send(_ => - { - foreach (var action in notificationsResetMap.Values) - action(); - }, null); - updateRealm?.Dispose(); updateRealm = null; } + // In order to ensure events arrive in the correct order, these *must* be fired post disposal of the update realm, + // and must be posted to the synchronization context. + // This is because realm may fire event callbacks between the `unregisterAllSubscriptions` and `updateRealm.Dispose` + // calls above. + syncContext?.Send(_ => + { + foreach (var action in notificationsResetMap.Values) + action(); + }, null); + const int sleep_length = 200; int timeout = 5000; From 791ea0308f8026c127d26ee2118b083ffa72abf9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 17:09:28 +0900 Subject: [PATCH 679/996] Add flag to guard against deadlocks during blocking operations --- osu.Game/Database/RealmAccess.cs | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 3572446aef..dc7c8bf668 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -89,10 +89,15 @@ namespace osu.Game.Database private Realm? updateRealm; + private bool isSendingNotificationResetEvents; + public Realm Realm => ensureUpdateRealm(); private Realm ensureUpdateRealm() { + if (isSendingNotificationResetEvents) + throw new InvalidOperationException("Cannot retrieve a realm context from a notification callback during a blocking operation."); + if (!ThreadSafety.IsUpdateThread) throw new InvalidOperationException(@$"Use {nameof(getRealmInstance)} when performing realm operations from a non-update thread"); @@ -614,8 +619,20 @@ namespace osu.Game.Database // calls above. syncContext?.Send(_ => { - foreach (var action in notificationsResetMap.Values) - action(); + // Flag ensures that we don't get in a deadlocked scenario due to a callback attempting to access `RealmAccess.Realm` or `RealmAccess.Run` + // and hitting `realmRetrievalLock` a second time. Generally such usages should not exist, and as such we throw when an attempt is made + // to use in this fashion. + isSendingNotificationResetEvents = true; + + try + { + foreach (var action in notificationsResetMap.Values) + action(); + } + finally + { + isSendingNotificationResetEvents = false; + } }, null); const int sleep_length = 200; From 885fb92aada99e4dfccb0904f544e16a093787ba Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 17:21:57 +0900 Subject: [PATCH 680/996] Move final empty result set sending to post-compact --- osu.Game/Database/RealmAccess.cs | 44 ++++++++++++++++---------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index dc7c8bf668..9bdbebfe89 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -613,28 +613,6 @@ namespace osu.Game.Database updateRealm = null; } - // In order to ensure events arrive in the correct order, these *must* be fired post disposal of the update realm, - // and must be posted to the synchronization context. - // This is because realm may fire event callbacks between the `unregisterAllSubscriptions` and `updateRealm.Dispose` - // calls above. - syncContext?.Send(_ => - { - // Flag ensures that we don't get in a deadlocked scenario due to a callback attempting to access `RealmAccess.Realm` or `RealmAccess.Run` - // and hitting `realmRetrievalLock` a second time. Generally such usages should not exist, and as such we throw when an attempt is made - // to use in this fashion. - isSendingNotificationResetEvents = true; - - try - { - foreach (var action in notificationsResetMap.Values) - action(); - } - finally - { - isSendingNotificationResetEvents = false; - } - }, null); - const int sleep_length = 200; int timeout = 5000; @@ -656,6 +634,28 @@ namespace osu.Game.Database // We still want to continue with the blocking operation, though. Logger.Log($"Realm compact failed with error {e}", LoggingTarget.Database); } + + // In order to ensure events arrive in the correct order, these *must* be fired post disposal of the update realm, + // and must be posted to the synchronization context. + // This is because realm may fire event callbacks between the `unregisterAllSubscriptions` and `updateRealm.Dispose` + // calls above. + syncContext?.Send(_ => + { + // Flag ensures that we don't get in a deadlocked scenario due to a callback attempting to access `RealmAccess.Realm` or `RealmAccess.Run` + // and hitting `realmRetrievalLock` a second time. Generally such usages should not exist, and as such we throw when an attempt is made + // to use in this fashion. + isSendingNotificationResetEvents = true; + + try + { + foreach (var action in notificationsResetMap.Values) + action(); + } + finally + { + isSendingNotificationResetEvents = false; + } + }, null); } catch { From d1a22562621a2768056d3604400108f0b20c5441 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 17:48:11 +0900 Subject: [PATCH 681/996] Refactor `SkinSection` to avoid unnecessary realm queries --- .../Overlays/Settings/Sections/SkinSection.cs | 83 ++++++++++--------- 1 file changed, 42 insertions(+), 41 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index 0846c023c1..441a439596 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -18,6 +18,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Localisation; using osu.Game.Skinning; using osu.Game.Skinning.Editor; +using Realms; namespace osu.Game.Overlays.Settings.Sections { @@ -51,12 +52,6 @@ namespace osu.Game.Overlays.Settings.Sections private IDisposable realmSubscription; - private IQueryable queryRealmSkins() => - realm.Realm.All() - .Where(s => !s.DeletePending) - .OrderByDescending(s => s.Protected) // protected skins should be at the top. - .ThenBy(s => s.Name, StringComparer.OrdinalIgnoreCase); - [BackgroundDependencyLoader(permitNulls: true)] private void load(OsuConfigManager config, [CanBeNull] SkinEditorOverlay skinEditor) { @@ -83,37 +78,54 @@ namespace osu.Game.Overlays.Settings.Sections skinDropdown.Current = dropdownBindable; - realmSubscription = realm.RegisterForNotifications(r => queryRealmSkins(), (sender, changes, error) => - { - // The first fire of this is a bit redundant due to the call below, - // but this is safest in case the subscription is restored after a context recycle. - updateItems(); - }); - - updateItems(); + realmSubscription = realm.RegisterForNotifications(r => realm.Realm.All() + .Where(s => !s.DeletePending) + .OrderByDescending(s => s.Protected) // protected skins should be at the top. + .ThenBy(s => s.Name, StringComparer.OrdinalIgnoreCase), skinsChanged); configBindable.BindValueChanged(id => Scheduler.AddOnce(updateSelectedSkinFromConfig)); - updateSelectedSkinFromConfig(); - dropdownBindable.BindValueChanged(skin => + dropdownBindable.BindValueChanged(dropdownSelectionChanged); + } + + private void dropdownSelectionChanged(ValueChangedEvent> skin) + { + // Only handle cases where it's clear the user has intent to change skins. + if (skin.OldValue == null) return; + + if (skin.NewValue.Equals(random_skin_info)) { - if (skin.NewValue.Equals(random_skin_info)) + var skinBefore = skins.CurrentSkinInfo.Value; + + skins.SelectRandomSkin(); + + if (skinBefore == skins.CurrentSkinInfo.Value) { - var skinBefore = skins.CurrentSkinInfo.Value; - - skins.SelectRandomSkin(); - - if (skinBefore == skins.CurrentSkinInfo.Value) - { - // the random selection didn't change the skin, so we should manually update the dropdown to match. - dropdownBindable.Value = skins.CurrentSkinInfo.Value; - } - - return; + // the random selection didn't change the skin, so we should manually update the dropdown to match. + dropdownBindable.Value = skins.CurrentSkinInfo.Value; } - configBindable.Value = skin.NewValue.ID.ToString(); - }); + return; + } + + configBindable.Value = skin.NewValue.ID.ToString(); + } + + private void skinsChanged(IRealmCollection skins, ChangeSet changes, Exception error) + { + // This can only mean that realm is recycling, else we would see the protected skins. + // Because we are using `Live<>` in this class, we don't need to worry about this scenario too much. + if (!skins.Any()) + return; + + int protectedCount = skins.Count(s => s.Protected); + + skinItems = skins.ToLive(realm); + skinItems.Insert(protectedCount, random_skin_info); + + skinDropdown.Items = skinItems; + + updateSelectedSkinFromConfig(); } private void updateSelectedSkinFromConfig() @@ -126,17 +138,6 @@ namespace osu.Game.Overlays.Settings.Sections dropdownBindable.Value = skin ?? skinDropdown.Items.First(); } - private void updateItems() - { - int protectedCount = queryRealmSkins().Count(s => s.Protected); - - skinItems = queryRealmSkins().ToLive(realm); - - skinItems.Insert(protectedCount, random_skin_info); - - skinDropdown.Items = skinItems; - } - protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); From f2d48d088d81773f1d93f298b8e7d142565b7e80 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 17:57:03 +0900 Subject: [PATCH 682/996] Fix realm migration failures with presence of databased EF rulesets that don't exist on disk --- osu.Game/Rulesets/EFRulesetInfo.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/osu.Game/Rulesets/EFRulesetInfo.cs b/osu.Game/Rulesets/EFRulesetInfo.cs index 473b7c657e..9559cfa00c 100644 --- a/osu.Game/Rulesets/EFRulesetInfo.cs +++ b/osu.Game/Rulesets/EFRulesetInfo.cs @@ -28,21 +28,15 @@ namespace osu.Game.Rulesets public Ruleset CreateInstance() { if (!Available) - throw new RulesetLoadException(@"Ruleset not available"); + return null; var type = Type.GetType(InstantiationInfo); if (type == null) - throw new RulesetLoadException(@"Type lookup failure"); + return null; var ruleset = Activator.CreateInstance(type) as Ruleset; - if (ruleset == null) - throw new RulesetLoadException(@"Instantiation failure"); - - // overwrite the pre-populated RulesetInfo with a potentially database attached copy. - // ruleset.RulesetInfo = this; - return ruleset; } From 3aa681005bd5bf676b0cdf912aa0567a8120f077 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 18:04:53 +0900 Subject: [PATCH 683/996] Skip importing scores which have no matching realm ruleset There's no real way to recover these unless we want to start importing rulesets into realm. And that seems counter productive. This can only happen if users don't have the dll present any more, and it was removed far before realm was tracking rulesets (else it would have an `Available=0` entry in realm to match). --- osu.Game/Database/EFToRealmMigrator.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index adf91e4a41..33f7c25b28 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -286,6 +286,7 @@ namespace osu.Game.Database var transaction = r.BeginWrite(); int written = 0; + int missing = 0; try { @@ -300,6 +301,13 @@ namespace osu.Game.Database var beatmap = r.All().First(b => b.Hash == score.BeatmapInfo.Hash); var ruleset = r.Find(score.Ruleset.ShortName); + + if (ruleset == null) + { + log($"Skipping {++missing} scores with missing ruleset"); + continue; + } + var user = new RealmUser { OnlineID = score.User.OnlineID, From 45636ce04b224ca516b798c253f2289c357aaa5c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 18:25:27 +0900 Subject: [PATCH 684/996] Remove collection `ToLive` helper method to avoid confusion --- osu.Game/Database/RealmObjectExtensions.cs | 6 ------ .../Overlays/Settings/Sections/SkinSection.cs | 18 +++++++++++------- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/osu.Game/Database/RealmObjectExtensions.cs b/osu.Game/Database/RealmObjectExtensions.cs index dba8633f53..7a0ca2c85a 100644 --- a/osu.Game/Database/RealmObjectExtensions.cs +++ b/osu.Game/Database/RealmObjectExtensions.cs @@ -216,12 +216,6 @@ namespace osu.Game.Database return new RealmLiveUnmanaged(realmObject); } - public static List> ToLive(this IEnumerable realmList, RealmAccess realm) - where T : RealmObject, IHasGuidPrimaryKey - { - return realmList.Select(l => new RealmLive(l, realm)).Cast>().ToList(); - } - public static Live ToLive(this T realmObject, RealmAccess realm) where T : RealmObject, IHasGuidPrimaryKey { diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index 441a439596..1dfe49945f 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -42,7 +42,7 @@ namespace osu.Game.Overlays.Settings.Sections Name = "", }.ToLiveUnmanaged(); - private List> skinItems; + private readonly List> dropdownItems = new List>(); [Resolved] private SkinManager skins { get; set; } @@ -111,19 +111,23 @@ namespace osu.Game.Overlays.Settings.Sections configBindable.Value = skin.NewValue.ID.ToString(); } - private void skinsChanged(IRealmCollection skins, ChangeSet changes, Exception error) + private void skinsChanged(IRealmCollection sender, ChangeSet changes, Exception error) { // This can only mean that realm is recycling, else we would see the protected skins. // Because we are using `Live<>` in this class, we don't need to worry about this scenario too much. - if (!skins.Any()) + if (!sender.Any()) return; - int protectedCount = skins.Count(s => s.Protected); + int protectedCount = sender.Count(s => s.Protected); - skinItems = skins.ToLive(realm); - skinItems.Insert(protectedCount, random_skin_info); + // For simplicity repopulate the full list. + // In the future we should change this to properly handle ChangeSet events. + dropdownItems.Clear(); + foreach (var skin in sender) + dropdownItems.Add(skin.ToLive(realm)); + dropdownItems.Insert(protectedCount, random_skin_info); - skinDropdown.Items = skinItems; + skinDropdown.Items = dropdownItems; updateSelectedSkinFromConfig(); } From 473c4d00cabb5079b8c1630dd46659889cb25665 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 18:38:44 +0900 Subject: [PATCH 685/996] Fix grouped difficulty icons using incorrect lookup for ruleset grouping --- osu.Game/Screens/Select/Carousel/SetPanelContent.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Carousel/SetPanelContent.cs b/osu.Game/Screens/Select/Carousel/SetPanelContent.cs index 619806f96e..82523c9d9d 100644 --- a/osu.Game/Screens/Select/Carousel/SetPanelContent.cs +++ b/osu.Game/Screens/Select/Carousel/SetPanelContent.cs @@ -87,7 +87,7 @@ namespace osu.Game.Screens.Select.Carousel var beatmaps = carouselSet.Beatmaps.ToList(); return beatmaps.Count > maximum_difficulty_icons - ? (IEnumerable)beatmaps.GroupBy(b => b.BeatmapInfo.RulesetID) + ? (IEnumerable)beatmaps.GroupBy(b => b.BeatmapInfo.Ruleset.ShortName) .Select(group => new FilterableGroupedDifficultyIcon(group.ToList(), group.Last().BeatmapInfo.Ruleset)) : beatmaps.Select(b => new FilterableDifficultyIcon(b)); } From 57e624d8e7519427fbe05a54da4fe7bed5f7cf67 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 18:46:32 +0900 Subject: [PATCH 686/996] Fix custom rulesets being displayed before official ones --- osu.Game/Rulesets/RulesetStore.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index 9af9ace7ad..0f518be639 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -163,7 +163,10 @@ namespace osu.Game.Rulesets } } - availableRulesets.AddRange(detachedRulesets); + // add known official rulesets first.. + availableRulesets.AddRange(detachedRulesets.Where(r => r.OnlineID >= 0).OrderBy(r => r.OnlineID)); + // .. then add any customs + availableRulesets.AddRange(detachedRulesets.Where(r => r.OnlineID < 0).OrderBy(r => r.ShortName)); }); } From abe2cccaaecd1a01b5f3855705b4195958ef11da Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 19:03:26 +0900 Subject: [PATCH 687/996] Fix completely invalid method of testing realm migration --- osu.Game.Tests/Database/RealmLiveTests.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Database/RealmLiveTests.cs b/osu.Game.Tests/Database/RealmLiveTests.cs index 3f81b36378..4bc1f5078a 100644 --- a/osu.Game.Tests/Database/RealmLiveTests.cs +++ b/osu.Game.Tests/Database/RealmLiveTests.cs @@ -47,16 +47,14 @@ namespace osu.Game.Tests.Database liveBeatmap = beatmap.ToLive(realm); }); - using (realm.BlockAllOperations()) - { - // recycle realm before migrating - } - using (var migratedStorage = new TemporaryNativeStorage("realm-test-migration-target")) { migratedStorage.DeleteDirectory(string.Empty); - storage.Migrate(migratedStorage); + using (realm.BlockAllOperations()) + { + storage.Migrate(migratedStorage); + } Assert.IsFalse(liveBeatmap?.PerformRead(l => l.Hidden)); } From b3f2392358ac141b17f28328932d299384e3bec9 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 26 Jan 2022 19:04:18 +0900 Subject: [PATCH 688/996] Resolve compilation error due to removed method --- osu.Game/Overlays/Music/PlaylistOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs index 7b705a905b..59ade0918d 100644 --- a/osu.Game/Overlays/Music/PlaylistOverlay.cs +++ b/osu.Game/Overlays/Music/PlaylistOverlay.cs @@ -116,7 +116,7 @@ namespace osu.Game.Overlays.Music { beatmapSets.Clear(); // must use AddRange to avoid RearrangeableList sort overhead per add op. - beatmapSets.AddRange(sender.ToLive(realm)); + beatmapSets.AddRange(sender.Select(b => b.ToLive(realm))); return; } From 378173cc66e202e97a342b51c6f0f4a6327cf278 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 22:46:29 +0900 Subject: [PATCH 689/996] Fix some score imports failing due to null string attempted to be parsed as json --- osu.Game/Scoring/EFScoreInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Scoring/EFScoreInfo.cs b/osu.Game/Scoring/EFScoreInfo.cs index 1dd4e3b6b3..4161336cfc 100644 --- a/osu.Game/Scoring/EFScoreInfo.cs +++ b/osu.Game/Scoring/EFScoreInfo.cs @@ -105,7 +105,7 @@ namespace osu.Game.Scoring public string ModsJson { get => JsonConvert.SerializeObject(APIMods); - set => APIMods = JsonConvert.DeserializeObject(value); + set => APIMods = !string.IsNullOrEmpty(value) ? JsonConvert.DeserializeObject(value) : Array.Empty(); } [NotMapped] From 7f34085baa52a837edef6e1e1ed431f186d08224 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 26 Jan 2022 17:04:28 +0300 Subject: [PATCH 690/996] Mark `EditorSavingTestScene` as abstract --- osu.Game/Tests/Visual/EditorSavingTestScene.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/EditorSavingTestScene.cs b/osu.Game/Tests/Visual/EditorSavingTestScene.cs index 72b5d076a5..cc39ead1de 100644 --- a/osu.Game/Tests/Visual/EditorSavingTestScene.cs +++ b/osu.Game/Tests/Visual/EditorSavingTestScene.cs @@ -16,7 +16,7 @@ namespace osu.Game.Tests.Visual /// /// Tests the general expected flow of creating a new beatmap, saving it, then loading it back from song select. /// - public class EditorSavingTestScene : OsuGameTestScene + public abstract class EditorSavingTestScene : OsuGameTestScene { protected Editor Editor => Game.ChildrenOfType().FirstOrDefault(); From 873d3676153521b732167bfeb052445931984846 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Wed, 26 Jan 2022 15:51:39 +0100 Subject: [PATCH 691/996] Fix custom rulesets not being able to convert maps --- osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index 6b198ab505..7fd312371b 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -29,7 +29,7 @@ namespace osu.Game.Screens.Select.Carousel bool match = criteria.Ruleset == null || BeatmapInfo.RulesetID == criteria.Ruleset.OnlineID || - (BeatmapInfo.RulesetID == 0 && criteria.Ruleset.OnlineID > 0 && criteria.AllowConvertedBeatmaps); + (BeatmapInfo.RulesetID == 0 && criteria.Ruleset.OnlineID != 0 && criteria.AllowConvertedBeatmaps); if (BeatmapInfo.BeatmapSet?.Equals(criteria.SelectedBeatmapSet) == true) { From e712fab2994557019a825c80d8658197fca51b21 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Wed, 26 Jan 2022 16:14:07 +0100 Subject: [PATCH 692/996] Add test for custom ruleset conversion filtering --- .../NonVisual/Filtering/FilterMatchingTest.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs index 74904f4585..2e7a4a65e5 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs @@ -78,6 +78,20 @@ namespace osu.Game.Tests.NonVisual.Filtering Assert.IsFalse(carouselItem.Filtered.Value); } + [Test] + public void TestCriteriaMatchingConvertedBeatmapsForCustomRulesets() + { + var exampleBeatmapInfo = getExampleBeatmap(); + var criteria = new FilterCriteria + { + Ruleset = new RulesetInfo { OnlineID = -25 }, + AllowConvertedBeatmaps = true + }; + var carouselItem = new CarouselBeatmap(exampleBeatmapInfo); + carouselItem.Filter(criteria); + Assert.IsFalse(carouselItem.Filtered.Value); + } + [Test] [TestCase(true)] [TestCase(false)] From 6ec9c5c21a7891c0d1d7e8f9e88b44f482885da1 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Wed, 26 Jan 2022 16:23:00 +0100 Subject: [PATCH 693/996] Use default custom ruleset ID --- osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs index 2e7a4a65e5..363753cb33 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs @@ -84,7 +84,7 @@ namespace osu.Game.Tests.NonVisual.Filtering var exampleBeatmapInfo = getExampleBeatmap(); var criteria = new FilterCriteria { - Ruleset = new RulesetInfo { OnlineID = -25 }, + Ruleset = new RulesetInfo { OnlineID = -1 }, AllowConvertedBeatmaps = true }; var carouselItem = new CarouselBeatmap(exampleBeatmapInfo); From f21e3d0d866e637870d9a9693b693b108c114611 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 00:34:51 +0900 Subject: [PATCH 694/996] Block collection loading until realm migration has completed --- osu.Game/Collections/CollectionManager.cs | 5 +++++ osu.Game/Database/DatabaseContextFactory.cs | 6 ++++++ osu.Game/Database/EFToRealmMigrator.cs | 1 + 3 files changed, 12 insertions(+) diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs index c4f991094c..5845e0d4d1 100644 --- a/osu.Game/Collections/CollectionManager.cs +++ b/osu.Game/Collections/CollectionManager.cs @@ -50,9 +50,14 @@ namespace osu.Game.Collections this.storage = storage; } + [Resolved(canBeNull: true)] + private DatabaseContextFactory efContextFactory { get; set; } = null!; + [BackgroundDependencyLoader] private void load() { + efContextFactory?.WaitForMigrationCompletion(); + Collections.CollectionChanged += collectionsChanged; if (storage.Exists(database_backup_name)) diff --git a/osu.Game/Database/DatabaseContextFactory.cs b/osu.Game/Database/DatabaseContextFactory.cs index c84edbfb81..123ed8019c 100644 --- a/osu.Game/Database/DatabaseContextFactory.cs +++ b/osu.Game/Database/DatabaseContextFactory.cs @@ -184,5 +184,11 @@ namespace osu.Game.Database } public static string CreateDatabaseConnectionString(string filename, Storage storage) => string.Concat("Data Source=", storage.GetFullPath($@"{filename}", true)); + + private readonly ManualResetEventSlim migrationComplete = new ManualResetEventSlim(); + + public void SetMigrationCompletion() => migrationComplete.Set(); + + public void WaitForMigrationCompletion() => migrationComplete.Wait(); } } diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index 33f7c25b28..96baad35ec 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -128,6 +128,7 @@ namespace osu.Game.Database }, TaskCreationOptions.LongRunning).ContinueWith(t => { migrationCompleted.SetResult(true); + efContextFactory.SetMigrationCompletion(); }); } From 0c2ed2f9a77c863865844f20bc0796ef1d6e14e0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 01:25:55 +0900 Subject: [PATCH 695/996] Add failing test coverage of incorrect filter ruleset matching --- osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs index 363753cb33..33204d33a7 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs @@ -16,7 +16,11 @@ namespace osu.Game.Tests.NonVisual.Filtering { private BeatmapInfo getExampleBeatmap() => new BeatmapInfo { - Ruleset = new RulesetInfo { OnlineID = 0 }, + Ruleset = new RulesetInfo + { + ShortName = "osu", + OnlineID = 0 + }, StarRating = 4.0d, Difficulty = new BeatmapDifficulty { @@ -57,7 +61,7 @@ namespace osu.Game.Tests.NonVisual.Filtering var exampleBeatmapInfo = getExampleBeatmap(); var criteria = new FilterCriteria { - Ruleset = new RulesetInfo { OnlineID = 6 } + Ruleset = new RulesetInfo { ShortName = "catch" } }; var carouselItem = new CarouselBeatmap(exampleBeatmapInfo); carouselItem.Filter(criteria); From f70e10e8a4607dd51254a9915c78880b35a4f923 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 01:25:40 +0900 Subject: [PATCH 696/996] Fix ruleset filter matching using `OnlineID` instead of `ShortName` --- osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index 7fd312371b..e6dee0233f 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -28,7 +28,7 @@ namespace osu.Game.Screens.Select.Carousel bool match = criteria.Ruleset == null || - BeatmapInfo.RulesetID == criteria.Ruleset.OnlineID || + BeatmapInfo.Ruleset.ShortName == criteria.Ruleset.ShortName || (BeatmapInfo.RulesetID == 0 && criteria.Ruleset.OnlineID != 0 && criteria.AllowConvertedBeatmaps); if (BeatmapInfo.BeatmapSet?.Equals(criteria.SelectedBeatmapSet) == true) From 4382adad8240fee9c85f1b5cb21efea70c011184 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 25 Jan 2022 23:20:33 +0100 Subject: [PATCH 697/996] Add test coverage for editor changes not resetting after exit without save --- .../Visual/Editing/TestSceneEditorSaving.cs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs index 58daab1ce2..adaa24d542 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs @@ -4,11 +4,13 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Screens; using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Compose.Components.Timeline; +using osu.Game.Screens.Select; using osuTK.Input; namespace osu.Game.Tests.Visual.Editing @@ -116,5 +118,24 @@ namespace osu.Game.Tests.Visual.Editing EditorBeatmap.HitObjects[0].SampleControlPoint != SampleControlPoint.DEFAULT && EditorBeatmap.HitObjects[0].DifficultyControlPoint != DifficultyControlPoint.DEFAULT); } + + [Test] + public void TestExitWithoutSaveFromExistingBeatmap() + { + const string tags_to_save = "these tags will be saved"; + const string tags_to_discard = "these tags should be discarded"; + + AddStep("Set tags", () => EditorBeatmap.BeatmapInfo.Metadata.Tags = tags_to_save); + SaveEditor(); + AddAssert("Tags saved correctly", () => EditorBeatmap.BeatmapInfo.Metadata.Tags == tags_to_save); + + ReloadEditorToSameBeatmap(); + AddAssert("Tags saved correctly", () => EditorBeatmap.BeatmapInfo.Metadata.Tags == tags_to_save); + AddStep("Set tags again", () => EditorBeatmap.BeatmapInfo.Metadata.Tags = tags_to_discard); + + AddStep("Exit editor", () => Editor.Exit()); + AddUntilStep("Wait for song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect); + AddAssert("Tags reverted correctly", () => Game.Beatmap.Value.BeatmapInfo.Metadata.Tags == tags_to_save); + } } } From d760283665b6ecadcdee4989ded6e7684387dd41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 25 Jan 2022 23:31:42 +0100 Subject: [PATCH 698/996] Ensure edited beatmap is restored to a baseline state on exit --- osu.Game/Screens/Edit/Editor.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index b42f629aad..e26453f99a 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -574,7 +574,9 @@ namespace osu.Game.Screens.Edit // To update the game-wide beatmap with any changes, perform a re-fetch on exit/suspend. // This is required as the editor makes its local changes via EditorBeatmap // (which are not propagated outwards to a potentially cached WorkingBeatmap). - var refetchedBeatmap = beatmapManager.GetWorkingBeatmap(Beatmap.Value.BeatmapInfo); + ((IWorkingBeatmapCache)beatmapManager).Invalidate(Beatmap.Value.BeatmapInfo); + var refetchedBeatmapInfo = beatmapManager.QueryBeatmap(b => b.ID == Beatmap.Value.BeatmapInfo.ID); + var refetchedBeatmap = beatmapManager.GetWorkingBeatmap(refetchedBeatmapInfo); if (!(refetchedBeatmap is DummyWorkingBeatmap)) { From e0616476e2a0a772c5998b0194d8a0dab0ad0eb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 26 Jan 2022 20:52:14 +0100 Subject: [PATCH 699/996] Fix test gameplay tests failing due to beatmap refetch on suspend --- .../Visual/Editing/TestSceneEditorTestGameplay.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs index 79afc8cf27..e2406a8d27 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs @@ -70,7 +70,11 @@ namespace osu.Game.Tests.Visual.Editing AddUntilStep("current screen is editor", () => Stack.CurrentScreen is Editor); AddUntilStep("background has correct params", () => { - var background = this.ChildrenOfType().Single(); + // the test gameplay player's beatmap may be the "same" beatmap as the one being edited, *but* the `BeatmapInfo` references may differ + // due to the beatmap refetch logic ran on editor suspend. + // this test cares about checking the background belonging to the editor specifically, so check that using reference equality + // (as `.Equals()` cannot discern between the two, as they technically share the same database GUID). + var background = this.ChildrenOfType().Single(b => ReferenceEquals(b.Beatmap.BeatmapInfo, EditorBeatmap.BeatmapInfo)); return background.Colour == Color4.DarkGray && background.BlurAmount.Value == 0; }); AddAssert("no mods selected", () => SelectedMods.Value.Count == 0); @@ -99,7 +103,11 @@ namespace osu.Game.Tests.Visual.Editing AddUntilStep("current screen is editor", () => Stack.CurrentScreen is Editor); AddUntilStep("background has correct params", () => { - var background = this.ChildrenOfType().Single(); + // the test gameplay player's beatmap may be the "same" beatmap as the one being edited, *but* the `BeatmapInfo` references may differ + // due to the beatmap refetch logic ran on editor suspend. + // this test cares about checking the background belonging to the editor specifically, so check that using reference equality + // (as `.Equals()` cannot discern between the two, as they technically share the same database GUID). + var background = this.ChildrenOfType().Single(b => ReferenceEquals(b.Beatmap.BeatmapInfo, EditorBeatmap.BeatmapInfo)); return background.Colour == Color4.DarkGray && background.BlurAmount.Value == 0; }); From 587c0f965c877ee05db77ad87762ee9c810f4366 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 14:32:20 +0900 Subject: [PATCH 700/996] Add more attempts to delete EF database Just noticed in passing. Probably best we do this since it was known to fail on windows in some rare cases. --- osu.Game/Database/DatabaseContextFactory.cs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/DatabaseContextFactory.cs b/osu.Game/Database/DatabaseContextFactory.cs index 123ed8019c..45557aa5ec 100644 --- a/osu.Game/Database/DatabaseContextFactory.cs +++ b/osu.Game/Database/DatabaseContextFactory.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.IO; using System.Linq; using System.Threading; @@ -163,7 +164,24 @@ namespace osu.Game.Database try { - storage.Delete(DATABASE_NAME); + int attempts = 10; + + // Retry logic taken from MigratableStorage.AttemptOperation. + while (true) + { + try + { + storage.Delete(DATABASE_NAME); + return; + } + catch (Exception) + { + if (attempts-- == 0) + throw; + } + + Thread.Sleep(250); + } } catch { From deb5d75b5f21078d0877fd1ce5b75468fc896e3d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 14:33:44 +0900 Subject: [PATCH 701/996] Change migration process to always delete old EF database It is already backed up, so this is probably fine. --- osu.Game/Database/EFToRealmMigrator.cs | 44 +++++++++++++++++++++----- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index 96baad35ec..072d1c95f1 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -15,6 +15,8 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Models; +using osu.Game.Overlays; +using osu.Game.Overlays.Notifications; using osu.Game.Rulesets; using osu.Game.Scoring; using osu.Game.Skinning; @@ -40,6 +42,12 @@ namespace osu.Game.Database [Resolved] private OsuConfigManager config { get; set; } = null!; + [Resolved] + private NotificationOverlay notificationOverlay { get; set; } = null!; + + [Resolved] + private OsuGame game { get; set; } = null!; + private readonly OsuSpriteText currentOperationText; public EFToRealmMigrator() @@ -96,7 +104,11 @@ namespace osu.Game.Database protected override void LoadComplete() { base.LoadComplete(); + beginMigration(); + } + private void beginMigration() + { Task.Factory.StartNew(() => { using (var ef = efContextFactory.Get()) @@ -117,21 +129,37 @@ namespace osu.Game.Database migrateBeatmaps(ef); migrateScores(ef); } - - // Delete the database permanently. - // Will cause future startups to not attempt migration. - log("Migration successful, deleting EF database"); - efContextFactory.ResetDatabase(); - - if (DebugUtils.IsDebugBuild) - Logger.Log("Your development database has been fully migrated to realm. If you switch back to a pre-realm branch and need your previous database, rename the backup file back to \"client.db\".\n\nNote that doing this can potentially leave your file store in a bad state.", level: LogLevel.Important); }, TaskCreationOptions.LongRunning).ContinueWith(t => { + if (t.Exception == null) + { + log("Migration successful!"); + + if (DebugUtils.IsDebugBuild) + Logger.Log("Your development database has been fully migrated to realm. If you switch back to a pre-realm branch and need your previous database, rename the backup file back to \"client.db\".\n\nNote that doing this can potentially leave your file store in a bad state.", level: LogLevel.Important); + } + else + { + log("Migration failed!"); + Logger.Log(t.Exception.ToString(), LoggingTarget.Database); + } + + // Regardless of success, since the game is going to continue with startup let's move the ef database out of the way. + // If we were to not do this, the migration would run another time the next time the user starts the game. + deletePreRealmData(); + migrationCompleted.SetResult(true); efContextFactory.SetMigrationCompletion(); }); } + private void deletePreRealmData() + { + // Delete the database permanently. + // Will cause future startups to not attempt migration. + efContextFactory.ResetDatabase(); + } + private void log(string message) { Logger.Log(message, LoggingTarget.Database); From b745252962b232e3bc3173491ab37495f302a56d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 14:34:18 +0900 Subject: [PATCH 702/996] Show notification when migration fails to give users a recovery path --- osu.Game/Database/EFToRealmMigrator.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index 072d1c95f1..d110ac2f9f 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -142,6 +142,16 @@ namespace osu.Game.Database { log("Migration failed!"); Logger.Log(t.Exception.ToString(), LoggingTarget.Database); + + notificationOverlay.Post(new SimpleErrorNotification + { + Text = "IMPORTANT: During data migration, some of your data could not be successfully migrated. It has been backed up in your osu! folder.\n\nFor further assistance, please open a discussion on github and attach your backup files (click to get started).", + Activated = () => + { + game.OpenUrlExternally(@"https://github.com/ppy/osu/discussions/new?title=Realm%20migration%20issue&body=Please%20attach%20your%20database%20backups%20by%20zipping%20them%20and%20dragging%20in%20here!&category=q-a"); + return true; + } + }); } // Regardless of success, since the game is going to continue with startup let's move the ef database out of the way. From 08948f60f3108ccfaf098be3ebe99a69ffe61c93 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 14:39:07 +0900 Subject: [PATCH 703/996] Move backups to "backups" subfolder to make them easier to find --- osu.Game/OsuGameBase.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 09ca4e450d..8363c41437 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -202,16 +202,18 @@ namespace osu.Game // See https://github.com/ppy/osu/pull/16547 for more discussion. if (EFContextFactory != null) { + const string backup_folder = "backups"; + string migration = $"before_final_migration_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}"; - EFContextFactory.CreateBackup($"client.{migration}.db"); - realm.CreateBackup($"client.{migration}.realm"); + EFContextFactory.CreateBackup(Path.Combine(backup_folder, $"client.{migration}.db")); + realm.CreateBackup(Path.Combine(backup_folder, $"client.{migration}.realm")); using (var source = Storage.GetStream("collection.db")) { if (source != null) { - using (var destination = Storage.GetStream($"collection.{migration}.db", FileAccess.Write, FileMode.CreateNew)) + using (var destination = Storage.GetStream(Path.Combine(backup_folder, $"collection.{migration}.db"), FileAccess.Write, FileMode.CreateNew)) source.CopyTo(destination); } } From 31abb372e5c2ac123decc43f88aa8d68b5d3c2ff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 14:52:43 +0900 Subject: [PATCH 704/996] Automatically zip and show the backup archive to the user --- osu.Game/Database/EFToRealmMigrator.cs | 29 ++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index d110ac2f9f..c41461cc68 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.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.IO; using System.Linq; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; @@ -9,6 +10,7 @@ using osu.Framework.Development; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Logging; +using osu.Framework.Platform; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics; @@ -22,6 +24,10 @@ using osu.Game.Scoring; using osu.Game.Skinning; using osuTK; using Realms; +using SharpCompress.Archives; +using SharpCompress.Archives.Zip; +using SharpCompress.Common; +using SharpCompress.Writers.Zip; #nullable enable @@ -48,6 +54,9 @@ namespace osu.Game.Database [Resolved] private OsuGame game { get; set; } = null!; + [Resolved] + private Storage storage { get; set; } = null!; + private readonly OsuSpriteText currentOperationText; public EFToRealmMigrator() @@ -145,10 +154,26 @@ namespace osu.Game.Database notificationOverlay.Post(new SimpleErrorNotification { - Text = "IMPORTANT: During data migration, some of your data could not be successfully migrated. It has been backed up in your osu! folder.\n\nFor further assistance, please open a discussion on github and attach your backup files (click to get started).", + Text = "IMPORTANT: During data migration, some of your data could not be successfully migrated. The previous version has been backed up.\n\nFor further assistance, please open a discussion on github and attach your backup files (click to get started).", Activated = () => { - game.OpenUrlExternally(@"https://github.com/ppy/osu/discussions/new?title=Realm%20migration%20issue&body=Please%20attach%20your%20database%20backups%20by%20zipping%20them%20and%20dragging%20in%20here!&category=q-a"); + game.OpenUrlExternally(@"https://github.com/ppy/osu/discussions/new?title=Realm%20migration%20issue&body=Please%20drag%20the%20""attach_me.zip""%20file%20here!&category=q-a"); + + const string attachment_filename = "attach_me.zip"; + const string backup_folder = "backups"; + + var backupStorage = storage.GetStorageForDirectory(backup_folder); + + backupStorage.Delete(attachment_filename); + + using (var zip = ZipArchive.Create()) + { + zip.AddAllFromDirectory(backupStorage.GetFullPath(string.Empty)); + zip.SaveTo(Path.Combine(backupStorage.GetFullPath(string.Empty), attachment_filename), new ZipWriterOptions(CompressionType.Deflate)); + } + + backupStorage.PresentFileExternally(attachment_filename); + return true; } }); From 465e7d29feb9529dbde477bcf384bf8ef94b2949 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 14:53:11 +0900 Subject: [PATCH 705/996] Avoid showing the external link warning --- osu.Game/Database/EFToRealmMigrator.cs | 2 +- osu.Game/Online/Chat/ExternalLinkOpener.cs | 4 ++-- osu.Game/OsuGame.cs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index c41461cc68..05bc86a7c0 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -157,7 +157,7 @@ namespace osu.Game.Database Text = "IMPORTANT: During data migration, some of your data could not be successfully migrated. The previous version has been backed up.\n\nFor further assistance, please open a discussion on github and attach your backup files (click to get started).", Activated = () => { - game.OpenUrlExternally(@"https://github.com/ppy/osu/discussions/new?title=Realm%20migration%20issue&body=Please%20drag%20the%20""attach_me.zip""%20file%20here!&category=q-a"); + game.OpenUrlExternally(@"https://github.com/ppy/osu/discussions/new?title=Realm%20migration%20issue&body=Please%20drag%20the%20""attach_me.zip""%20file%20here!&category=q-a", true); const string attachment_filename = "attach_me.zip"; const string backup_folder = "backups"; diff --git a/osu.Game/Online/Chat/ExternalLinkOpener.cs b/osu.Game/Online/Chat/ExternalLinkOpener.cs index 8407e2ca6a..328b43c4e8 100644 --- a/osu.Game/Online/Chat/ExternalLinkOpener.cs +++ b/osu.Game/Online/Chat/ExternalLinkOpener.cs @@ -27,9 +27,9 @@ namespace osu.Game.Online.Chat externalLinkWarning = config.GetBindable(OsuSetting.ExternalLinkWarning); } - public void OpenUrlExternally(string url) + public void OpenUrlExternally(string url, bool bypassWarning = false) { - if (externalLinkWarning.Value) + if (!bypassWarning && externalLinkWarning.Value) dialogOverlay.Push(new ExternalLinkDialog(url, () => host.OpenUrlExternally(url))); else host.OpenUrlExternally(url); diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index c2e1b25d94..5b58dec0c3 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -357,12 +357,12 @@ namespace osu.Game } }); - public void OpenUrlExternally(string url) => waitForReady(() => externalLinkOpener, _ => + public void OpenUrlExternally(string url, bool bypassExternalUrlWarning = false) => waitForReady(() => externalLinkOpener, _ => { if (url.StartsWith('/')) url = $"{API.APIEndpointUrl}{url}"; - externalLinkOpener.OpenUrlExternally(url); + externalLinkOpener.OpenUrlExternally(url, bypassExternalUrlWarning); }); /// From fb081384e13ca4a09bee571636021ea0ce894dc2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 14:55:52 +0900 Subject: [PATCH 706/996] Add safety against zip creation potentially failing (probably can't but still) --- osu.Game/Database/EFToRealmMigrator.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index 05bc86a7c0..723b3eeada 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -166,11 +166,15 @@ namespace osu.Game.Database backupStorage.Delete(attachment_filename); - using (var zip = ZipArchive.Create()) + try { - zip.AddAllFromDirectory(backupStorage.GetFullPath(string.Empty)); - zip.SaveTo(Path.Combine(backupStorage.GetFullPath(string.Empty), attachment_filename), new ZipWriterOptions(CompressionType.Deflate)); + using (var zip = ZipArchive.Create()) + { + zip.AddAllFromDirectory(backupStorage.GetFullPath(string.Empty)); + zip.SaveTo(Path.Combine(backupStorage.GetFullPath(string.Empty), attachment_filename), new ZipWriterOptions(CompressionType.Deflate)); + } } + catch { } backupStorage.PresentFileExternally(attachment_filename); From 67ccb879926f309f3b2d3dbd3bc31a2a8760b05e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 14:56:04 +0900 Subject: [PATCH 707/996] Add exception message to discussion template url --- osu.Game/Database/EFToRealmMigrator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index 723b3eeada..639bf10e19 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -157,7 +157,7 @@ namespace osu.Game.Database Text = "IMPORTANT: During data migration, some of your data could not be successfully migrated. The previous version has been backed up.\n\nFor further assistance, please open a discussion on github and attach your backup files (click to get started).", Activated = () => { - game.OpenUrlExternally(@"https://github.com/ppy/osu/discussions/new?title=Realm%20migration%20issue&body=Please%20drag%20the%20""attach_me.zip""%20file%20here!&category=q-a", true); + game.OpenUrlExternally($@"https://github.com/ppy/osu/discussions/new?title=Realm%20migration%20issue ({t.Exception.Message})&body=Please%20drag%20the%20""attach_me.zip""%20file%20here!&category=q-a", true); const string attachment_filename = "attach_me.zip"; const string backup_folder = "backups"; From f30d63107a73c1a4f059366a12483f086e26c22f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 15:19:27 +0900 Subject: [PATCH 708/996] Add `SortID` to `RulesetInfo` to allow stable ordering of rulesets for display --- .../NonVisual/RulesetInfoOrderingTest.cs | 38 +++++++++++++++++++ osu.Game/Rulesets/RulesetInfo.cs | 5 +++ osu.Game/Rulesets/RulesetStore.cs | 5 +-- .../Select/Carousel/CarouselBeatmap.cs | 2 +- .../Select/Carousel/CarouselBeatmapSet.cs | 2 +- 5 files changed, 46 insertions(+), 6 deletions(-) create mode 100644 osu.Game.Tests/NonVisual/RulesetInfoOrderingTest.cs diff --git a/osu.Game.Tests/NonVisual/RulesetInfoOrderingTest.cs b/osu.Game.Tests/NonVisual/RulesetInfoOrderingTest.cs new file mode 100644 index 0000000000..e7c04d27c1 --- /dev/null +++ b/osu.Game.Tests/NonVisual/RulesetInfoOrderingTest.cs @@ -0,0 +1,38 @@ +// 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.Game.Rulesets; +using osu.Game.Rulesets.Catch; +using osu.Game.Rulesets.Osu; + +namespace osu.Game.Tests.NonVisual +{ + [TestFixture] + public class RulesetInfoOrderingTest + { + [Test] + public void TestOrdering() + { + var rulesets = new[] + { + new RulesetInfo("custom2", "Custom Ruleset 2", string.Empty, -1), + new OsuRuleset().RulesetInfo, + new RulesetInfo("custom3", "Custom Ruleset 3", string.Empty, -1), + new RulesetInfo("custom2", "Custom Ruleset 2", string.Empty, -1), + new CatchRuleset().RulesetInfo, + new RulesetInfo("custom3", "Custom Ruleset 3", string.Empty, -1), + }; + + var orderedRulesets = rulesets.OrderBy(r => r.SortID); + + // Ensure all customs are after official. + Assert.That(orderedRulesets.Select(r => r.OnlineID), Is.EqualTo(new[] { 0, 2, -1, -1, -1, -1 })); + + // Ensure customs are grouped next to each other (ie. stably sorted). + Assert.That(orderedRulesets.SkipWhile(r => r.ShortName != "custom2").Skip(1).First().ShortName, Is.EqualTo("custom2")); + Assert.That(orderedRulesets.SkipWhile(r => r.ShortName != "custom3").Skip(1).First().ShortName, Is.EqualTo("custom3")); + } + } +} diff --git a/osu.Game/Rulesets/RulesetInfo.cs b/osu.Game/Rulesets/RulesetInfo.cs index 2e2ec5c024..d721926e95 100644 --- a/osu.Game/Rulesets/RulesetInfo.cs +++ b/osu.Game/Rulesets/RulesetInfo.cs @@ -24,6 +24,11 @@ namespace osu.Game.Rulesets public string InstantiationInfo { get; set; } = string.Empty; + /// + /// A best effort sort ID which provides stable ordering and puts online rulesets before non-online rulesets. + /// + public int SortID => OnlineID >= 0 ? OnlineID : Math.Abs(ShortName.GetHashCode()); + public RulesetInfo(string shortName, string name, string instantiationInfo, int onlineID) { ShortName = shortName; diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index 0f518be639..a9fd8f430e 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -163,10 +163,7 @@ namespace osu.Game.Rulesets } } - // add known official rulesets first.. - availableRulesets.AddRange(detachedRulesets.Where(r => r.OnlineID >= 0).OrderBy(r => r.OnlineID)); - // .. then add any customs - availableRulesets.AddRange(detachedRulesets.Where(r => r.OnlineID < 0).OrderBy(r => r.ShortName)); + availableRulesets.AddRange(detachedRulesets.OrderBy(r => r.SortID)); }); } diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index e6dee0233f..9d49fdaf6e 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -89,7 +89,7 @@ namespace osu.Game.Screens.Select.Carousel { default: case SortMode.Difficulty: - int ruleset = BeatmapInfo.RulesetID.CompareTo(otherBeatmap.BeatmapInfo.RulesetID); + int ruleset = BeatmapInfo.Ruleset.SortID.CompareTo(otherBeatmap.BeatmapInfo.Ruleset.SortID); if (ruleset != 0) return ruleset; return BeatmapInfo.StarRating.CompareTo(otherBeatmap.BeatmapInfo.StarRating); diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index b2b3b5411c..274be7b73a 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -39,7 +39,7 @@ namespace osu.Game.Screens.Select.Carousel beatmapSet.Beatmaps .Where(b => !b.Hidden) - .OrderBy(b => b.RulesetID) + .OrderBy(b => b.Ruleset.SortID) .ThenBy(b => b.StarRating) .Select(b => new CarouselBeatmap(b)) .ForEach(AddChild); From e67b1fe0ec9c50b7ddb429edb2ee13d534e881ad Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 15:25:56 +0900 Subject: [PATCH 709/996] Make `Ruleset.RulesetInfo` get only --- osu.Game/Rulesets/Ruleset.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index d279f6d6ee..616540b59c 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets [ExcludeFromDynamicCompile] public abstract class Ruleset { - public RulesetInfo RulesetInfo { get; internal set; } + public RulesetInfo RulesetInfo { get; } private static readonly ConcurrentDictionary mod_reference_cache = new ConcurrentDictionary(); From 714177cce13a86d3665d1685831da3a1e30c7382 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 15:23:16 +0900 Subject: [PATCH 710/996] Remove pointless constructor in `RulesetInfo` --- osu.Game.Tests/Database/RealmTest.cs | 2 +- osu.Game/Rulesets/RulesetInfo.cs | 8 -------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/osu.Game.Tests/Database/RealmTest.cs b/osu.Game.Tests/Database/RealmTest.cs index c2339dd9ad..838759c991 100644 --- a/osu.Game.Tests/Database/RealmTest.cs +++ b/osu.Game.Tests/Database/RealmTest.cs @@ -114,7 +114,7 @@ namespace osu.Game.Tests.Database } protected static RulesetInfo CreateRuleset() => - new RulesetInfo(0, "osu!", "osu", true); + new RulesetInfo("osu", "osu!", string.Empty, 0) { Available = true }; private class RealmTestGame : Framework.Game { diff --git a/osu.Game/Rulesets/RulesetInfo.cs b/osu.Game/Rulesets/RulesetInfo.cs index 2e2ec5c024..5171f0d63c 100644 --- a/osu.Game/Rulesets/RulesetInfo.cs +++ b/osu.Game/Rulesets/RulesetInfo.cs @@ -37,14 +37,6 @@ namespace osu.Game.Rulesets { } - public RulesetInfo(int? onlineID, string name, string shortName, bool available) - { - OnlineID = onlineID ?? -1; - Name = name; - ShortName = shortName; - Available = available; - } - public bool Available { get; set; } public bool Equals(RulesetInfo? other) From 5288eedd311feabd82cebf1ddf19336557197016 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 15:19:48 +0900 Subject: [PATCH 711/996] Update all usages of `RulesetID` and `Ruleset.ID` to use `Ruleset.OnlineID` --- .../ManiaFilterCriteria.cs | 2 +- .../Beatmaps/TaikoBeatmapConverter.cs | 2 +- .../Formats/LegacyBeatmapDecoderTest.cs | 2 +- .../Formats/LegacyBeatmapEncoderTest.cs | 2 +- .../Beatmaps/Formats/OsuJsonDecoderTest.cs | 2 +- .../Editing/TestSceneDifficultySwitching.cs | 4 +- .../Editing/TestSceneEditorTestGameplay.cs | 2 +- .../TestScenePlayerScoreSubmission.cs | 4 +- .../Visual/Gameplay/TestSceneSpectator.cs | 2 +- .../Visual/Multiplayer/QueueModeTestScene.cs | 4 +- .../TestSceneMultiSpectatorScreen.cs | 2 +- .../Multiplayer/TestSceneMultiplayer.cs | 48 +++++++++---------- .../TestSceneMultiplayerMatchSongSelect.cs | 4 +- .../TestSceneMultiplayerPlaylist.cs | 2 +- .../TestSceneMultiplayerQueueList.cs | 2 +- .../Visual/Multiplayer/TestSceneTeamVersus.cs | 8 ++-- .../SongSelect/TestSceneBeatmapCarousel.cs | 2 +- .../SongSelect/TestScenePlaySongSelect.cs | 26 +++++----- .../Components/TournamentModIcon.cs | 2 +- osu.Game/Beatmaps/BeatmapInfo.cs | 1 - osu.Game/Beatmaps/FlatFileWorkingBeatmap.cs | 2 +- .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 8 ++-- .../Beatmaps/Formats/LegacyBeatmapEncoder.cs | 18 ++++--- osu.Game/Rulesets/RulesetInfo.cs | 6 --- osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs | 2 +- osu.Game/Screens/Edit/Editor.cs | 4 +- .../Select/Carousel/CarouselBeatmap.cs | 2 +- .../Select/Leaderboards/BeatmapLeaderboard.cs | 2 +- osu.Game/Screens/Select/SongSelect.cs | 2 +- osu.Game/Stores/BeatmapImporter.cs | 4 +- .../Tests/Beatmaps/BeatmapConversionTest.cs | 2 +- .../Tests/Beatmaps/HitObjectSampleTest.cs | 2 +- osu.Game/Tests/Visual/OsuTestScene.cs | 2 +- 33 files changed, 89 insertions(+), 90 deletions(-) diff --git a/osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs b/osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs index 0290230490..c8832dfdfb 100644 --- a/osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs +++ b/osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Mania public bool Matches(BeatmapInfo beatmapInfo) { - return !keys.HasFilter || (beatmapInfo.RulesetID == new ManiaRuleset().LegacyID && keys.IsInRange(ManiaBeatmapConverter.GetColumnCountForNonConvert(beatmapInfo))); + return !keys.HasFilter || (beatmapInfo.Ruleset.OnlineID == new ManiaRuleset().LegacyID && keys.IsInRange(ManiaBeatmapConverter.GetColumnCountForNonConvert(beatmapInfo))); } public bool TryParseCustomKeywordCriteria(string key, Operator op, string value) diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index 613874b7d6..b1d8575de4 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps Beatmap converted = base.ConvertBeatmap(original, cancellationToken); - if (original.BeatmapInfo.RulesetID == 3) + if (original.BeatmapInfo.Ruleset.OnlineID == 3) { // Post processing step to transform mania hit objects with the same start time into strong hits converted.HitObjects = converted.HitObjects.GroupBy(t => t.StartTime).Select(x => diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 0459753b28..1edd21b5a9 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -60,7 +60,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(0, beatmapInfo.AudioLeadIn); Assert.AreEqual(164471, metadata.PreviewTime); Assert.AreEqual(0.7f, beatmapInfo.StackLeniency); - Assert.IsTrue(beatmapInfo.RulesetID == 0); + Assert.IsTrue(beatmapInfo.Ruleset.OnlineID == 0); Assert.IsFalse(beatmapInfo.LetterboxInBreaks); Assert.IsFalse(beatmapInfo.SpecialStyle); Assert.IsFalse(beatmapInfo.WidescreenStoryboard); diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs index d12da1a22f..d19b3c71f1 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs @@ -195,7 +195,7 @@ namespace osu.Game.Tests.Beatmaps.Formats private IBeatmap convert(IBeatmap beatmap) { - switch (beatmap.BeatmapInfo.RulesetID) + switch (beatmap.BeatmapInfo.Ruleset.OnlineID) { case 0: beatmap.BeatmapInfo.Ruleset = new OsuRuleset().RulesetInfo; diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs index 06ed638e0a..2eb75259d9 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs @@ -52,7 +52,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(0, beatmapInfo.AudioLeadIn); Assert.AreEqual(0.7f, beatmapInfo.StackLeniency); Assert.AreEqual(false, beatmapInfo.SpecialStyle); - Assert.IsTrue(beatmapInfo.RulesetID == 0); + Assert.IsTrue(beatmapInfo.Ruleset.OnlineID == 0); Assert.AreEqual(false, beatmapInfo.LetterboxInBreaks); Assert.AreEqual(false, beatmapInfo.WidescreenStoryboard); Assert.AreEqual(CountdownType.None, beatmapInfo.Countdown); diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs b/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs index cf6488f721..81cb286058 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs @@ -82,8 +82,8 @@ namespace osu.Game.Tests.Visual.Editing AddStep("set target difficulty", () => { targetDifficulty = sameRuleset - ? importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo) && beatmap.RulesetID == Beatmap.Value.BeatmapInfo.RulesetID) - : importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo) && beatmap.RulesetID != Beatmap.Value.BeatmapInfo.RulesetID); + ? importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo) && beatmap.Ruleset.ShortName == Beatmap.Value.BeatmapInfo.Ruleset.ShortName) + : importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo) && beatmap.Ruleset.ShortName != Beatmap.Value.BeatmapInfo.Ruleset.ShortName); }); switchToDifficulty(() => targetDifficulty); confirmEditingBeatmap(() => targetDifficulty); diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs index 79afc8cf27..2b827bee74 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs @@ -45,7 +45,7 @@ namespace osu.Game.Tests.Visual.Editing } protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) - => beatmaps.GetWorkingBeatmap(importedBeatmapSet.Beatmaps.First(b => b.RulesetID == 0)); + => beatmaps.GetWorkingBeatmap(importedBeatmapSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)); protected override void LoadEditor() { diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs index a4a4f351ec..a9675a2ee2 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs @@ -110,7 +110,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("results displayed", () => Player.GetChildScreen() is ResultsScreen); AddAssert("ensure passing submission", () => Player.SubmittedScore?.ScoreInfo.Passed == true); - AddAssert("submitted score has correct ruleset ID", () => Player.SubmittedScore?.ScoreInfo.RulesetID == new TaikoRuleset().RulesetInfo.ID); + AddAssert("submitted score has correct ruleset ID", () => Player.SubmittedScore?.ScoreInfo.Ruleset.ShortName == new TaikoRuleset().RulesetInfo.ShortName); } [Test] @@ -130,7 +130,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("results displayed", () => Player.GetChildScreen() is ResultsScreen); AddAssert("ensure passing submission", () => Player.SubmittedScore?.ScoreInfo.Passed == true); - AddAssert("submitted score has correct ruleset ID", () => Player.SubmittedScore?.ScoreInfo.RulesetID == new ManiaRuleset().RulesetInfo.ID); + AddAssert("submitted score has correct ruleset ID", () => Player.SubmittedScore?.ScoreInfo.Ruleset.ShortName == new ManiaRuleset().RulesetInfo.ShortName); } [Test] diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index 8b7e1a1d85..0afd2c76dd 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -62,7 +62,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("import beatmap", () => { importedBeatmap = BeatmapImportHelper.LoadOszIntoOsu(game, virtualTrack: true).GetResultSafely(); - importedBeatmapId = importedBeatmap.Beatmaps.First(b => b.RulesetID == 0).OnlineID; + importedBeatmapId = importedBeatmap.Beatmaps.First(b => b.Ruleset.OnlineID == 0).OnlineID; }); } diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs index 4cd19b53a4..c79395b343 100644 --- a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs +++ b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs @@ -60,8 +60,8 @@ namespace osu.Game.Tests.Visual.Multiplayer { beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); importedSet = beatmaps.GetAllUsableBeatmapSets().First(); - InitialBeatmap = importedSet.Beatmaps.First(b => b.RulesetID == 0); - OtherBeatmap = importedSet.Beatmaps.Last(b => b.RulesetID == 0); + InitialBeatmap = importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0); + OtherBeatmap = importedSet.Beatmaps.Last(b => b.Ruleset.OnlineID == 0); }); AddStep("load multiplayer", () => LoadScreen(multiplayerComponents = new TestMultiplayerComponents())); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index 9d67742e4d..b84f7760e4 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -48,7 +48,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private void load() { importedSet = BeatmapImportHelper.LoadOszIntoOsu(game, virtualTrack: true).GetResultSafely(); - importedBeatmap = importedSet.Beatmaps.First(b => b.RulesetID == 0); + importedBeatmap = importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0); importedBeatmapId = importedBeatmap.OnlineID; } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 3f151a0ae8..3563869d8b 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -98,7 +98,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -234,7 +234,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -256,7 +256,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -286,7 +286,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -317,7 +317,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -339,7 +339,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -372,7 +372,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -392,7 +392,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -414,7 +414,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -453,7 +453,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -492,7 +492,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -531,7 +531,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -565,7 +565,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -605,7 +605,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -625,7 +625,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, AllowedMods = { new OsuModHidden() } } @@ -665,7 +665,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -696,7 +696,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -714,7 +714,7 @@ namespace osu.Game.Tests.Visual.Multiplayer roomManager.ServerSideRooms[0].Playlist.Add(new PlaylistItem { ID = 2, - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, }); }); @@ -742,7 +742,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -778,7 +778,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -817,7 +817,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -828,7 +828,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("join other user", () => client.AddUser(new APIUser { Id = 1234 })); AddStep("add item as other user", () => client.AddUserPlaylistItem(1234, new MultiplayerPlaylistItem(new PlaylistItem { - BeatmapID = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo.OnlineID + BeatmapID = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo.OnlineID }))); AddUntilStep("item arrived in playlist", () => client.Room?.Playlist.Count == 2); @@ -848,7 +848,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -859,7 +859,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("join other user", () => client.AddUser(new APIUser { Id = 1234 })); AddStep("add item as other user", () => client.AddUserPlaylistItem(1234, new MultiplayerPlaylistItem(new PlaylistItem { - BeatmapID = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo.OnlineID + BeatmapID = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo.OnlineID }))); AddUntilStep("item arrived in playlist", () => client.Room?.Playlist.Count == 2); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index 5465061891..457b53ae61 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -107,7 +107,7 @@ namespace osu.Game.Tests.Visual.Multiplayer BeatmapInfo selectedBeatmap = null; AddStep("select beatmap", - () => songSelect.Carousel.SelectBeatmap(selectedBeatmap = beatmaps.Where(beatmap => beatmap.RulesetID == new OsuRuleset().LegacyID).ElementAt(1))); + () => songSelect.Carousel.SelectBeatmap(selectedBeatmap = beatmaps.Where(beatmap => beatmap.Ruleset.OnlineID == new OsuRuleset().LegacyID).ElementAt(1))); AddUntilStep("wait for selection", () => Beatmap.Value.BeatmapInfo.Equals(selectedBeatmap)); AddStep("exit song select", () => songSelect.Exit()); @@ -139,7 +139,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("change ruleset", () => Ruleset.Value = new TaikoRuleset().RulesetInfo); AddStep("select beatmap", - () => songSelect.Carousel.SelectBeatmap(selectedBeatmap = beatmaps.First(beatmap => beatmap.RulesetID == new TaikoRuleset().LegacyID))); + () => songSelect.Carousel.SelectBeatmap(selectedBeatmap = beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == new TaikoRuleset().LegacyID))); AddUntilStep("wait for selection", () => Beatmap.Value.BeatmapInfo.Equals(selectedBeatmap)); AddStep("set mods", () => SelectedMods.Value = new[] { new TaikoModDoubleTime() }); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs index d970ab6c34..936798e6b4 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs @@ -57,7 +57,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); importedSet = beatmaps.GetAllUsableBeatmapSets().First(); - importedBeatmap = importedSet.Beatmaps.First(b => b.RulesetID == 0); + importedBeatmap = importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0); }); AddStep("change to all players mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers })); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs index d83421ee3a..ddf794b437 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs @@ -62,7 +62,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); importedSet = beatmaps.GetAllUsableBeatmapSets().First(); - importedBeatmap = importedSet.Beatmaps.First(b => b.RulesetID == 0); + importedBeatmap = importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0); }); AddStep("change to all players mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers })); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs index 73c67d26d9..781f0a1824 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs @@ -73,7 +73,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -94,7 +94,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -132,7 +132,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -158,7 +158,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 0298c3bea9..830631ebad 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -586,7 +586,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("filter to ruleset 0", () => carousel.Filter(new FilterCriteria { Ruleset = rulesets.AvailableRulesets.ElementAt(0) }, false)); AddStep("select filtered map skipping filtered", () => carousel.SelectBeatmap(testMixed.Beatmaps[1], false)); - AddAssert("unfiltered beatmap not selected", () => carousel.SelectedBeatmapInfo.RulesetID == 0); + AddAssert("unfiltered beatmap not selected", () => carousel.SelectedBeatmapInfo.Ruleset.OnlineID == 0); AddStep("remove mixed set", () => { diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 458c6130c7..136173c8a7 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -325,10 +325,10 @@ namespace osu.Game.Tests.Visual.SongSelect changeRuleset(2); addRulesetImportStep(2); addRulesetImportStep(1); - AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo.RulesetID == 2); + AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo.Ruleset.OnlineID == 2); changeRuleset(1); - AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo.RulesetID == 1); + AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo.Ruleset.OnlineID == 1); changeRuleset(0); AddUntilStep("no selection", () => songSelect.Carousel.SelectedBeatmapInfo == null); @@ -341,7 +341,7 @@ namespace osu.Game.Tests.Visual.SongSelect changeRuleset(2); addRulesetImportStep(2); - AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo.RulesetID == 2); + AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo.Ruleset.OnlineID == 2); addRulesetImportStep(0); addRulesetImportStep(0); @@ -352,7 +352,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("select beatmap/ruleset externally", () => { target = manager.GetAllUsableBeatmapSets() - .Last(b => b.Beatmaps.Any(bi => bi.RulesetID == 0)).Beatmaps.Last(); + .Last(b => b.Beatmaps.Any(bi => bi.Ruleset.OnlineID == 0)).Beatmaps.Last(); Ruleset.Value = rulesets.AvailableRulesets.First(r => r.OnlineID == 0); Beatmap.Value = manager.GetWorkingBeatmap(target); @@ -371,7 +371,7 @@ namespace osu.Game.Tests.Visual.SongSelect changeRuleset(2); addRulesetImportStep(2); - AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo.RulesetID == 2); + AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo.Ruleset.OnlineID == 2); addRulesetImportStep(0); addRulesetImportStep(0); @@ -382,7 +382,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("select beatmap/ruleset externally", () => { target = manager.GetAllUsableBeatmapSets() - .Last(b => b.Beatmaps.Any(bi => bi.RulesetID == 0)).Beatmaps.Last(); + .Last(b => b.Beatmaps.Any(bi => bi.Ruleset.OnlineID == 0)).Beatmaps.Last(); Beatmap.Value = manager.GetWorkingBeatmap(target); Ruleset.Value = rulesets.AvailableRulesets.First(r => r.OnlineID == 0); @@ -493,9 +493,9 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("select beatmap externally", () => { target = manager.GetAllUsableBeatmapSets() - .First(b => b.Beatmaps.Any(bi => bi.RulesetID == targetRuleset)) + .First(b => b.Beatmaps.Any(bi => bi.Ruleset.OnlineID == targetRuleset)) .Beatmaps - .First(bi => bi.RulesetID == targetRuleset); + .First(bi => bi.Ruleset.OnlineID == targetRuleset); Beatmap.Value = manager.GetWorkingBeatmap(target); }); @@ -544,7 +544,7 @@ namespace osu.Game.Tests.Visual.SongSelect { target = manager .GetAllUsableBeatmapSets() - .First(b => b.Beatmaps.Any(bi => bi.RulesetID == 1)) + .First(b => b.Beatmaps.Any(bi => bi.Ruleset.OnlineID == 1)) .Beatmaps.First(); Beatmap.Value = manager.GetWorkingBeatmap(target); @@ -799,8 +799,8 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestChangeRulesetWhilePresentingScore() { - BeatmapInfo getPresentBeatmap() => manager.GetAllUsableBeatmapSets().Where(s => !s.DeletePending).SelectMany(s => s.Beatmaps).First(b => b.RulesetID == 0); - BeatmapInfo getSwitchBeatmap() => manager.GetAllUsableBeatmapSets().Where(s => !s.DeletePending).SelectMany(s => s.Beatmaps).First(b => b.RulesetID == 1); + BeatmapInfo getPresentBeatmap() => manager.GetAllUsableBeatmapSets().Where(s => !s.DeletePending).SelectMany(s => s.Beatmaps).First(b => b.Ruleset.OnlineID == 0); + BeatmapInfo getSwitchBeatmap() => manager.GetAllUsableBeatmapSets().Where(s => !s.DeletePending).SelectMany(s => s.Beatmaps).First(b => b.Ruleset.OnlineID == 1); changeRuleset(0); @@ -831,8 +831,8 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestChangeBeatmapWhilePresentingScore() { - BeatmapInfo getPresentBeatmap() => manager.GetAllUsableBeatmapSets().Where(s => !s.DeletePending).SelectMany(s => s.Beatmaps).First(b => b.RulesetID == 0); - BeatmapInfo getSwitchBeatmap() => manager.GetAllUsableBeatmapSets().Where(s => !s.DeletePending).SelectMany(s => s.Beatmaps).First(b => b.RulesetID == 1); + BeatmapInfo getPresentBeatmap() => manager.GetAllUsableBeatmapSets().Where(s => !s.DeletePending).SelectMany(s => s.Beatmaps).First(b => b.Ruleset.OnlineID == 0); + BeatmapInfo getSwitchBeatmap() => manager.GetAllUsableBeatmapSets().Where(s => !s.DeletePending).SelectMany(s => s.Beatmaps).First(b => b.Ruleset.OnlineID == 1); changeRuleset(0); diff --git a/osu.Game.Tournament/Components/TournamentModIcon.cs b/osu.Game.Tournament/Components/TournamentModIcon.cs index ed8a36c220..57a0390ac2 100644 --- a/osu.Game.Tournament/Components/TournamentModIcon.cs +++ b/osu.Game.Tournament/Components/TournamentModIcon.cs @@ -47,7 +47,7 @@ namespace osu.Game.Tournament.Components return; } - var ruleset = rulesets.GetRuleset(ladderInfo.Ruleset.Value?.ID ?? 0); + var ruleset = rulesets.GetRuleset(ladderInfo.Ruleset.Value?.OnlineID ?? 0); var modIcon = ruleset?.CreateInstance().CreateModFromAcronym(modAcronym); if (modIcon == null) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 4e1a34ddbf..3e6e33f1d0 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -155,7 +155,6 @@ namespace osu.Game.Beatmaps [Ignored] public int RulesetID { - get => Ruleset.OnlineID; set { if (!string.IsNullOrEmpty(Ruleset.InstantiationInfo)) diff --git a/osu.Game/Beatmaps/FlatFileWorkingBeatmap.cs b/osu.Game/Beatmaps/FlatFileWorkingBeatmap.cs index dc8201a402..163da12b2e 100644 --- a/osu.Game/Beatmaps/FlatFileWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/FlatFileWorkingBeatmap.cs @@ -30,7 +30,7 @@ namespace osu.Game.Beatmaps { this.beatmap = beatmap; - beatmap.BeatmapInfo.Ruleset = rulesetProvider(beatmap.BeatmapInfo.RulesetID).RulesetInfo; + beatmap.BeatmapInfo.Ruleset = rulesetProvider(beatmap.BeatmapInfo.Ruleset.OnlineID).RulesetInfo; if (beatmapId.HasValue) beatmap.BeatmapInfo.OnlineID = beatmapId.Value; diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 8f3f05aa9f..35d1cefeca 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -141,9 +141,11 @@ namespace osu.Game.Beatmaps.Formats break; case @"Mode": - beatmap.BeatmapInfo.RulesetID = Parsing.ParseInt(pair.Value); + int rulesetID = Parsing.ParseInt(pair.Value); - switch (beatmap.BeatmapInfo.RulesetID) + beatmap.BeatmapInfo.RulesetID = rulesetID; + + switch (rulesetID) { case 0: parser = new Rulesets.Objects.Legacy.Osu.ConvertHitObjectParser(getOffsetTime(), FormatVersion); @@ -397,7 +399,7 @@ namespace osu.Game.Beatmaps.Formats OmitFirstBarLine = omitFirstBarSignature, }; - bool isOsuRuleset = beatmap.BeatmapInfo.RulesetID == 0; + bool isOsuRuleset = beatmap.BeatmapInfo.Ruleset.OnlineID == 0; // scrolling rulesets use effect points rather than difficulty points for scroll speed adjustments. if (!isOsuRuleset) effectPoint.ScrollSpeed = speedMultiplier; diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 9d848fd8a4..7ddbc2f768 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -35,6 +35,8 @@ namespace osu.Game.Beatmaps.Formats [CanBeNull] private readonly ISkin skin; + private readonly int onlineRulesetID; + /// /// Creates a new . /// @@ -45,7 +47,9 @@ namespace osu.Game.Beatmaps.Formats this.beatmap = beatmap; this.skin = skin; - if (beatmap.BeatmapInfo.RulesetID < 0 || beatmap.BeatmapInfo.RulesetID > 3) + onlineRulesetID = beatmap.BeatmapInfo.Ruleset.OnlineID; + + if (onlineRulesetID < 0 || onlineRulesetID > 3) throw new ArgumentException("Only beatmaps in the osu, taiko, catch, or mania rulesets can be encoded to the legacy beatmap format.", nameof(beatmap)); } @@ -88,7 +92,7 @@ namespace osu.Game.Beatmaps.Formats writer.WriteLine(FormattableString.Invariant($"Countdown: {(int)beatmap.BeatmapInfo.Countdown}")); writer.WriteLine(FormattableString.Invariant($"SampleSet: {toLegacySampleBank((beatmap.HitObjects.FirstOrDefault()?.SampleControlPoint ?? SampleControlPoint.DEFAULT).SampleBank)}")); writer.WriteLine(FormattableString.Invariant($"StackLeniency: {beatmap.BeatmapInfo.StackLeniency}")); - writer.WriteLine(FormattableString.Invariant($"Mode: {beatmap.BeatmapInfo.RulesetID}")); + writer.WriteLine(FormattableString.Invariant($"Mode: {onlineRulesetID}")); writer.WriteLine(FormattableString.Invariant($"LetterboxInBreaks: {(beatmap.BeatmapInfo.LetterboxInBreaks ? '1' : '0')}")); // if (beatmap.BeatmapInfo.UseSkinSprites) // writer.WriteLine(@"UseSkinSprites: 1"); @@ -102,7 +106,7 @@ namespace osu.Game.Beatmaps.Formats writer.WriteLine(@"EpilepsyWarning: 1"); if (beatmap.BeatmapInfo.CountdownOffset > 0) writer.WriteLine(FormattableString.Invariant($@"CountdownOffset: {beatmap.BeatmapInfo.CountdownOffset}")); - if (beatmap.BeatmapInfo.RulesetID == 3) + if (onlineRulesetID == 3) writer.WriteLine(FormattableString.Invariant($"SpecialStyle: {(beatmap.BeatmapInfo.SpecialStyle ? '1' : '0')}")); writer.WriteLine(FormattableString.Invariant($"WidescreenStoryboard: {(beatmap.BeatmapInfo.WidescreenStoryboard ? '1' : '0')}")); if (beatmap.BeatmapInfo.SamplesMatchPlaybackRate) @@ -147,7 +151,7 @@ namespace osu.Game.Beatmaps.Formats writer.WriteLine(FormattableString.Invariant($"ApproachRate: {beatmap.Difficulty.ApproachRate}")); // Taiko adjusts the slider multiplier (see: LEGACY_TAIKO_VELOCITY_MULTIPLIER) - writer.WriteLine(beatmap.BeatmapInfo.RulesetID == 1 + writer.WriteLine(onlineRulesetID == 1 ? FormattableString.Invariant($"SliderMultiplier: {beatmap.Difficulty.SliderMultiplier / LEGACY_TAIKO_VELOCITY_MULTIPLIER}") : FormattableString.Invariant($"SliderMultiplier: {beatmap.Difficulty.SliderMultiplier}")); @@ -179,7 +183,7 @@ namespace osu.Game.Beatmaps.Formats SampleControlPoint lastRelevantSamplePoint = null; DifficultyControlPoint lastRelevantDifficultyPoint = null; - bool isOsuRuleset = beatmap.BeatmapInfo.RulesetID == 0; + bool isOsuRuleset = onlineRulesetID == 0; // iterate over hitobjects and pull out all required sample and difficulty changes extractDifficultyControlPoints(beatmap.HitObjects); @@ -318,7 +322,7 @@ namespace osu.Game.Beatmaps.Formats { Vector2 position = new Vector2(256, 192); - switch (beatmap.BeatmapInfo.RulesetID) + switch (onlineRulesetID) { case 0: case 2: @@ -372,7 +376,7 @@ namespace osu.Game.Beatmaps.Formats break; case IHasDuration _: - if (beatmap.BeatmapInfo.RulesetID == 3) + if (onlineRulesetID == 3) type |= LegacyHitObjectType.Hold; else type |= LegacyHitObjectType.Spinner; diff --git a/osu.Game/Rulesets/RulesetInfo.cs b/osu.Game/Rulesets/RulesetInfo.cs index 2e2ec5c024..e2007c910e 100644 --- a/osu.Game/Rulesets/RulesetInfo.cs +++ b/osu.Game/Rulesets/RulesetInfo.cs @@ -98,11 +98,5 @@ namespace osu.Game.Rulesets return ruleset; } - - #region Compatibility properties - - public int ID => OnlineID; - - #endregion } } diff --git a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs index 2902ff7848..9460ec680c 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs @@ -34,7 +34,7 @@ namespace osu.Game.Scoring.Legacy this.score = score; this.beatmap = beatmap; - if (score.ScoreInfo.BeatmapInfo.RulesetID < 0 || score.ScoreInfo.BeatmapInfo.RulesetID > 3) + if (score.ScoreInfo.BeatmapInfo.Ruleset.OnlineID < 0 || score.ScoreInfo.BeatmapInfo.Ruleset.OnlineID > 3) throw new ArgumentException("Only scores in the osu, taiko, catch, or mania rulesets can be encoded to the legacy score format.", nameof(score)); } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index b42f629aad..55b01bd8ab 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -338,7 +338,7 @@ namespace osu.Game.Screens.Edit public EditorState GetState([CanBeNull] BeatmapInfo nextBeatmap = null) => new EditorState { Time = clock.CurrentTimeAccurate, - ClipboardContent = nextBeatmap == null || editorBeatmap.BeatmapInfo.RulesetID == nextBeatmap.RulesetID ? Clipboard.Content.Value : string.Empty + ClipboardContent = nextBeatmap == null || editorBeatmap.BeatmapInfo.Ruleset.ShortName == nextBeatmap.Ruleset.ShortName ? Clipboard.Content.Value : string.Empty }; /// @@ -780,7 +780,7 @@ namespace osu.Game.Screens.Edit var difficultyItems = new List(); - foreach (var rulesetBeatmaps in beatmapSet.Beatmaps.GroupBy(b => b.RulesetID).OrderBy(group => group.Key)) + foreach (var rulesetBeatmaps in beatmapSet.Beatmaps.GroupBy(b => b.Ruleset.ShortName).OrderBy(group => group.Key)) { if (difficultyItems.Count > 0) difficultyItems.Add(new EditorMenuItemSpacer()); diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index e6dee0233f..3962f7b26f 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -29,7 +29,7 @@ namespace osu.Game.Screens.Select.Carousel bool match = criteria.Ruleset == null || BeatmapInfo.Ruleset.ShortName == criteria.Ruleset.ShortName || - (BeatmapInfo.RulesetID == 0 && criteria.Ruleset.OnlineID != 0 && criteria.AllowConvertedBeatmaps); + (BeatmapInfo.Ruleset.OnlineID == 0 && criteria.Ruleset.OnlineID != 0 && criteria.AllowConvertedBeatmaps); if (BeatmapInfo.BeatmapSet?.Equals(criteria.SelectedBeatmapSet) == true) { diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index f25997650b..2070e53257 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -155,7 +155,7 @@ namespace osu.Game.Screens.Select.Leaderboards var scores = r.All() .AsEnumerable() // TODO: update to use a realm filter directly (or at least figure out the beatmap part to reduce scope). - .Where(s => !s.DeletePending && s.BeatmapInfo.ID == fetchBeatmapInfo.ID && s.Ruleset.OnlineID == ruleset.Value.ID); + .Where(s => !s.DeletePending && s.BeatmapInfo.ID == fetchBeatmapInfo.ID && s.Ruleset.ShortName == ruleset.Value.ShortName); if (filterMods && !mods.Value.Any()) { diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 837f30eb2b..10150fcd9f 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -502,7 +502,7 @@ namespace osu.Game.Screens.Select // clear pending task immediately to track any potential nested debounce operation. selectionChangedDebounce = null; - Logger.Log($"updating selection with beatmap:{beatmap?.ID.ToString() ?? "null"} ruleset:{ruleset?.ID.ToString() ?? "null"}"); + Logger.Log($"updating selection with beatmap:{beatmap?.ID.ToString() ?? "null"} ruleset:{ruleset?.ShortName ?? "null"}"); if (transferRulesetValue()) { diff --git a/osu.Game/Stores/BeatmapImporter.cs b/osu.Game/Stores/BeatmapImporter.cs index a6f20c8d4f..709dd67087 100644 --- a/osu.Game/Stores/BeatmapImporter.cs +++ b/osu.Game/Stores/BeatmapImporter.cs @@ -219,11 +219,11 @@ namespace osu.Game.Stores var decodedInfo = decoded.BeatmapInfo; var decodedDifficulty = decodedInfo.Difficulty; - var ruleset = realm.All().FirstOrDefault(r => r.OnlineID == decodedInfo.RulesetID); + var ruleset = realm.All().FirstOrDefault(r => r.OnlineID == decodedInfo.Ruleset.OnlineID); if (ruleset?.Available != true) { - Logger.Log($"Skipping import of {file.Filename} due to missing local ruleset {decodedInfo.RulesetID}.", LoggingTarget.Database); + Logger.Log($"Skipping import of {file.Filename} due to missing local ruleset {decodedInfo.Ruleset.OnlineID}.", LoggingTarget.Database); continue; } diff --git a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs index 897d4363f1..8d622955b7 100644 --- a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs +++ b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs @@ -175,7 +175,7 @@ namespace osu.Game.Tests.Beatmaps var beatmap = decoder.Decode(stream); var rulesetInstance = CreateRuleset(); - beatmap.BeatmapInfo.Ruleset = beatmap.BeatmapInfo.RulesetID == rulesetInstance.RulesetInfo.ID ? rulesetInstance.RulesetInfo : new RulesetInfo(); + beatmap.BeatmapInfo.Ruleset = beatmap.BeatmapInfo.Ruleset.OnlineID == rulesetInstance.RulesetInfo.OnlineID ? rulesetInstance.RulesetInfo : new RulesetInfo(); return beatmap; } diff --git a/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs b/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs index f7e154b5e7..2a3e51b4f5 100644 --- a/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs +++ b/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs @@ -79,7 +79,7 @@ namespace osu.Game.Tests.Beatmaps currentTestBeatmap = Decoder.GetDecoder(reader).Decode(reader); // populate ruleset for beatmap converters that require it to be present. - var ruleset = rulesetStore.GetRuleset(currentTestBeatmap.BeatmapInfo.RulesetID); + var ruleset = rulesetStore.GetRuleset(currentTestBeatmap.BeatmapInfo.Ruleset.OnlineID); Debug.Assert(ruleset != null); diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 42e96f80ca..ec02655544 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -282,7 +282,7 @@ namespace osu.Game.Tests.Visual Status = beatmap.Status, Checksum = beatmap.MD5Hash, AuthorID = beatmap.Metadata.Author.OnlineID, - RulesetID = beatmap.RulesetID, + RulesetID = beatmap.Ruleset.OnlineID, StarRating = beatmap.StarRating, DifficultyName = beatmap.DifficultyName, } From 5637fd64d641dcf147d7f9e2d17532bf351264ed Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 15:59:20 +0900 Subject: [PATCH 712/996] Perform ordering using `IComparable` instead --- .../NonVisual/RulesetInfoOrderingTest.cs | 2 +- osu.Game/Rulesets/EFRulesetInfo.cs | 2 ++ osu.Game/Rulesets/IRulesetInfo.cs | 2 +- osu.Game/Rulesets/RulesetInfo.cs | 19 ++++++++++++++----- osu.Game/Rulesets/RulesetStore.cs | 2 +- .../Select/Carousel/CarouselBeatmap.cs | 2 +- .../Select/Carousel/CarouselBeatmapSet.cs | 2 +- 7 files changed, 21 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/NonVisual/RulesetInfoOrderingTest.cs b/osu.Game.Tests/NonVisual/RulesetInfoOrderingTest.cs index e7c04d27c1..ae999d08d5 100644 --- a/osu.Game.Tests/NonVisual/RulesetInfoOrderingTest.cs +++ b/osu.Game.Tests/NonVisual/RulesetInfoOrderingTest.cs @@ -25,7 +25,7 @@ namespace osu.Game.Tests.NonVisual new RulesetInfo("custom3", "Custom Ruleset 3", string.Empty, -1), }; - var orderedRulesets = rulesets.OrderBy(r => r.SortID); + var orderedRulesets = rulesets.OrderBy(r => r); // Ensure all customs are after official. Assert.That(orderedRulesets.Select(r => r.OnlineID), Is.EqualTo(new[] { 0, 2, -1, -1, -1, -1 })); diff --git a/osu.Game/Rulesets/EFRulesetInfo.cs b/osu.Game/Rulesets/EFRulesetInfo.cs index 9559cfa00c..b4a64fe932 100644 --- a/osu.Game/Rulesets/EFRulesetInfo.cs +++ b/osu.Game/Rulesets/EFRulesetInfo.cs @@ -42,6 +42,8 @@ namespace osu.Game.Rulesets public bool Equals(EFRulesetInfo other) => other != null && ID == other.ID && Available == other.Available && Name == other.Name && InstantiationInfo == other.InstantiationInfo; + public int CompareTo(RulesetInfo other) => ID?.CompareTo(other.ID) ?? -1; + public override bool Equals(object obj) => obj is EFRulesetInfo rulesetInfo && Equals(rulesetInfo); public bool Equals(IRulesetInfo other) => other is RulesetInfo b && Equals(b); diff --git a/osu.Game/Rulesets/IRulesetInfo.cs b/osu.Game/Rulesets/IRulesetInfo.cs index 6599e0d59d..44731a2495 100644 --- a/osu.Game/Rulesets/IRulesetInfo.cs +++ b/osu.Game/Rulesets/IRulesetInfo.cs @@ -11,7 +11,7 @@ namespace osu.Game.Rulesets /// /// A representation of a ruleset's metadata. /// - public interface IRulesetInfo : IHasOnlineID, IEquatable + public interface IRulesetInfo : IHasOnlineID, IEquatable, IComparable { /// /// The user-exposed name of this ruleset. diff --git a/osu.Game/Rulesets/RulesetInfo.cs b/osu.Game/Rulesets/RulesetInfo.cs index d721926e95..896b9ee279 100644 --- a/osu.Game/Rulesets/RulesetInfo.cs +++ b/osu.Game/Rulesets/RulesetInfo.cs @@ -24,11 +24,6 @@ namespace osu.Game.Rulesets public string InstantiationInfo { get; set; } = string.Empty; - /// - /// A best effort sort ID which provides stable ordering and puts online rulesets before non-online rulesets. - /// - public int SortID => OnlineID >= 0 ? OnlineID : Math.Abs(ShortName.GetHashCode()); - public RulesetInfo(string shortName, string name, string instantiationInfo, int onlineID) { ShortName = shortName; @@ -62,6 +57,20 @@ namespace osu.Game.Rulesets public bool Equals(IRulesetInfo? other) => other is RulesetInfo b && Equals(b); + public int CompareTo(RulesetInfo other) + { + // Official rulesets are always given precedence for the time being. + if (OnlineID >= 0) + { + if (other.OnlineID >= 0) + return OnlineID.CompareTo(other.OnlineID); + + return -1; + } + + return string.Compare(ShortName, other.ShortName, StringComparison.Ordinal); + } + public override int GetHashCode() { // Importantly, ignore the underlying realm hash code, as it will usually not match. diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index a9fd8f430e..d017d54ed9 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -163,7 +163,7 @@ namespace osu.Game.Rulesets } } - availableRulesets.AddRange(detachedRulesets.OrderBy(r => r.SortID)); + availableRulesets.AddRange(detachedRulesets.OrderBy(r => r)); }); } diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index 9d49fdaf6e..0045e7ba4f 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -89,7 +89,7 @@ namespace osu.Game.Screens.Select.Carousel { default: case SortMode.Difficulty: - int ruleset = BeatmapInfo.Ruleset.SortID.CompareTo(otherBeatmap.BeatmapInfo.Ruleset.SortID); + int ruleset = BeatmapInfo.Ruleset.CompareTo(otherBeatmap.BeatmapInfo.Ruleset); if (ruleset != 0) return ruleset; return BeatmapInfo.StarRating.CompareTo(otherBeatmap.BeatmapInfo.StarRating); diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index 274be7b73a..fc4b6c27f3 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -39,7 +39,7 @@ namespace osu.Game.Screens.Select.Carousel beatmapSet.Beatmaps .Where(b => !b.Hidden) - .OrderBy(b => b.Ruleset.SortID) + .OrderBy(b => b.Ruleset) .ThenBy(b => b.StarRating) .Select(b => new CarouselBeatmap(b)) .ForEach(AddChild); From b87d1a61a8d2d2d48674ba3bed92568cabef7c25 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 16:39:36 +0900 Subject: [PATCH 713/996] Fix `ButtonSystem` null reference crash due to missing null check in delayed animations ```csharp [runtime] 2022-01-27 07:36:34 [error]: System.NullReferenceException: Object reference not set to an instance of an object. [runtime] 2022-01-27 07:36:34 [error]: at osu.Game.Screens.Menu.ButtonSystem.<>c__DisplayClass56_0.b__1() in /Users/dean/Projects/osu/osu.Game/Screens/Menu/ButtonSystem.cs:line 357 [runtime] 2022-01-27 07:36:34 [error]: at osu.Framework.Threading.ScheduledDelegate.RunTaskInternal() [runtime] 2022-01-27 07:36:34 [error]: at osu.Framework.Threading.Scheduler.Update() [runtime] 2022-01-27 07:36:34 [error]: at osu.Framework.Graphics.Drawable.UpdateSubTree() ``` --- osu.Game/Screens/Menu/ButtonSystem.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index 32731407fd..b03425fef4 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -49,6 +50,7 @@ namespace osu.Game.Screens.Menu public const float BUTTON_WIDTH = 140f; public const float WEDGE_WIDTH = 20; + [CanBeNull] private OsuLogo logo; /// @@ -328,9 +330,9 @@ namespace osu.Game.Screens.Menu game?.Toolbar.Hide(); - logo.ClearTransforms(targetMember: nameof(Position)); - logo.MoveTo(new Vector2(0.5f), 800, Easing.OutExpo); - logo.ScaleTo(1, 800, Easing.OutExpo); + logo?.ClearTransforms(targetMember: nameof(Position)); + logo?.MoveTo(new Vector2(0.5f), 800, Easing.OutExpo); + logo?.ScaleTo(1, 800, Easing.OutExpo); }, buttonArea.Alpha * 150); break; @@ -354,7 +356,7 @@ namespace osu.Game.Screens.Menu logoDelayedAction = Scheduler.AddDelayed(() => { if (impact) - logo.Impact(); + logo?.Impact(); game?.Toolbar.Show(); }, 200); From 0a45aa80cb011e658dc1d7c3504d2fe4ef12f079 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 16:16:49 +0900 Subject: [PATCH 714/996] Remove unnecessary double-schedule in `UpdateBeatmapSet` --- osu.Game/Screens/Select/BeatmapCarousel.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index dff2c598c3..94e520ba1c 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -311,13 +311,10 @@ namespace osu.Game.Screens.Select itemsCache.Invalidate(); - Schedule(() => - { - if (!Scroll.UserScrolling) - ScrollToSelected(true); + if (!Scroll.UserScrolling) + ScrollToSelected(true); - BeatmapSetsChanged?.Invoke(); - }); + BeatmapSetsChanged?.Invoke(); }); /// From 449e9bcf5c123e1e1c92534f8f3a710412a5028d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 16:17:38 +0900 Subject: [PATCH 715/996] Ensure beatmap carousel scroll position is maintained during deletion operations --- osu.Game/Screens/Select/BeatmapCarousel.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 94e520ba1c..c27915c383 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -286,6 +286,9 @@ namespace osu.Game.Screens.Select root.RemoveChild(existingSet); itemsCache.Invalidate(); + + if (!Scroll.UserScrolling) + ScrollToSelected(true); }); public void UpdateBeatmapSet(BeatmapSetInfo beatmapSet) => Schedule(() => From f2cecad83b2e1df2b2e15c5cd49e04534cf55dca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 16:38:02 +0900 Subject: [PATCH 716/996] Add failing test coverage showing carousel deletions don't keep scroll position --- .../SongSelect/TestSceneBeatmapCarousel.cs | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 0298c3bea9..2f2001c4f2 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -9,6 +9,7 @@ using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Rulesets; @@ -40,6 +41,36 @@ namespace osu.Game.Tests.Visual.SongSelect this.rulesets = rulesets; } + [Test] + public void TestScrollPositionMaintainedOnAdd() + { + loadBeatmaps(count: 1, randomDifficulties: false); + + for (int i = 0; i < 10; i++) + { + AddRepeatStep("Add some sets", () => carousel.UpdateBeatmapSet(TestResources.CreateTestBeatmapSetInfo()), 4); + + checkSelectionIsCentered(); + } + } + + [Test] + public void TestScrollPositionMaintainedOnDelete() + { + loadBeatmaps(count: 50, randomDifficulties: false); + + for (int i = 0; i < 10; i++) + { + AddRepeatStep("Remove some sets", () => + carousel.RemoveBeatmapSet(carousel.Items.Select(item => item.Item) + .OfType() + .OrderBy(item => item.GetHashCode()) + .First(item => item.State.Value != CarouselItemState.Selected && item.Visible).BeatmapSet), 4); + + checkSelectionIsCentered(); + } + } + [Test] public void TestManyPanels() { @@ -813,6 +844,18 @@ namespace osu.Game.Tests.Visual.SongSelect carousel.Items.Count(s => (diff ? s.Item is CarouselBeatmap : s.Item is CarouselBeatmapSet) && s.Item.Visible) == count); } + private void checkSelectionIsCentered() + { + AddAssert("Selected panel is centered", () => + { + return Precision.AlmostEquals( + carousel.ScreenSpaceDrawQuad.Centre, + carousel.Items + .First(i => i.Item.State.Value == CarouselItemState.Selected) + .ScreenSpaceDrawQuad.Centre, 100); + }); + } + private void checkNoSelection() => AddAssert("Selection is null", () => currentSelection == null); private void nextRandom() => From 3ae5973ab7de887e1908430d3601fbc18301489e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 17:08:12 +0900 Subject: [PATCH 717/996] Fix compilation error due to commit split --- osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs | 2 +- osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index 3962f7b26f..1f346d19f0 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -89,7 +89,7 @@ namespace osu.Game.Screens.Select.Carousel { default: case SortMode.Difficulty: - int ruleset = BeatmapInfo.RulesetID.CompareTo(otherBeatmap.BeatmapInfo.RulesetID); + int ruleset = BeatmapInfo.Ruleset.OnlineID.CompareTo(otherBeatmap.BeatmapInfo.Ruleset.OnlineID); if (ruleset != 0) return ruleset; return BeatmapInfo.StarRating.CompareTo(otherBeatmap.BeatmapInfo.StarRating); diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index b2b3b5411c..6156fea769 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -39,7 +39,7 @@ namespace osu.Game.Screens.Select.Carousel beatmapSet.Beatmaps .Where(b => !b.Hidden) - .OrderBy(b => b.RulesetID) + .OrderBy(b => b.Ruleset.OnlineID) .ThenBy(b => b.StarRating) .Select(b => new CarouselBeatmap(b)) .ForEach(AddChild); From 831fa44433059078f0c96d06cf0e69b8686e5d8c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 19:35:04 +0900 Subject: [PATCH 718/996] Fix song select tests not waiting for beatmap imports to arrive After the change to realm, notification fires could take a frame or two. We aren't accounting for this. Fixes test failures like https://github.com/ppy/osu/runs/4963255990?check_suite_focus=true --- .../SongSelect/TestScenePlaySongSelect.cs | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 458c6130c7..ca0f2b8012 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -13,6 +13,7 @@ using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Configuration; +using osu.Game.Database; using osu.Game.Extensions; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API.Requests.Responses; @@ -73,6 +74,17 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("delete all beatmaps", () => manager?.Delete()); } + public override void TearDownSteps() + { + base.TearDownSteps(); + + AddStep("remove song select", () => + { + songSelect?.Expire(); + songSelect = null; + }); + } + [Test] public void TestSingleFilterOnEnter() { @@ -870,9 +882,16 @@ namespace osu.Game.Tests.Visual.SongSelect return set.ChildrenOfType().ToList().FindIndex(i => i == icon); } - private void addRulesetImportStep(int id) => AddStep($"import test map for ruleset {id}", () => importForRuleset(id)); + private void addRulesetImportStep(int id) + { + Live imported = null; + AddStep($"import test map for ruleset {id}", () => imported = importForRuleset(id)); + // This is specifically for cases where the add is happening post song select load. + // For cases where song select is null, the assertions are provided by the load checks. + AddUntilStep("wait for imported to arrive in carousel", () => songSelect == null || songSelect.Carousel.BeatmapSets.Any(s => s.ID == imported?.ID)); + } - private void importForRuleset(int id) => manager.Import(TestResources.CreateTestBeatmapSetInfo(3, rulesets.AvailableRulesets.Where(r => r.OnlineID == id).ToArray())); + private Live importForRuleset(int id) => manager.Import(TestResources.CreateTestBeatmapSetInfo(3, rulesets.AvailableRulesets.Where(r => r.OnlineID == id).ToArray())); private void checkMusicPlaying(bool playing) => AddUntilStep($"music {(playing ? "" : "not ")}playing", () => music.IsPlaying == playing); From 1bdf16494baa48fd15715ece296b198f6f60b64f Mon Sep 17 00:00:00 2001 From: Spooghetti420 Date: Thu, 27 Jan 2022 11:35:31 +0000 Subject: [PATCH 719/996] Add No Long Notes mod --- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 3 +- .../Mods/ManiaModNoLongNotes.cs | 80 +++++++++++++++++++ 2 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Mania/Mods/ManiaModNoLongNotes.cs diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 6fc7dc018b..3471f4ca66 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -243,7 +243,8 @@ namespace osu.Game.Rulesets.Mania new ManiaModDifficultyAdjust(), new ManiaModClassic(), new ManiaModInvert(), - new ManiaModConstantSpeed() + new ManiaModConstantSpeed(), + new ManiaModNoLongNotes() }; case ModType.Automation: diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModNoLongNotes.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModNoLongNotes.cs new file mode 100644 index 0000000000..7295a9eb6e --- /dev/null +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModNoLongNotes.cs @@ -0,0 +1,80 @@ +// 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 osu.Framework.Extensions.IEnumerableExtensions; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mods; +using osu.Framework.Graphics.Sprites; +using System; +using System.Collections.Generic; +using osu.Game.Audio; +using osu.Game.Rulesets.Mania.Beatmaps; +using osu.Framework.Utils; +using osu.Game.Overlays.Settings; +using osu.Framework.Bindables; +using osu.Game.Configuration; +using osu.Game.Graphics; + + + +namespace osu.Game.Rulesets.Mania.Mods +{ + public class ManiaModNoLongNotes : Mod, IApplicableAfterBeatmapConversion + { + + public override string Name => "No Long Notes"; + public override string Acronym => "NL"; + public override double ScoreMultiplier => 1; + public override string Description => @"Turns all held notes into tap notes. No coordination required."; + public override IconUsage? Icon => FontAwesome.Solid.DotCircle; + public override ModType Type => ModType.Conversion; + + [SettingSource("Add end notes", "Also add a note at the end of a held note")] + public BindableBool AddEndNotes { get; } = new BindableBool + { + Default = true, + Value = true + }; + + [SettingSource("Length threshold", "Only add an end note for held notes longer than this threshold (in milliseconds)")] + public BindableNumber Threshold { get; } = new BindableDouble + { + MinValue = 1.0, + MaxValue = 1990.0, + Default = 200.0, + Value = 200.0, + Precision = 1.0, + }; + public void ApplyToBeatmap(IBeatmap beatmap) + { + var maniaBeatmap = (ManiaBeatmap)beatmap; + + var newObjects = new List(); + beatmap.HitObjects.OfType().ForEach(h => + { + // Add a note for the beginning of the hold note + newObjects.Add(new Note + { + Column = h.Column, + StartTime = h.StartTime, + Samples = h.Samples + }); + + // Don't add an end note if the duration is below the threshold, or end notes are disabled + if (AddEndNotes.Value && h.Duration > Threshold.Value) + { + newObjects.Add(new Note + { + Column = h.Column, + StartTime = h.EndTime, + Samples = h.Samples + }); + } + }); + + maniaBeatmap.HitObjects = maniaBeatmap.HitObjects.OfType().Concat(newObjects).OrderBy(h => h.StartTime).ToList(); + } + } +} From 33b7bdcf8260cf0d9090d9edaa186729682b8b3b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 21:49:33 +0900 Subject: [PATCH 720/996] Update pointless `CompareTo` implementation --- osu.Game/Rulesets/EFRulesetInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/EFRulesetInfo.cs b/osu.Game/Rulesets/EFRulesetInfo.cs index b4a64fe932..ba56adac49 100644 --- a/osu.Game/Rulesets/EFRulesetInfo.cs +++ b/osu.Game/Rulesets/EFRulesetInfo.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets public bool Equals(EFRulesetInfo other) => other != null && ID == other.ID && Available == other.Available && Name == other.Name && InstantiationInfo == other.InstantiationInfo; - public int CompareTo(RulesetInfo other) => ID?.CompareTo(other.ID) ?? -1; + public int CompareTo(RulesetInfo other) => OnlineID.CompareTo(other.OnlineID); public override bool Equals(object obj) => obj is EFRulesetInfo rulesetInfo && Equals(rulesetInfo); From 2d1a8a9d492258f760ed9d1475135862d76f483f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 21:58:04 +0900 Subject: [PATCH 721/996] Use a more correct `CompareTo` implementation for ruleset ordering --- osu.Game/Rulesets/RulesetInfo.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/RulesetInfo.cs b/osu.Game/Rulesets/RulesetInfo.cs index dd47a19f32..0a0941d1ff 100644 --- a/osu.Game/Rulesets/RulesetInfo.cs +++ b/osu.Game/Rulesets/RulesetInfo.cs @@ -51,14 +51,14 @@ namespace osu.Game.Rulesets public int CompareTo(RulesetInfo other) { + if (OnlineID >= 0 && other.OnlineID >= 0) + return OnlineID.CompareTo(other.OnlineID); + // Official rulesets are always given precedence for the time being. if (OnlineID >= 0) - { - if (other.OnlineID >= 0) - return OnlineID.CompareTo(other.OnlineID); - return -1; - } + if (other.OnlineID >= 0) + return 1; return string.Compare(ShortName, other.ShortName, StringComparison.Ordinal); } From fae4f8bd8e17f7a5df716b8a19ca731a229c714f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 21:59:44 +0900 Subject: [PATCH 722/996] Move nulling of previous `songSelect` to `SetUpSteps` instead --- .../Visual/SongSelect/TestScenePlaySongSelect.cs | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index ca0f2b8012..0c452f857f 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -69,22 +69,13 @@ namespace osu.Game.Tests.Visual.SongSelect { Ruleset.Value = new OsuRuleset().RulesetInfo; Beatmap.SetDefault(); + + songSelect = null; }); AddStep("delete all beatmaps", () => manager?.Delete()); } - public override void TearDownSteps() - { - base.TearDownSteps(); - - AddStep("remove song select", () => - { - songSelect?.Expire(); - songSelect = null; - }); - } - [Test] public void TestSingleFilterOnEnter() { From 81461be49fede927581065f46f03331d69d93e8d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 00:14:18 +0900 Subject: [PATCH 723/996] Skip beatmap imports where ruleset is not present in realm Closes #16651. When a ruleset is not available, the `Find` call would return null. When a null is passed to the constructor, `BeatmapInfo` would create an "osu" ruleset, which tries to get stored to realm and fails on duplicate primary key. Probably need to add better safeties against this (or change that constructor...) but this will fix the migration process. Probably not serious enough to pull the build. This only affects rulesets like karaoke which have custom beatmaps. --- osu.Game/Database/EFToRealmMigrator.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index 639bf10e19..9fb0130619 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -232,6 +232,7 @@ namespace osu.Game.Database var transaction = r.BeginWrite(); int written = 0; + int missing = 0; try { @@ -261,6 +262,12 @@ namespace osu.Game.Database var ruleset = r.Find(beatmap.RulesetInfo.ShortName); var metadata = getBestMetadata(beatmap.Metadata, beatmapSet.Metadata); + if (ruleset == null) + { + log($"Skipping {++missing} beatmaps with missing ruleset"); + continue; + } + var realmBeatmap = new BeatmapInfo(ruleset, new BeatmapDifficulty(beatmap.BaseDifficulty), metadata) { DifficultyName = beatmap.DifficultyName, From 942ea896f1dfab080c624e57b8c90b30844e56ba Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 01:20:32 +0900 Subject: [PATCH 724/996] Skip scores missing beatmaps during realm migration --- osu.Game/Database/EFToRealmMigrator.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index 9fb0130619..a53704df9d 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -374,12 +374,12 @@ namespace osu.Game.Database log($"Migrated {written}/{count} scores..."); } - var beatmap = r.All().First(b => b.Hash == score.BeatmapInfo.Hash); + var beatmap = r.All().FirstOrDefault(b => b.Hash == score.BeatmapInfo.Hash); var ruleset = r.Find(score.Ruleset.ShortName); - if (ruleset == null) + if (beatmap == null || ruleset == null) { - log($"Skipping {++missing} scores with missing ruleset"); + log($"Skipping {++missing} scores with missing ruleset or beatmap"); continue; } From c0b2f8bd01b62c2bbd0366c8d97eb883e95b9349 Mon Sep 17 00:00:00 2001 From: Spooghetti420 Date: Thu, 27 Jan 2022 16:21:38 +0000 Subject: [PATCH 725/996] Fix newline style in mod --- osu.Game.Rulesets.Mania/Mods/ManiaModNoLongNotes.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModNoLongNotes.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModNoLongNotes.cs index 7295a9eb6e..56522a4587 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModNoLongNotes.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModNoLongNotes.cs @@ -25,10 +25,15 @@ namespace osu.Game.Rulesets.Mania.Mods { public override string Name => "No Long Notes"; + public override string Acronym => "NL"; + public override double ScoreMultiplier => 1; + public override string Description => @"Turns all held notes into tap notes. No coordination required."; + public override IconUsage? Icon => FontAwesome.Solid.DotCircle; + public override ModType Type => ModType.Conversion; [SettingSource("Add end notes", "Also add a note at the end of a held note")] From 400633bd99a8cb86f647437d18f25ee4c702232b Mon Sep 17 00:00:00 2001 From: Spooghetti420 Date: Thu, 27 Jan 2022 16:23:09 +0000 Subject: [PATCH 726/996] Add another newline --- osu.Game.Rulesets.Mania/Mods/ManiaModNoLongNotes.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModNoLongNotes.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModNoLongNotes.cs index 56522a4587..d10003b783 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModNoLongNotes.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModNoLongNotes.cs @@ -52,6 +52,7 @@ namespace osu.Game.Rulesets.Mania.Mods Value = 200.0, Precision = 1.0, }; + public void ApplyToBeatmap(IBeatmap beatmap) { var maniaBeatmap = (ManiaBeatmap)beatmap; From 9deeaee40481e7702c4b0e143ec177e755928dd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 27 Jan 2022 16:57:38 +0100 Subject: [PATCH 727/996] Fix tournament client not loading Caused by a `LoadComponentsAsync()` call being fired from a worker thread, which will throw exceptions since the recent addition of safety checks around that method. --- osu.Game.Tournament/TournamentGame.cs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tournament/TournamentGame.cs b/osu.Game.Tournament/TournamentGame.cs index 5d613894d4..7967f54b49 100644 --- a/osu.Game.Tournament/TournamentGame.cs +++ b/osu.Game.Tournament/TournamentGame.cs @@ -61,18 +61,15 @@ namespace osu.Game.Tournament loadingSpinner.Show(); - BracketLoadTask.ContinueWith(t => + BracketLoadTask.ContinueWith(t => Schedule(() => { if (t.IsFaulted) { - Schedule(() => - { - loadingSpinner.Hide(); - loadingSpinner.Expire(); + loadingSpinner.Hide(); + loadingSpinner.Expire(); - Logger.Error(t.Exception, "Couldn't load bracket with error"); - Add(new WarningBox($"Your {BRACKET_FILENAME} file could not be parsed. Please check runtime.log for more details.")); - }); + Logger.Error(t.Exception, "Couldn't load bracket with error"); + Add(new WarningBox($"Your {BRACKET_FILENAME} file could not be parsed. Please check runtime.log for more details.")); return; } @@ -143,7 +140,7 @@ namespace osu.Game.Tournament windowMode.Value = WindowMode.Windowed; }), true); }); - }); + })); } } } From 7dc3940dee206b6ebec790078c4eefa6ee78b464 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 27 Jan 2022 21:41:09 +0100 Subject: [PATCH 728/996] Add test coverage for preserving legacy beatmap info defaults --- .../Formats/LegacyBeatmapDecoderTest.cs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 1edd21b5a9..3a6051ed43 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -794,5 +794,32 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.That(path.Distance, Is.EqualTo(1)); } } + + [Test] + public void TestLegacyDefaultsPreserved() + { + var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + + using (var memoryStream = new MemoryStream()) + using (var stream = new LineBufferedReader(memoryStream)) + { + var decoded = decoder.Decode(stream); + + Assert.Multiple(() => + { + Assert.That(decoded.BeatmapInfo.AudioLeadIn, Is.EqualTo(0)); + Assert.That(decoded.BeatmapInfo.StackLeniency, Is.EqualTo(0.7f)); + Assert.That(decoded.BeatmapInfo.SpecialStyle, Is.False); + Assert.That(decoded.BeatmapInfo.LetterboxInBreaks, Is.False); + Assert.That(decoded.BeatmapInfo.WidescreenStoryboard, Is.False); + Assert.That(decoded.BeatmapInfo.EpilepsyWarning, Is.False); + Assert.That(decoded.BeatmapInfo.SamplesMatchPlaybackRate, Is.False); + Assert.That(decoded.BeatmapInfo.Countdown, Is.EqualTo(CountdownType.Normal)); + Assert.That(decoded.BeatmapInfo.CountdownOffset, Is.EqualTo(0)); + Assert.That(decoded.BeatmapInfo.Metadata.PreviewTime, Is.EqualTo(-1)); + Assert.That(decoded.BeatmapInfo.Ruleset.OnlineID, Is.EqualTo(0)); + }); + } + } } } From 1b8136e3e0fa023a613dd0b66602b9b943db83d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 27 Jan 2022 21:41:30 +0100 Subject: [PATCH 729/996] Change some `BeatmapInfo` defaults in a backwards compatible manner --- osu.Game/Beatmaps/BeatmapInfo.cs | 4 ++-- osu.Game/Beatmaps/BeatmapManager.cs | 9 +-------- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 15 +++++++++++++++ 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 3e6e33f1d0..e4bfd768b7 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -99,11 +99,11 @@ namespace osu.Game.Beatmaps public bool LetterboxInBreaks { get; set; } - public bool WidescreenStoryboard { get; set; } + public bool WidescreenStoryboard { get; set; } = true; public bool EpilepsyWarning { get; set; } - public bool SamplesMatchPlaybackRate { get; set; } + public bool SamplesMatchPlaybackRate { get; set; } = true; public double DistanceSpacing { get; set; } diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 414b7cd12b..e4fdb3d471 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -90,14 +90,7 @@ namespace osu.Game.Beatmaps { Beatmaps = { - new BeatmapInfo - { - Difficulty = new BeatmapDifficulty(), - Ruleset = ruleset, - Metadata = metadata, - WidescreenStoryboard = true, - SamplesMatchPlaybackRate = true, - } + new BeatmapInfo(ruleset, new BeatmapDifficulty(), metadata) } }; diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 35d1cefeca..01ba0fcc9f 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -56,6 +56,8 @@ namespace osu.Game.Beatmaps.Formats this.beatmap = beatmap; this.beatmap.BeatmapInfo.BeatmapVersion = FormatVersion; + applyLegacyDefaults(this.beatmap.BeatmapInfo); + base.ParseStreamInto(stream, beatmap); flushPendingPoints(); @@ -70,6 +72,19 @@ namespace osu.Game.Beatmaps.Formats hitObject.ApplyDefaults(this.beatmap.ControlPointInfo, this.beatmap.Difficulty); } + /// + /// Some `BeatmapInfo` members have default values that differ from the default values used by stable. + /// In addition, legacy beatmaps will sometimes not contain some configuration keys, in which case + /// the legacy default values should be used. + /// This method's intention is to restore those legacy defaults. + /// See also: https://osu.ppy.sh/wiki/en/Client/File_formats/Osu_%28file_format%29 + /// + private void applyLegacyDefaults(BeatmapInfo beatmapInfo) + { + beatmapInfo.WidescreenStoryboard = false; + beatmapInfo.SamplesMatchPlaybackRate = false; + } + protected override bool ShouldSkipLine(string line) => base.ShouldSkipLine(line) || line.StartsWith(' ') || line.StartsWith('_'); protected override void ParseLine(Beatmap beatmap, Section section, string line) From 6674567af1d678f33eb7c54920e3f11cba7301a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 27 Jan 2022 21:51:51 +0100 Subject: [PATCH 730/996] Use -1 as the default preview time globally in metadata --- osu.Game/Beatmaps/BeatmapMetadata.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs index cb38373bd3..1514d3af7a 100644 --- a/osu.Game/Beatmaps/BeatmapMetadata.cs +++ b/osu.Game/Beatmaps/BeatmapMetadata.cs @@ -39,7 +39,7 @@ namespace osu.Game.Beatmaps /// The time in milliseconds to begin playing the track for preview purposes. /// If -1, the track should begin playing at 40% of its length. /// - public int PreviewTime { get; set; } + public int PreviewTime { get; set; } = -1; public string AudioFile { get; set; } = string.Empty; public string BackgroundFile { get; set; } = string.Empty; From 3e068e564d79d6b446b93a69904268abd19ba194 Mon Sep 17 00:00:00 2001 From: Spooghetti420 Date: Thu, 27 Jan 2022 20:56:51 +0000 Subject: [PATCH 731/996] Update mod per discussion + create test --- .../Mods/TestSceneManiaModNoHolds.cs | 52 +++++++++++++ osu.Game.Rulesets.Mania/ManiaRuleset.cs | 2 +- .../Mods/ManiaModNoHolds.cs | 75 +++++++++++++++++++ 3 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModNoHolds.cs create mode 100644 osu.Game.Rulesets.Mania/Mods/ManiaModNoHolds.cs diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModNoHolds.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModNoHolds.cs new file mode 100644 index 0000000000..411f891f43 --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModNoHolds.cs @@ -0,0 +1,52 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mania.Mods; +using osu.Game.Tests.Visual; +using osu.Game.Rulesets.Mania.Tests; +using System.Collections.Generic; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Objects; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Mania.Beatmaps; + +namespace osu.Game.Rulesets.Mania.Tests.Mods +{ + public class TestSceneManiaModNoHolds : ModTestScene + { + protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset(); + + [Test] + public void TestMapHasNoHeldNotes() + { + var testBeatmap = createBeatmap(); + Assert.That(!testBeatmap.HitObjects.OfType().Any()); + } + + + private static IBeatmap createBeatmap() + { + var beatmap = createRawBeatmap(); + var noHoldsMod = new ManiaModNoHolds(); + + foreach (var hitObject in beatmap.HitObjects) + hitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + + noHoldsMod.ApplyToBeatmap(beatmap); + + return beatmap; + } + private static IBeatmap createRawBeatmap() + { + var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 }); + beatmap.HitObjects.Add(new Note { StartTime = 1000 }); + beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, EndTime = 3000 }); + return beatmap; + } + } +} diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 3471f4ca66..55ca1a465e 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -244,7 +244,7 @@ namespace osu.Game.Rulesets.Mania new ManiaModClassic(), new ManiaModInvert(), new ManiaModConstantSpeed(), - new ManiaModNoLongNotes() + new ManiaModNoHolds() }; case ModType.Automation: diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModNoHolds.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModNoHolds.cs new file mode 100644 index 0000000000..0d5ca44898 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModNoHolds.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.Linq; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mods; +using osu.Framework.Graphics.Sprites; +using System; +using System.Collections.Generic; +using osu.Game.Audio; +using osu.Game.Rulesets.Mania.Beatmaps; +using osu.Framework.Utils; +using osu.Game.Overlays.Settings; +using osu.Framework.Bindables; +using osu.Game.Configuration; +using osu.Game.Graphics; + +namespace osu.Game.Rulesets.Mania.Mods +{ + public class ManiaModNoHolds : Mod, IApplicableAfterBeatmapConversion + { + public override string Name => "No Holds"; + + public override string Acronym => "NH"; + + public override double ScoreMultiplier => 1; + + public override string Description => @"Turns all hold notes into normal notes. No coordination required."; + + public override IconUsage? Icon => FontAwesome.Solid.DotCircle; + + public override ModType Type => ModType.Conversion; + + [SettingSource("Add end notes", "Also add a note at the end of a hold note")] + public BindableBool AddEndNotes { get; } = new BindableBool + { + Default = true, + Value = true + }; + + public void ApplyToBeatmap(IBeatmap beatmap) + { + var maniaBeatmap = (ManiaBeatmap)beatmap; + + var newObjects = new List(); + foreach (var h in beatmap.HitObjects.OfType()) + { + // Add a note for the beginning of the hold note + newObjects.Add(new Note + { + Column = h.Column, + StartTime = h.StartTime, + Samples = h.GetNodeSamples(0) + }); + + // Don't add an end note if the duration is shorter than some threshold, or end notes are disabled + if (AddEndNotes.Value && h.Duration > 200) + { + newObjects.Add(new Note + { + Column = h.Column, + StartTime = h.EndTime, + Samples = h.GetNodeSamples((h.NodeSamples?.Count - 1) ?? 1) + }); + } + } + + maniaBeatmap.HitObjects = maniaBeatmap.HitObjects.OfType().Concat(newObjects).OrderBy(h => h.StartTime).ToList(); + } + + } + +} From 146c54a2c14608b95363647a0e965aeeb47aa03e Mon Sep 17 00:00:00 2001 From: Spooghetti420 Date: Thu, 27 Jan 2022 21:02:59 +0000 Subject: [PATCH 732/996] Fix code formatting --- .../Mods/TestSceneManiaModNoHolds.cs | 1 - osu.Game.Rulesets.Mania/Mods/ManiaModNoHolds.cs | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModNoHolds.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModNoHolds.cs index 411f891f43..f22b724c99 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModNoHolds.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModNoHolds.cs @@ -28,7 +28,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods Assert.That(!testBeatmap.HitObjects.OfType().Any()); } - private static IBeatmap createBeatmap() { var beatmap = createRawBeatmap(); diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModNoHolds.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModNoHolds.cs index 0d5ca44898..a18dbad4a1 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModNoHolds.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModNoHolds.cs @@ -69,7 +69,5 @@ namespace osu.Game.Rulesets.Mania.Mods maniaBeatmap.HitObjects = maniaBeatmap.HitObjects.OfType().Concat(newObjects).OrderBy(h => h.StartTime).ToList(); } - } - -} +} \ No newline at end of file From f59828e2d9dc745e352ecbbf01fe811389e2ed47 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 28 Jan 2022 13:27:12 +0900 Subject: [PATCH 733/996] Add audio feedback to song select 'random' --- osu.Game/Screens/Select/BeatmapCarousel.cs | 29 +++++++++++++++++++++- osu.Game/Screens/Select/SongSelect.cs | 21 +++++++++++++--- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index c27915c383..ef19eb187a 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -6,6 +6,8 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Caching; using osu.Framework.Graphics; @@ -151,6 +153,10 @@ namespace osu.Game.Screens.Select private readonly DrawablePool setPool = new DrawablePool(100); + private Sample spinSample; + + private int visibleSetsCount; + public BeatmapCarousel() { root = new CarouselRoot(this); @@ -169,8 +175,10 @@ namespace osu.Game.Screens.Select } [BackgroundDependencyLoader] - private void load(OsuConfigManager config) + private void load(OsuConfigManager config, AudioManager audio) { + spinSample = audio.Samples.Get("SongSelect/random-spin"); + config.BindWith(OsuSetting.RandomSelectAlgorithm, RandomAlgorithm); config.BindWith(OsuSetting.SongSelectRightMouseScroll, RightClickScrollingEnabled); @@ -419,6 +427,9 @@ namespace osu.Game.Screens.Select return false; var visibleSets = beatmapSets.Where(s => !s.Filtered.Value).ToList(); + + visibleSetsCount = visibleSets.Count; + if (!visibleSets.Any()) return false; @@ -450,6 +461,9 @@ namespace osu.Game.Screens.Select else set = visibleSets.ElementAt(RNG.Next(visibleSets.Count)); + if (selectedBeatmapSet != null) + playSpinSample(distanceBetween(set, selectedBeatmapSet)); + select(set); return true; } @@ -464,12 +478,25 @@ namespace osu.Game.Screens.Select { if (RandomAlgorithm.Value == RandomSelectAlgorithm.RandomPermutation) previouslyVisitedRandomSets.Remove(selectedBeatmapSet); + + if (selectedBeatmapSet != null) + playSpinSample(distanceBetween(beatmap, selectedBeatmapSet)); + select(beatmap); break; } } } + private double distanceBetween(CarouselItem item1, CarouselItem item2) => Math.Ceiling(Math.Abs(item1.CarouselYPosition - item2.CarouselYPosition) / DrawableCarouselItem.MAX_HEIGHT); + + private void playSpinSample(double distance) + { + var chan = spinSample.GetChannel(); + chan.Frequency.Value = 1f + Math.Min(1f, distance / visibleSetsCount); + chan.Play(); + } + private void select(CarouselItem item) { if (!AllowSelection) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 10150fcd9f..6fbadfe6cf 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -100,6 +100,7 @@ namespace osu.Game.Screens.Select private Sample sampleChangeDifficulty; private Sample sampleChangeBeatmap; + private Sample sampleRandomBeatmap; private Container carouselContainer; @@ -109,6 +110,8 @@ namespace osu.Game.Screens.Select private double audioFeedbackLastPlaybackTime; + private bool randomClicked; + [Resolved] private MusicController music { get; set; } @@ -288,6 +291,7 @@ namespace osu.Game.Screens.Select sampleChangeDifficulty = audio.Samples.Get(@"SongSelect/select-difficulty"); sampleChangeBeatmap = audio.Samples.Get(@"SongSelect/select-expand"); + sampleRandomBeatmap = audio.Samples.Get(@"SongSelect/select-random"); SampleConfirm = audio.Samples.Get(@"SongSelect/confirm-selection"); if (dialogOverlay != null) @@ -315,8 +319,16 @@ namespace osu.Game.Screens.Select (new FooterButtonMods { Current = Mods }, ModSelect), (new FooterButtonRandom { - NextRandom = () => Carousel.SelectNextRandom(), - PreviousRandom = Carousel.SelectPreviousRandom + NextRandom = () => + { + randomClicked = true; + Carousel.SelectNextRandom(); + }, + PreviousRandom = () => + { + randomClicked = true; + Carousel.SelectPreviousRandom(); + } }, null), (new FooterButtonOptions(), BeatmapOptions) }; @@ -486,7 +498,9 @@ namespace osu.Game.Screens.Select { if (beatmap != null && beatmapInfoPrevious != null && Time.Current - audioFeedbackLastPlaybackTime >= 50) { - if (beatmap.BeatmapSet?.ID == beatmapInfoPrevious.BeatmapSet?.ID) + if (randomClicked) + sampleRandomBeatmap.Play(); + else if (beatmap.BeatmapSet?.ID == beatmapInfoPrevious.BeatmapSet?.ID) sampleChangeDifficulty.Play(); else sampleChangeBeatmap.Play(); @@ -494,6 +508,7 @@ namespace osu.Game.Screens.Select audioFeedbackLastPlaybackTime = Time.Current; } + randomClicked = false; beatmapInfoPrevious = beatmap; } From c44af4853d6aabb82006d7944e146d82c06401fd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 13:43:53 +0900 Subject: [PATCH 734/996] Add thread safety to `PollingComponent.Poll` implementations --- .../Components/ListingPollingComponent.cs | 14 ++++++++------ .../Components/SelectionPollingComponent.cs | 15 +++++++++------ 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.cs b/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.cs index fcf7767958..666d425f62 100644 --- a/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.cs +++ b/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.cs @@ -33,7 +33,7 @@ namespace osu.Game.Screens.OnlinePlay.Components }); } - private GetRoomsRequest pollReq; + private GetRoomsRequest lastPollRequest; protected override Task Poll() { @@ -45,10 +45,11 @@ namespace osu.Game.Screens.OnlinePlay.Components var tcs = new TaskCompletionSource(); - pollReq?.Cancel(); - pollReq = new GetRoomsRequest(Filter.Value.Status, Filter.Value.Category); + lastPollRequest?.Cancel(); - pollReq.Success += result => + var req = new GetRoomsRequest(Filter.Value.Status, Filter.Value.Category); + + req.Success += result => { foreach (var existing in RoomManager.Rooms.ToArray()) { @@ -66,10 +67,11 @@ namespace osu.Game.Screens.OnlinePlay.Components tcs.SetResult(true); }; - pollReq.Failure += _ => tcs.SetResult(false); + req.Failure += _ => tcs.SetResult(false); - API.Queue(pollReq); + API.Queue(req); + lastPollRequest = req; return tcs.Task; } } diff --git a/osu.Game/Screens/OnlinePlay/Components/SelectionPollingComponent.cs b/osu.Game/Screens/OnlinePlay/Components/SelectionPollingComponent.cs index 22842fbb9e..e05bdf8c8e 100644 --- a/osu.Game/Screens/OnlinePlay/Components/SelectionPollingComponent.cs +++ b/osu.Game/Screens/OnlinePlay/Components/SelectionPollingComponent.cs @@ -18,7 +18,7 @@ namespace osu.Game.Screens.OnlinePlay.Components this.room = room; } - private GetRoomRequest pollReq; + private GetRoomRequest lastPollRequest; protected override Task Poll() { @@ -30,19 +30,22 @@ namespace osu.Game.Screens.OnlinePlay.Components var tcs = new TaskCompletionSource(); - pollReq?.Cancel(); - pollReq = new GetRoomRequest(room.RoomID.Value.Value); + lastPollRequest?.Cancel(); - pollReq.Success += result => + var req = new GetRoomRequest(room.RoomID.Value.Value); + + req.Success += result => { result.RemoveExpiredPlaylistItems(); RoomManager.AddOrUpdateRoom(result); tcs.SetResult(true); }; - pollReq.Failure += _ => tcs.SetResult(false); + req.Failure += _ => tcs.SetResult(false); - API.Queue(pollReq); + API.Queue(req); + + lastPollRequest = req; return tcs.Task; } From c953a5d5034416bb32ee2caada7c0a74f0b4551a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 13:44:09 +0900 Subject: [PATCH 735/996] Ensure `PollingComponent.Poll` is always called from the update thread Not strictly required since all `Poll` implementations are now threadsafe, but extra safety is never a bad thing? --- osu.Game/Online/PollingComponent.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/PollingComponent.cs b/osu.Game/Online/PollingComponent.cs index 243be8da44..5eddb3b49d 100644 --- a/osu.Game/Online/PollingComponent.cs +++ b/osu.Game/Online/PollingComponent.cs @@ -2,8 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics; using System.Threading.Tasks; using osu.Framework.Bindables; +using osu.Framework.Development; using osu.Framework.Graphics.Containers; using osu.Framework.Threading; @@ -66,6 +68,8 @@ namespace osu.Game.Online private void doPoll() { + Debug.Assert(ThreadSafety.IsUpdateThread); + scheduledPoll = null; pollingActive = true; Poll().ContinueWith(_ => pollComplete()); @@ -96,13 +100,13 @@ namespace osu.Game.Online if (!lastTimePolled.HasValue) { - doPoll(); + Scheduler.AddOnce(doPoll); return; } if (Time.Current - lastTimePolled.Value > TimeBetweenPolls.Value) { - doPoll(); + Scheduler.AddOnce(doPoll); return; } From 91be77ad3d61f844c997681cfdf5b2643115bd43 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 14:01:10 +0900 Subject: [PATCH 736/996] Fix null ref in `ComposeScreen` when ruleset doesn't provide a composer --- osu.Game/Screens/Edit/Compose/ComposeScreen.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs index 2bdf59b21c..2cde962b12 100644 --- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs +++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs @@ -82,6 +82,11 @@ namespace osu.Game.Screens.Edit.Compose protected override void LoadComplete() { base.LoadComplete(); + + // May be null in the case of a ruleset that doesn't have editor support, see CreateMainContent(). + if (composer == null) + return; + EditorBeatmap.SelectedHitObjects.BindCollectionChanged((_, __) => updateClipboardActionAvailability()); clipboard.BindValueChanged(_ => updateClipboardActionAvailability()); composer.OnLoadComplete += _ => updateClipboardActionAvailability(); From b3856c900541e1af68aa8e4a5c55b9a03f62356f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 14:01:31 +0900 Subject: [PATCH 737/996] Fix editor crashing on custom rulesets due to `ChangeHandler` not being supported As per https://github.com/ppy/osu/discussions/16668, even without proper saving support some ruleset developers do want to work on the editor. This brings things back into a workable state. --- osu.Game/Screens/Edit/Editor.cs | 46 +++++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 4812da98f4..2fead84deb 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -28,6 +28,8 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Online.API; using osu.Game.Overlays; +using osu.Game.Overlays.Notifications; +using osu.Game.Rulesets; using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit.Components; using osu.Game.Screens.Edit.Components.Menus; @@ -61,7 +63,16 @@ namespace osu.Game.Screens.Edit public override bool? AllowTrackAdjustments => false; - protected bool HasUnsavedChanges => lastSavedHash != changeHandler.CurrentStateHash; + protected bool HasUnsavedChanges + { + get + { + if (!canSave) + return false; + + return lastSavedHash != changeHandler?.CurrentStateHash; + } + } [Resolved] private BeatmapManager beatmapManager { get; set; } @@ -72,10 +83,15 @@ namespace osu.Game.Screens.Edit [Resolved(canBeNull: true)] private DialogOverlay dialogOverlay { get; set; } + [Resolved(canBeNull: true)] + private NotificationOverlay notifications { get; set; } + public IBindable SamplePlaybackDisabled => samplePlaybackDisabled; private readonly Bindable samplePlaybackDisabled = new Bindable(); + private bool canSave; + private bool exitConfirmed; private string lastSavedHash; @@ -92,6 +108,8 @@ namespace osu.Game.Screens.Edit private IBeatmap playableBeatmap; private EditorBeatmap editorBeatmap; + + [CanBeNull] // Should be non-null once it can support custom rulesets. private EditorChangeHandler changeHandler; private EditorMenuBar menuBar; @@ -172,8 +190,14 @@ namespace osu.Game.Screens.Edit AddInternal(editorBeatmap = new EditorBeatmap(playableBeatmap, loadableBeatmap.GetSkin(), loadableBeatmap.BeatmapInfo)); dependencies.CacheAs(editorBeatmap); - changeHandler = new EditorChangeHandler(editorBeatmap); - dependencies.CacheAs(changeHandler); + + canSave = editorBeatmap.BeatmapInfo.Ruleset.CreateInstance() is ILegacyRuleset; + + if (canSave) + { + changeHandler = new EditorChangeHandler(editorBeatmap); + dependencies.CacheAs(changeHandler); + } beatDivisor.Value = editorBeatmap.BeatmapInfo.BeatDivisor; beatDivisor.BindValueChanged(divisor => editorBeatmap.BeatmapInfo.BeatDivisor = divisor.NewValue); @@ -311,8 +335,8 @@ namespace osu.Game.Screens.Edit } }); - changeHandler.CanUndo.BindValueChanged(v => undoMenuItem.Action.Disabled = !v.NewValue, true); - changeHandler.CanRedo.BindValueChanged(v => redoMenuItem.Action.Disabled = !v.NewValue, true); + changeHandler?.CanUndo.BindValueChanged(v => undoMenuItem.Action.Disabled = !v.NewValue, true); + changeHandler?.CanRedo.BindValueChanged(v => redoMenuItem.Action.Disabled = !v.NewValue, true); menuBar.Mode.ValueChanged += onModeChanged; } @@ -353,6 +377,12 @@ namespace osu.Game.Screens.Edit protected void Save() { + if (!canSave) + { + notifications?.Post(new SimpleErrorNotification { Text = "Saving is not supported for this ruleset yet, sorry!" }); + return; + } + // no longer new after first user-triggered save. isNewBeatmap = false; @@ -648,9 +678,9 @@ namespace osu.Game.Screens.Edit #endregion - protected void Undo() => changeHandler.RestoreState(-1); + protected void Undo() => changeHandler?.RestoreState(-1); - protected void Redo() => changeHandler.RestoreState(1); + protected void Redo() => changeHandler?.RestoreState(1); private void resetTrack(bool seekToStart = false) { @@ -761,7 +791,7 @@ namespace osu.Game.Screens.Edit private void updateLastSavedHash() { - lastSavedHash = changeHandler.CurrentStateHash; + lastSavedHash = changeHandler?.CurrentStateHash; } private List createFileMenuItems() From b7d8c9bf067f64021a4648ecfd413648258f373f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 14:18:10 +0900 Subject: [PATCH 738/996] Fix a couple of cases of incorrect equality checks in the case both values are null --- .../BeatmapSet/Scores/TopScoreStatisticsSection.cs | 3 +++ .../Screens/Select/Leaderboards/BeatmapLeaderboard.cs | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs index 1f3f73a60a..ec795cf6b2 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs @@ -107,6 +107,9 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { set { + if (score == null && value == null) + return; + if (score?.Equals(value) == true) return; diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 2070e53257..31cbe91f5c 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -38,6 +38,14 @@ namespace osu.Game.Screens.Select.Leaderboards get => beatmapInfo; set { + if (beatmapInfo == null && value == null) + { + // always null scores to ensure a correct initial display. + // see weird `scoresLoadedOnce` logic in base implementation. + Scores = null; + return; + } + if (beatmapInfo?.Equals(value) == true) return; From f32d56e213fa26566b38ca12bb44cf9d8f0f0100 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 14:48:17 +0900 Subject: [PATCH 739/996] Bring `HoldForMenuButton` tests up-to-date in code quality --- .../Gameplay/TestSceneHoldForMenuButton.cs | 37 ++++++++++++------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs index 235842acc9..e0765ab5fb 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs @@ -3,9 +3,10 @@ using System.Linq; using NUnit.Framework; -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; +using osu.Framework.Testing; +using osu.Game.Graphics.Containers; using osu.Game.Screens.Play.HUD; using osuTK; using osuTK.Input; @@ -19,28 +20,35 @@ namespace osu.Game.Tests.Visual.Gameplay protected override double TimePerAction => 100; // required for the early exit test, since hold-to-confirm delay is 200ms - [BackgroundDependencyLoader] - private void load() + private HoldForMenuButton holdForMenuButton; + + [SetUpSteps] + public void SetUpSteps() { - HoldForMenuButton holdForMenuButton; - - Add(holdForMenuButton = new HoldForMenuButton + AddStep("create button", () => { - Origin = Anchor.BottomRight, - Anchor = Anchor.BottomRight, - Action = () => exitAction = true + exitAction = false; + + Child = holdForMenuButton = new HoldForMenuButton + { + Scale = new Vector2(2), + Origin = Anchor.CentreRight, + Anchor = Anchor.CentreRight, + Action = () => exitAction = true + }; }); + } - var text = holdForMenuButton.Children.OfType().First(); - + [Test] + public void TestMovementAndTrigger() + { AddStep("Trigger text fade in", () => InputManager.MoveMouseTo(holdForMenuButton)); - AddUntilStep("Text visible", () => text.IsPresent && !exitAction); + AddUntilStep("Text visible", () => getSpriteText().IsPresent && !exitAction); AddStep("Trigger text fade out", () => InputManager.MoveMouseTo(Vector2.One)); - AddUntilStep("Text is not visible", () => !text.IsPresent && !exitAction); + AddUntilStep("Text is not visible", () => !getSpriteText().IsPresent && !exitAction); AddStep("Trigger exit action", () => { - exitAction = false; InputManager.MoveMouseTo(holdForMenuButton); InputManager.PressButton(MouseButton.Left); }); @@ -50,6 +58,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("Trigger exit action", () => InputManager.PressButton(MouseButton.Left)); AddUntilStep($"{nameof(holdForMenuButton.Action)} was triggered", () => exitAction); + AddStep("Release", () => InputManager.ReleaseButton(MouseButton.Left)); } } } From 28c8e07e3f223aaaa15fe840fc8ee375c31dc48d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 14:48:34 +0900 Subject: [PATCH 740/996] Ensure hold for menu button fades out if the cursor is never moved Closes https://github.com/ppy/osu/discussions/16669. --- .../Visual/Gameplay/TestSceneHoldForMenuButton.cs | 10 ++++++++++ osu.Game/Screens/Play/HUD/HoldForMenuButton.cs | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs index e0765ab5fb..feec06f372 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs @@ -60,5 +60,15 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep($"{nameof(holdForMenuButton.Action)} was triggered", () => exitAction); AddStep("Release", () => InputManager.ReleaseButton(MouseButton.Left)); } + + [Test] + public void TestFadeOnNoInput() + { + AddStep("move mouse away", () => InputManager.MoveMouseTo(Vector2.One)); + AddUntilStep("wait for text fade out", () => !getSpriteText().IsPresent); + AddUntilStep("wait for button fade out", () => holdForMenuButton.Alpha < 0.1f); + } + + private SpriteText getSpriteText() => holdForMenuButton.Children.OfType().First(); } } diff --git a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs index 5a7ef786d3..430f001427 100644 --- a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs +++ b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs @@ -80,7 +80,7 @@ namespace osu.Game.Screens.Play.HUD base.LoadComplete(); } - private float positionalAdjust; + private float positionalAdjust = 1; // Start at 1 to handle the case where a user never send positional input. protected override bool OnMouseMove(MouseMoveEvent e) { From 32f9299fe082f7347aebfceef7aa6f8dd017f2f6 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 28 Jan 2022 15:26:29 +0900 Subject: [PATCH 741/996] Remove unused using --- osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs index feec06f372..ddb0872541 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs @@ -6,7 +6,6 @@ using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Testing; -using osu.Game.Graphics.Containers; using osu.Game.Screens.Play.HUD; using osuTK; using osuTK.Input; From cb7ae413feea2e53fd4b5aa3b59167ae0a398c13 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 15:50:13 +0900 Subject: [PATCH 742/996] Ensure test game is always active --- osu.Game/Tests/Visual/OsuGameTestScene.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/Tests/Visual/OsuGameTestScene.cs b/osu.Game/Tests/Visual/OsuGameTestScene.cs index ebbd9bb5dd..dabf49c15e 100644 --- a/osu.Game/Tests/Visual/OsuGameTestScene.cs +++ b/osu.Game/Tests/Visual/OsuGameTestScene.cs @@ -158,6 +158,14 @@ namespace osu.Game.Tests.Visual Dependencies.Get().SetValue(Static.MutedAudioNotificationShownOnce, true); } + + protected override void Update() + { + base.Update(); + + // when running in visual tests and the window loses focus, we generally don't want the game to pause. + ((Bindable)IsActive).Value = true; + } } public class TestLoader : Loader From 778eebc94df6b2ff8a068b014aaee05de8bd594b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 15:50:22 +0900 Subject: [PATCH 743/996] Add test coverage of local score import and deletion --- .../Navigation/TestSceneScreenNavigation.cs | 90 +++++++++++++++---- osu.Game/Tests/Visual/OsuGameTestScene.cs | 3 + 2 files changed, 74 insertions(+), 19 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index 89dca77af4..6d89feef52 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.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; @@ -10,16 +11,19 @@ using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; +using osu.Game.Online.Leaderboards; using osu.Game.Overlays; using osu.Game.Overlays.Mods; using osu.Game.Overlays.Toolbar; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Scoring; using osu.Game.Screens.Menu; using osu.Game.Screens.OnlinePlay.Lounge; using osu.Game.Screens.Play; using osu.Game.Screens.Ranking; using osu.Game.Screens.Select; +using osu.Game.Screens.Select.Leaderboards; using osu.Game.Screens.Select.Options; using osu.Game.Tests.Beatmaps.IO; using osuTK; @@ -96,35 +100,52 @@ namespace osu.Game.Tests.Visual.Navigation [Test] public void TestRetryFromResults() { - Player player = null; - ResultsScreen results = null; + var getOriginalPlayer = playToResults(); - IWorkingBeatmap beatmap() => Game.Beatmap.Value; + AddStep("attempt to retry", () => ((ResultsScreen)Game.ScreenStack.CurrentScreen).ChildrenOfType().First().Action()); + AddUntilStep("wait for player", () => Game.ScreenStack.CurrentScreen != getOriginalPlayer() && Game.ScreenStack.CurrentScreen is Player); + } - Screens.Select.SongSelect songSelect = null; - PushAndConfirm(() => songSelect = new TestPlaySongSelect()); - AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded); + [Test] + public void TestDeleteScoreAfterPlaying() + { + playToResults(); - AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely()); + ScoreInfo score = null; + LeaderboardScore scorePanel = null; - AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); + AddStep("get score", () => score = ((ResultsScreen)Game.ScreenStack.CurrentScreen).Score); - AddStep("set mods", () => Game.SelectedMods.Value = new Mod[] { new OsuModNoFail(), new OsuModDoubleTime { SpeedChange = { Value = 2 } } }); + AddAssert("ensure score is databased", () => Game.Realm.Run(r => r.Find(score.ID)?.DeletePending == false)); - AddStep("press enter", () => InputManager.Key(Key.Enter)); + AddStep("press back button", () => Game.ChildrenOfType().First().Action()); - AddUntilStep("wait for player", () => + AddStep("show local scores", () => Game.ChildrenOfType().First().Current.Value = new BeatmapDetailAreaLeaderboardTabItem(BeatmapLeaderboardScope.Local)); + + AddUntilStep("wait for score displayed", () => (scorePanel = Game.ChildrenOfType().FirstOrDefault(s => s.Score.Equals(score))) != null); + + AddStep("right click panel", () => { - // dismiss any notifications that may appear (ie. muted notification). - clickMouseInCentre(); - return (player = Game.ScreenStack.CurrentScreen as Player) != null; + InputManager.MoveMouseTo(scorePanel); + InputManager.Click(MouseButton.Right); }); - AddUntilStep("wait for track playing", () => beatmap().Track.IsRunning); - AddStep("seek to near end", () => player.ChildrenOfType().First().Seek(beatmap().Beatmap.HitObjects[^1].StartTime - 1000)); - AddUntilStep("wait for pass", () => (results = Game.ScreenStack.CurrentScreen as ResultsScreen) != null && results.IsLoaded); - AddStep("attempt to retry", () => results.ChildrenOfType().First().Action()); - AddUntilStep("wait for player", () => Game.ScreenStack.CurrentScreen != player && Game.ScreenStack.CurrentScreen is Player); + AddStep("click delete", () => + { + var dropdownItem = Game + .ChildrenOfType().First() + .ChildrenOfType().First() + .ChildrenOfType().First(i => i.Item.Text.ToString() == "Delete"); + + InputManager.MoveMouseTo(dropdownItem); + InputManager.Click(MouseButton.Left); + }); + + AddStep("confirm deletion", () => InputManager.Key(Key.Number1)); + + AddAssert("ensure score is pending deletion", () => Game.Realm.Run(r => r.Find(score.ID)?.DeletePending == true)); + + AddUntilStep("wait for score panel removal", () => scorePanel.Parent == null); } [TestCase(true)] @@ -432,6 +453,37 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("test dispose doesn't crash", () => Game.Dispose()); } + private Func playToResults() + { + Player player = null; + + IWorkingBeatmap beatmap() => Game.Beatmap.Value; + + Screens.Select.SongSelect songSelect = null; + PushAndConfirm(() => songSelect = new TestPlaySongSelect()); + AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded); + + AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely()); + + AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); + + AddStep("set mods", () => Game.SelectedMods.Value = new Mod[] { new OsuModNoFail(), new OsuModDoubleTime { SpeedChange = { Value = 2 } } }); + + AddStep("press enter", () => InputManager.Key(Key.Enter)); + + AddUntilStep("wait for player", () => + { + // dismiss any notifications that may appear (ie. muted notification). + clickMouseInCentre(); + return (player = Game.ScreenStack.CurrentScreen as Player) != null; + }); + + AddUntilStep("wait for track playing", () => beatmap().Track.IsRunning); + AddStep("seek to near end", () => player.ChildrenOfType().First().Seek(beatmap().Beatmap.HitObjects[^1].StartTime - 1000)); + AddUntilStep("wait for pass", () => (Game.ScreenStack.CurrentScreen as ResultsScreen)?.IsLoaded == true); + return () => player; + } + private void clickMouseInCentre() { InputManager.MoveMouseTo(Game.ScreenSpaceDrawQuad.Centre); diff --git a/osu.Game/Tests/Visual/OsuGameTestScene.cs b/osu.Game/Tests/Visual/OsuGameTestScene.cs index dabf49c15e..3b8d9a4cd1 100644 --- a/osu.Game/Tests/Visual/OsuGameTestScene.cs +++ b/osu.Game/Tests/Visual/OsuGameTestScene.cs @@ -14,6 +14,7 @@ using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Configuration; +using osu.Game.Database; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Overlays; @@ -111,6 +112,8 @@ namespace osu.Game.Tests.Visual public new ScreenStack ScreenStack => base.ScreenStack; + public RealmAccess Realm => Dependencies.Get(); + public new BackButton BackButton => base.BackButton; public new BeatmapManager BeatmapManager => base.BeatmapManager; From 2453bf5ed0b8b3660c2a109ad2720ba125b1fc2c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 15:54:51 +0900 Subject: [PATCH 744/996] Add test coverage of the same thing but via "clear all scores" button --- .../Navigation/TestSceneScreenNavigation.cs | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index 6d89feef52..3be7cf7c5c 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -106,6 +106,35 @@ namespace osu.Game.Tests.Visual.Navigation AddUntilStep("wait for player", () => Game.ScreenStack.CurrentScreen != getOriginalPlayer() && Game.ScreenStack.CurrentScreen is Player); } + [Test] + public void TestDeleteAllScoresAfterPlaying() + { + playToResults(); + + ScoreInfo score = null; + LeaderboardScore scorePanel = null; + + AddStep("get score", () => score = ((ResultsScreen)Game.ScreenStack.CurrentScreen).Score); + + AddAssert("ensure score is databased", () => Game.Realm.Run(r => r.Find(score.ID)?.DeletePending == false)); + + AddStep("press back button", () => Game.ChildrenOfType().First().Action()); + + AddStep("show local scores", () => Game.ChildrenOfType().First().Current.Value = new BeatmapDetailAreaLeaderboardTabItem(BeatmapLeaderboardScope.Local)); + + AddUntilStep("wait for score displayed", () => (scorePanel = Game.ChildrenOfType().FirstOrDefault(s => s.Score.Equals(score))) != null); + + AddStep("open options", () => InputManager.Key(Key.F3)); + + AddStep("choose clear all scores", () => InputManager.Key(Key.Number4)); + + AddStep("confirm deletion", () => InputManager.Key(Key.Number1)); + + AddAssert("ensure score is pending deletion", () => Game.Realm.Run(r => r.Find(score.ID)?.DeletePending == true)); + + AddUntilStep("wait for score panel removal", () => scorePanel.Parent == null); + } + [Test] public void TestDeleteScoreAfterPlaying() { From 0d3ac4fd9c72db8eb2049d07652375c0de419f0c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 15:08:39 +0900 Subject: [PATCH 745/996] Fix delete local scores crashing the game --- osu.Game/Scoring/ScoreManager.cs | 9 +++++++++ osu.Game/Screens/Select/BeatmapClearScoresDialog.cs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 8f665224ee..6f9cce2d3c 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -266,6 +266,15 @@ namespace osu.Game.Scoring }); } + public void Delete(BeatmapInfo beatmap, bool silent = false) + { + realm.Run(r => + { + var beatmapScores = r.Find(beatmap.ID).Scores.ToList(); + scoreModelManager.Delete(beatmapScores, silent); + }); + } + public void Delete(List items, bool silent = false) { scoreModelManager.Delete(items, silent); diff --git a/osu.Game/Screens/Select/BeatmapClearScoresDialog.cs b/osu.Game/Screens/Select/BeatmapClearScoresDialog.cs index 774d3b4b28..4a16be4a3a 100644 --- a/osu.Game/Screens/Select/BeatmapClearScoresDialog.cs +++ b/osu.Game/Screens/Select/BeatmapClearScoresDialog.cs @@ -28,7 +28,7 @@ namespace osu.Game.Screens.Select Text = @"Yes. Please.", Action = () => { - Task.Run(() => scoreManager.Delete(s => !s.DeletePending && s.BeatmapInfo.ID == beatmapInfo.ID)) + Task.Run(() => scoreManager.Delete(beatmapInfo)) .ContinueWith(_ => onCompletion); } }, From 4d9b61212b22d154c0632c0d50978e90ecc59f9d Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 28 Jan 2022 18:13:51 +0900 Subject: [PATCH 746/996] Add 'cursor tap' audio feedback --- osu.Game/Graphics/Cursor/MenuCursor.cs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Cursor/MenuCursor.cs b/osu.Game/Graphics/Cursor/MenuCursor.cs index 8e272f637f..a89d8dac71 100644 --- a/osu.Game/Graphics/Cursor/MenuCursor.cs +++ b/osu.Game/Graphics/Cursor/MenuCursor.cs @@ -10,6 +10,8 @@ using osu.Framework.Graphics.Sprites; using osu.Game.Configuration; using System; using JetBrains.Annotations; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics.Textures; using osu.Framework.Input.Events; @@ -30,13 +32,17 @@ namespace osu.Game.Graphics.Cursor private DragRotationState dragRotationState; private Vector2 positionMouseDown; + private Sample tapSample; + [BackgroundDependencyLoader(true)] - private void load([NotNull] OsuConfigManager config, [CanBeNull] ScreenshotManager screenshotManager) + private void load([NotNull] OsuConfigManager config, [CanBeNull] ScreenshotManager screenshotManager, AudioManager audio) { cursorRotate = config.GetBindable(OsuSetting.CursorRotation); if (screenshotManager != null) screenshotCursorVisibility.BindTo(screenshotManager.CursorVisibility); + + tapSample = audio.Samples.Get(@"UI/cursor-tap"); } protected override bool OnMouseMove(MouseMoveEvent e) @@ -70,6 +76,18 @@ namespace osu.Game.Graphics.Cursor return base.OnMouseMove(e); } + protected override bool OnClick(ClickEvent e) + { + var channel = tapSample.GetChannel(); + + // scale to [-0.75, 0.75] so that the sample isn't fully panned left or right (sounds weird) + channel.Balance.Value = ((activeCursor.X / DrawWidth) * 2 - 1) * 0.75; + channel.Frequency.Value = 0.99 + RNG.NextDouble(0.02); + channel.Play(); + + return base.OnClick(e); + } + protected override bool OnMouseDown(MouseDownEvent e) { if (State.Value == Visibility.Visible) From 17f0d7897b8a361a7a1fc5e6faa2379efc0a9a8c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 18:35:59 +0900 Subject: [PATCH 747/996] Increase lenience of alpha check in `TestSceneOsuModNoScope` I believe the [test failures](https://github.com/ppy/osu/runs/4977283066?check_suite_focus=true) we're seeing here are due to the implementation of interpolation of the alpha being frame dependent (in a way that doesn't interact well with tests). The reason for never hitting the expected value is that the beatmap ends, causing the cursor to become fully visible again. It's probably already good-enough for most cases, so let's attempt to silence these test failures by not checking so precisely for the alpha value. We're checking for either 1 or 0 so it's not too important how close it is to either. --- osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs index 8e226c7ded..44404ca245 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs @@ -145,6 +145,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods private bool isBreak() => Player.IsBreakTime.Value; - private bool cursorAlphaAlmostEquals(float alpha) => Precision.AlmostEquals(Player.DrawableRuleset.Cursor.Alpha, alpha); + private bool cursorAlphaAlmostEquals(float alpha) => Precision.AlmostEquals(Player.DrawableRuleset.Cursor.Alpha, alpha, 0.1f); } } From 142a67e1634fb379e8725d3ad51e5cf4dd9825a6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 18:53:28 +0900 Subject: [PATCH 748/996] Fix approach rate not being transferred from OD on older beatmaps --- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 01ba0fcc9f..07ada8ecc4 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -311,10 +311,13 @@ namespace osu.Game.Beatmaps.Formats case @"OverallDifficulty": difficulty.OverallDifficulty = Parsing.ParseFloat(pair.Value); + if (!hasApproachRate) + difficulty.ApproachRate = difficulty.OverallDifficulty; break; case @"ApproachRate": difficulty.ApproachRate = Parsing.ParseFloat(pair.Value); + hasApproachRate = true; break; case @"SliderMultiplier": @@ -432,6 +435,7 @@ namespace osu.Game.Beatmaps.Formats private readonly List pendingControlPoints = new List(); private readonly HashSet pendingControlPointTypes = new HashSet(); private double pendingControlPointsTime; + private bool hasApproachRate; private void addControlPoint(double time, ControlPoint point, bool timingChange) { From 53ca597e2b2c85431c722f1801f00805179b150b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 19:12:32 +0900 Subject: [PATCH 749/996] 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 f85a96f819..2902c74f0a 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 aa6fb93aa0..b74d51e8f1 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 fbb4688588..90ffb9605f 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -60,7 +60,7 @@ - + @@ -83,7 +83,7 @@ - + From bda3cdc9a773ac76c36470174e13db398e33fd7e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 28 Jan 2022 19:28:42 +0900 Subject: [PATCH 750/996] Add tests --- .../Formats/LegacyBeatmapDecoderTest.cs | 42 +++++++++++++++++++ ...approach-rate-after-overall-difficulty.osu | 3 ++ ...pproach-rate-before-overall-difficulty.osu | 3 ++ .../Resources/undefined-approach-rate.osu | 2 + 4 files changed, 50 insertions(+) create mode 100644 osu.Game.Tests/Resources/approach-rate-after-overall-difficulty.osu create mode 100644 osu.Game.Tests/Resources/approach-rate-before-overall-difficulty.osu create mode 100644 osu.Game.Tests/Resources/undefined-approach-rate.osu diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 3a6051ed43..468cb7683c 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -821,5 +821,47 @@ namespace osu.Game.Tests.Beatmaps.Formats }); } } + + [Test] + public void TestUndefinedApproachRateInheritsOverallDifficulty() + { + var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + + using (var resStream = TestResources.OpenResource("undefined-approach-rate.osu")) + using (var stream = new LineBufferedReader(resStream)) + { + var decoded = decoder.Decode(stream); + Assert.That(decoded.Difficulty.ApproachRate, Is.EqualTo(1)); + Assert.That(decoded.Difficulty.OverallDifficulty, Is.EqualTo(1)); + } + } + + [Test] + public void TestApproachRateDefinedBeforeOverallDifficulty() + { + var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + + using (var resStream = TestResources.OpenResource("approach-rate-before-overall-difficulty.osu")) + using (var stream = new LineBufferedReader(resStream)) + { + var decoded = decoder.Decode(stream); + Assert.That(decoded.Difficulty.ApproachRate, Is.EqualTo(9)); + Assert.That(decoded.Difficulty.OverallDifficulty, Is.EqualTo(1)); + } + } + + [Test] + public void TestApproachRateDefinedAfterOverallDifficulty() + { + var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + + using (var resStream = TestResources.OpenResource("approach-rate-after-overall-difficulty.osu")) + using (var stream = new LineBufferedReader(resStream)) + { + var decoded = decoder.Decode(stream); + Assert.That(decoded.Difficulty.ApproachRate, Is.EqualTo(9)); + Assert.That(decoded.Difficulty.OverallDifficulty, Is.EqualTo(1)); + } + } } } diff --git a/osu.Game.Tests/Resources/approach-rate-after-overall-difficulty.osu b/osu.Game.Tests/Resources/approach-rate-after-overall-difficulty.osu new file mode 100644 index 0000000000..23732aef8c --- /dev/null +++ b/osu.Game.Tests/Resources/approach-rate-after-overall-difficulty.osu @@ -0,0 +1,3 @@ +[Difficulty] +OverallDifficulty:1 +ApproachRate:9 \ No newline at end of file diff --git a/osu.Game.Tests/Resources/approach-rate-before-overall-difficulty.osu b/osu.Game.Tests/Resources/approach-rate-before-overall-difficulty.osu new file mode 100644 index 0000000000..18885c6624 --- /dev/null +++ b/osu.Game.Tests/Resources/approach-rate-before-overall-difficulty.osu @@ -0,0 +1,3 @@ +[Difficulty] +ApproachRate:9 +OverallDifficulty:1 \ No newline at end of file diff --git a/osu.Game.Tests/Resources/undefined-approach-rate.osu b/osu.Game.Tests/Resources/undefined-approach-rate.osu new file mode 100644 index 0000000000..0de24238bf --- /dev/null +++ b/osu.Game.Tests/Resources/undefined-approach-rate.osu @@ -0,0 +1,2 @@ +[Difficulty] +OverallDifficulty:1 \ No newline at end of file From 397971c6313eff22d645c07d782547a4e7683a2e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 28 Jan 2022 22:06:34 +0900 Subject: [PATCH 751/996] Change FrameDataBundle.Frames into an IList --- osu.Game/Online/Spectator/FrameDataBundle.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/Spectator/FrameDataBundle.cs b/osu.Game/Online/Spectator/FrameDataBundle.cs index 0e59cdf4ce..a4c4972989 100644 --- a/osu.Game/Online/Spectator/FrameDataBundle.cs +++ b/osu.Game/Online/Spectator/FrameDataBundle.cs @@ -20,16 +20,16 @@ namespace osu.Game.Online.Spectator public FrameHeader Header { get; set; } [Key(1)] - public IEnumerable Frames { get; set; } + public IList Frames { get; set; } - public FrameDataBundle(ScoreInfo score, IEnumerable frames) + public FrameDataBundle(ScoreInfo score, IList frames) { Frames = frames; Header = new FrameHeader(score); } [JsonConstructor] - public FrameDataBundle(FrameHeader header, IEnumerable frames) + public FrameDataBundle(FrameHeader header, IList frames) { Header = header; Frames = frames; From 3037a3a7696b7f00cef3820c5c5994949de78622 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 28 Jan 2022 22:26:05 +0900 Subject: [PATCH 752/996] Purge final spectator frames before ending play --- .../Visual/Gameplay/TestSceneSpectator.cs | 18 ++++++++++++++++++ osu.Game/Online/Spectator/SpectatorClient.cs | 10 ++++++++-- .../Visual/Spectator/TestSpectatorClient.cs | 11 +++++++++++ 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index 0afd2c76dd..d1a25b07ec 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -15,11 +15,14 @@ using osu.Game.Online.Spectator; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Replays; using osu.Game.Rulesets.UI; +using osu.Game.Scoring; using osu.Game.Screens; using osu.Game.Screens.Play; +using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Beatmaps.IO; using osu.Game.Tests.Visual.Multiplayer; using osu.Game.Tests.Visual.Spectator; +using osuTK; namespace osu.Game.Tests.Visual.Gameplay { @@ -200,6 +203,21 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("screen didn't change", () => Stack.CurrentScreen is SoloSpectator); } + [Test] + public void TestFinalFramesPurgedBeforeEndingPlay() + { + AddStep("begin playing", () => spectatorClient.BeginPlaying(new GameplayState(new TestBeatmap(new OsuRuleset().RulesetInfo), new OsuRuleset()), new Score())); + + AddStep("send frames and finish play", () => + { + spectatorClient.HandleFrame(new OsuReplayFrame(1000, Vector2.Zero)); + spectatorClient.EndPlaying(); + }); + + // We can't access API because we're an "online" test. + AddAssert("last received frame has time = 1000", () => spectatorClient.LastReceivedFrame.First().Value.Time == 1000); + } + private OsuFramedReplayInputHandler replayHandler => (OsuFramedReplayInputHandler)Stack.ChildrenOfType().First().ReplayInputHandler; diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index 4da9bace70..fddb94fad7 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -167,6 +167,9 @@ namespace osu.Game.Online.Spectator if (!IsPlaying) return; + if (pendingFrames.Count > 0) + purgePendingFrames(true); + IsPlaying = false; currentBeatmap = null; @@ -238,9 +241,12 @@ namespace osu.Game.Online.Spectator purgePendingFrames(); } - private void purgePendingFrames() + private void purgePendingFrames(bool force = false) { - if (lastSend?.IsCompleted == false) + if (lastSend?.IsCompleted == false && !force) + return; + + if (pendingFrames.Count == 0) return; var frames = pendingFrames.ToArray(); diff --git a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs index f206d4f8b0..3f3d0f5b01 100644 --- a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs +++ b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs @@ -14,6 +14,7 @@ using osu.Framework.Utils; using osu.Game.Online.API; using osu.Game.Online.Spectator; using osu.Game.Replays.Legacy; +using osu.Game.Rulesets.Replays; using osu.Game.Scoring; namespace osu.Game.Tests.Visual.Spectator @@ -27,12 +28,22 @@ namespace osu.Game.Tests.Visual.Spectator public override IBindable IsConnected { get; } = new Bindable(true); + public readonly Dictionary LastReceivedFrame = new Dictionary(); private readonly Dictionary userBeatmapDictionary = new Dictionary(); private readonly Dictionary userNextFrameDictionary = new Dictionary(); [Resolved] private IAPIProvider api { get; set; } = null!; + public TestSpectatorClient() + { + OnNewFrames += (i, bundle) => + { + if (PlayingUsers.Contains(i)) + LastReceivedFrame[i] = bundle.Frames[^1]; + }; + } + /// /// Starts play for an arbitrary user. /// From 1253e1ecc1f7ec68f78f7b30878b673b2c37345d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 28 Jan 2022 20:25:12 +0100 Subject: [PATCH 753/996] Replace LINQ `Count()` invocation with count property access --- osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs index 4790bd44db..90abdf2ba3 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs @@ -184,7 +184,7 @@ namespace osu.Game.Tests.Visual.Gameplay private void onNewFrames(int userId, FrameDataBundle frames) { - Logger.Log($"Received {frames.Frames.Count()} new frames ({string.Join(',', frames.Frames.Select(f => ((int)f.Time).ToString()))})"); + Logger.Log($"Received {frames.Frames.Count} new frames ({string.Join(',', frames.Frames.Select(f => ((int)f.Time).ToString()))})"); foreach (var legacyFrame in frames.Frames) { From a4aa501bb540cd83f988febd90d128f1c890b0b9 Mon Sep 17 00:00:00 2001 From: Spooghetti420 Date: Fri, 28 Jan 2022 21:59:53 +0000 Subject: [PATCH 754/996] Change threshold from ms to beat-based, add tests --- .../Mods/TestSceneManiaModHoldOff.cs | 130 ++++++++++++++++++ .../Mods/TestSceneManiaModNoHolds.cs | 51 ------- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 2 +- ...{ManiaModNoHolds.cs => ManiaModHoldOff.cs} | 34 +++-- .../Mods/ManiaModNoLongNotes.cs | 86 ------------ 5 files changed, 155 insertions(+), 148 deletions(-) create mode 100644 osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs delete mode 100644 osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModNoHolds.cs rename osu.Game.Rulesets.Mania/Mods/{ManiaModNoHolds.cs => ManiaModHoldOff.cs} (66%) delete mode 100644 osu.Game.Rulesets.Mania/Mods/ManiaModNoLongNotes.cs diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs new file mode 100644 index 0000000000..e157cb50b1 --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs @@ -0,0 +1,130 @@ +// 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.Game.Beatmaps; +using osu.Game.Rulesets.Mania.Mods; +using osu.Game.Tests.Visual; +using System.Collections.Generic; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Mania.Beatmaps; +using osu.Game.Rulesets.Mania.Difficulty; +using osu.Game.Tests.Beatmaps; + +namespace osu.Game.Rulesets.Mania.Tests.Mods +{ + public class TestSceneManiaModHoldOff : ModTestScene + { + protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset(); + + [Test] + public void TestMapHasNoHoldNotes() + { + var testBeatmap = createModdedBeatmap(); + Assert.False(testBeatmap.HitObjects.OfType().Any()); + } + + [Test] + public void TestCorrectNoteValues() + { + var testBeatmap = createRawBeatmap(); + var noteValues = new List(testBeatmap.HitObjects.OfType().Count()); + foreach (HoldNote h in testBeatmap.HitObjects.OfType()) { + noteValues.Add(ManiaModHoldOff.getNoteValue(h, (ManiaBeatmap)testBeatmap)); + } + noteValues.Sort(); + Assert.AreEqual(noteValues, new List { 0.125, 0.250, 0.500, 1.000, 2.000 }); + } + + [TestCase(ManiaModHoldOff.BeatDivisors.Whole)] + [TestCase(ManiaModHoldOff.BeatDivisors.Half)] + [TestCase(ManiaModHoldOff.BeatDivisors.Quarter)] + [TestCase(ManiaModHoldOff.BeatDivisors.Eighth)] + public void TestCorrectObjectCount(ManiaModHoldOff.BeatDivisors minBeatSnap) { + /* + This test is to ensure that, given that end notes are enabled, + the mod produces the expected number of objects when the mod is applied. + */ + + // Mod settings will be set to include the correct beat snap value + var rawBeatmap = createRawBeatmap(); + var testBeatmap = createModdedBeatmap(minBeatSnap); + + // Calculate expected number of objects + int expectedObjectCount = 0; + double beatSnapValue = 1/(Math.Pow(2, (int)minBeatSnap)); + + foreach (ManiaHitObject h in rawBeatmap.HitObjects) { + // Both notes and hold notes account for at least one object + expectedObjectCount++; + if (h.GetType() == typeof(HoldNote)) { + var noteValue = ManiaModHoldOff.getNoteValue((HoldNote)h, (ManiaBeatmap)rawBeatmap); + if (noteValue >= beatSnapValue) { + // Should generate an end note if it's longer than the minimum note value + expectedObjectCount++; + } + } + } + + Assert.That(testBeatmap.HitObjects.Count() == expectedObjectCount); + } + + [Test] + public void TestDifficultyIncrease() { + // A lower minimum beat snap divisor should only make the map harder, never easier + // (as more notes can be spawned) + var beatmaps = new ManiaBeatmap[] { + createModdedBeatmap(ManiaModHoldOff.BeatDivisors.Whole), + createModdedBeatmap(ManiaModHoldOff.BeatDivisors.Half), + createModdedBeatmap(ManiaModHoldOff.BeatDivisors.Quarter), + createModdedBeatmap(ManiaModHoldOff.BeatDivisors.Eighth), + createModdedBeatmap(ManiaModHoldOff.BeatDivisors.Sixteenth) + }; + + var mapDifficulties = new double[beatmaps.Length]; + for (int i = 0; i < mapDifficulties.Length; i++) { + var workingBeatmap = new TestWorkingBeatmap(beatmaps[i]); + var difficultyCalculator = new ManiaDifficultyCalculator(new ManiaRuleset().RulesetInfo, workingBeatmap); + mapDifficulties[i] = difficultyCalculator.Calculate().StarRating; + if (i > 0) { + Assert.LessOrEqual(mapDifficulties[i-1], mapDifficulties[i]); + Assert.LessOrEqual(beatmaps[i-1].HitObjects.Count, beatmaps[i].HitObjects.Count); + } + } + } + + private static ManiaBeatmap createModdedBeatmap(ManiaModHoldOff.BeatDivisors minBeatSnap=ManiaModHoldOff.BeatDivisors.Whole) + { + var beatmap = createRawBeatmap(); + var holdOffMod = new ManiaModHoldOff(); + holdOffMod.MinBeatSnap.Value = minBeatSnap; // Set the specified beat snap setting + Assert.AreEqual(holdOffMod.MinBeatSnap.Value, minBeatSnap); + + foreach (var hitObject in beatmap.HitObjects) + hitObject.ApplyDefaults(beatmap.ControlPointInfo, new BeatmapDifficulty()); + + holdOffMod.ApplyToBeatmap(beatmap); + + return (ManiaBeatmap)beatmap; + } + private static ManiaBeatmap createRawBeatmap() + { + var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 }); + beatmap.ControlPointInfo.Add(0.0, new TimingControlPoint { BeatLength = 1000 } ); // Set BPM to 60 + + // Add test hit objects + beatmap.HitObjects.Add(new Note { StartTime = 4000 }); + beatmap.HitObjects.Add(new Note { StartTime = 4500 }); + beatmap.HitObjects.Add(new HoldNote { StartTime = 0, EndTime = 125 }); // 1/8 note + beatmap.HitObjects.Add(new HoldNote { StartTime = 0, EndTime = 250 }); // 1/4 note + beatmap.HitObjects.Add(new HoldNote { StartTime = 0, EndTime = 500 }); // 1/2 note + beatmap.HitObjects.Add(new HoldNote { StartTime = 0, EndTime = 1000 }); // 1/1 note + beatmap.HitObjects.Add(new HoldNote { StartTime = 0, EndTime = 2000 }); // 2/1 note + + return beatmap; + } + } +} diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModNoHolds.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModNoHolds.cs deleted file mode 100644 index f22b724c99..0000000000 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModNoHolds.cs +++ /dev/null @@ -1,51 +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.Linq; -using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Testing; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Mania.Mods; -using osu.Game.Tests.Visual; -using osu.Game.Rulesets.Mania.Tests; -using System.Collections.Generic; -using osu.Game.Rulesets.Mania.Objects; -using osu.Game.Rulesets.Objects; -using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Rulesets.Mania.Beatmaps; - -namespace osu.Game.Rulesets.Mania.Tests.Mods -{ - public class TestSceneManiaModNoHolds : ModTestScene - { - protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset(); - - [Test] - public void TestMapHasNoHeldNotes() - { - var testBeatmap = createBeatmap(); - Assert.That(!testBeatmap.HitObjects.OfType().Any()); - } - - private static IBeatmap createBeatmap() - { - var beatmap = createRawBeatmap(); - var noHoldsMod = new ManiaModNoHolds(); - - foreach (var hitObject in beatmap.HitObjects) - hitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - - noHoldsMod.ApplyToBeatmap(beatmap); - - return beatmap; - } - private static IBeatmap createRawBeatmap() - { - var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 }); - beatmap.HitObjects.Add(new Note { StartTime = 1000 }); - beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, EndTime = 3000 }); - return beatmap; - } - } -} diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 55ca1a465e..2098c7f5d8 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -244,7 +244,7 @@ namespace osu.Game.Rulesets.Mania new ManiaModClassic(), new ManiaModInvert(), new ManiaModConstantSpeed(), - new ManiaModNoHolds() + new ManiaModHoldOff() }; case ModType.Automation: diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModNoHolds.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs similarity index 66% rename from osu.Game.Rulesets.Mania/Mods/ManiaModNoHolds.cs rename to osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs index a18dbad4a1..cfdb58ee67 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModNoHolds.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs @@ -2,28 +2,23 @@ // See the LICENCE file in the repository root for full licence text. using System.Linq; -using osu.Framework.Extensions.IEnumerableExtensions; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mods; using osu.Framework.Graphics.Sprites; using System; using System.Collections.Generic; -using osu.Game.Audio; using osu.Game.Rulesets.Mania.Beatmaps; -using osu.Framework.Utils; -using osu.Game.Overlays.Settings; using osu.Framework.Bindables; using osu.Game.Configuration; -using osu.Game.Graphics; namespace osu.Game.Rulesets.Mania.Mods { - public class ManiaModNoHolds : Mod, IApplicableAfterBeatmapConversion + public class ManiaModHoldOff : Mod, IApplicableAfterBeatmapConversion { - public override string Name => "No Holds"; + public override string Name => "Hold Off"; - public override string Acronym => "NH"; + public override string Acronym => "HO"; public override double ScoreMultiplier => 1; @@ -40,11 +35,15 @@ namespace osu.Game.Rulesets.Mania.Mods Value = true }; + [SettingSource("Minimum end note beat snap", "Don't add end notes for hold notes shorter than this beat division")] + public Bindable MinBeatSnap { get; } = new Bindable(defaultValue: BeatDivisors.Half); + public void ApplyToBeatmap(IBeatmap beatmap) { var maniaBeatmap = (ManiaBeatmap)beatmap; var newObjects = new List(); + var beatSnap = 1/(Math.Pow(2, (double)MinBeatSnap.Value)); foreach (var h in beatmap.HitObjects.OfType()) { // Add a note for the beginning of the hold note @@ -56,7 +55,8 @@ namespace osu.Game.Rulesets.Mania.Mods }); // Don't add an end note if the duration is shorter than some threshold, or end notes are disabled - if (AddEndNotes.Value && h.Duration > 200) + var noteValue = getNoteValue(h, maniaBeatmap); // 1/1, 1/2, 1/4, etc. + if (AddEndNotes.Value && noteValue >= beatSnap) { newObjects.Add(new Note { @@ -66,8 +66,22 @@ namespace osu.Game.Rulesets.Mania.Mods }); } } - maniaBeatmap.HitObjects = maniaBeatmap.HitObjects.OfType().Concat(newObjects).OrderBy(h => h.StartTime).ToList(); } + + public static double getNoteValue(HoldNote holdNote, ManiaBeatmap beatmap) { + var bpmAtNoteTime = beatmap.ControlPointInfo.TimingPointAt(holdNote.StartTime).BPM; + var noteValue = (60*holdNote.Duration)/(1000*bpmAtNoteTime); + return noteValue; + } + + public enum BeatDivisors + { + Whole, + Half, + Quarter, + Eighth, + Sixteenth + } } } \ No newline at end of file diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModNoLongNotes.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModNoLongNotes.cs deleted file mode 100644 index d10003b783..0000000000 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModNoLongNotes.cs +++ /dev/null @@ -1,86 +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.Linq; -using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Mania.Objects; -using osu.Game.Rulesets.Mods; -using osu.Framework.Graphics.Sprites; -using System; -using System.Collections.Generic; -using osu.Game.Audio; -using osu.Game.Rulesets.Mania.Beatmaps; -using osu.Framework.Utils; -using osu.Game.Overlays.Settings; -using osu.Framework.Bindables; -using osu.Game.Configuration; -using osu.Game.Graphics; - - - -namespace osu.Game.Rulesets.Mania.Mods -{ - public class ManiaModNoLongNotes : Mod, IApplicableAfterBeatmapConversion - { - - public override string Name => "No Long Notes"; - - public override string Acronym => "NL"; - - public override double ScoreMultiplier => 1; - - public override string Description => @"Turns all held notes into tap notes. No coordination required."; - - public override IconUsage? Icon => FontAwesome.Solid.DotCircle; - - public override ModType Type => ModType.Conversion; - - [SettingSource("Add end notes", "Also add a note at the end of a held note")] - public BindableBool AddEndNotes { get; } = new BindableBool - { - Default = true, - Value = true - }; - - [SettingSource("Length threshold", "Only add an end note for held notes longer than this threshold (in milliseconds)")] - public BindableNumber Threshold { get; } = new BindableDouble - { - MinValue = 1.0, - MaxValue = 1990.0, - Default = 200.0, - Value = 200.0, - Precision = 1.0, - }; - - public void ApplyToBeatmap(IBeatmap beatmap) - { - var maniaBeatmap = (ManiaBeatmap)beatmap; - - var newObjects = new List(); - beatmap.HitObjects.OfType().ForEach(h => - { - // Add a note for the beginning of the hold note - newObjects.Add(new Note - { - Column = h.Column, - StartTime = h.StartTime, - Samples = h.Samples - }); - - // Don't add an end note if the duration is below the threshold, or end notes are disabled - if (AddEndNotes.Value && h.Duration > Threshold.Value) - { - newObjects.Add(new Note - { - Column = h.Column, - StartTime = h.EndTime, - Samples = h.Samples - }); - } - }); - - maniaBeatmap.HitObjects = maniaBeatmap.HitObjects.OfType().Concat(newObjects).OrderBy(h => h.StartTime).ToList(); - } - } -} From b2ebcabdd5c56fa734a6eaf8eacc8b8c5c3db9a6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 29 Jan 2022 13:27:19 +0900 Subject: [PATCH 755/996] Fix potential crash during stable install migration due to multiple configuration files Apparently this can be a thing on windows. Closes https://github.com/ppy/osu/discussions/16689. --- osu.Game/IO/StableStorage.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/osu.Game/IO/StableStorage.cs b/osu.Game/IO/StableStorage.cs index f5a8c4dc9e..84b7da91fc 100644 --- a/osu.Game/IO/StableStorage.cs +++ b/osu.Game/IO/StableStorage.cs @@ -34,11 +34,17 @@ namespace osu.Game.IO private string locateSongsDirectory() { - string configFile = GetFiles(".", $"osu!.{Environment.UserName}.cfg").SingleOrDefault(); + var configurationFiles = GetFiles(".", $"osu!.{Environment.UserName}.cfg"); - if (configFile != null) + // GetFiles returns case insensitive results, so multiple files could exist. + // Prefer a case-correct match, but fallback to any available. + string usableConfigFile = + configurationFiles.FirstOrDefault(f => f.Contains(Environment.UserName, StringComparison.Ordinal)) + ?? configurationFiles.FirstOrDefault(); + + if (usableConfigFile != null) { - using (var stream = GetStream(configFile)) + using (var stream = GetStream(usableConfigFile)) using (var textReader = new StreamReader(stream)) { string line; From c75ffe9b0764effd0c5270ac6b0a0bcd02c6762a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 29 Jan 2022 13:47:04 +0900 Subject: [PATCH 756/996] Apply code style changes --- osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs index cfdb58ee67..aa447356ff 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs @@ -43,7 +43,8 @@ namespace osu.Game.Rulesets.Mania.Mods var maniaBeatmap = (ManiaBeatmap)beatmap; var newObjects = new List(); - var beatSnap = 1/(Math.Pow(2, (double)MinBeatSnap.Value)); + double beatSnap = 1 / (Math.Pow(2, (double)MinBeatSnap.Value)); + foreach (var h in beatmap.HitObjects.OfType()) { // Add a note for the beginning of the hold note @@ -55,7 +56,8 @@ namespace osu.Game.Rulesets.Mania.Mods }); // Don't add an end note if the duration is shorter than some threshold, or end notes are disabled - var noteValue = getNoteValue(h, maniaBeatmap); // 1/1, 1/2, 1/4, etc. + double noteValue = getNoteValue(h, maniaBeatmap); // 1/1, 1/2, 1/4, etc. + if (AddEndNotes.Value && noteValue >= beatSnap) { newObjects.Add(new Note @@ -66,13 +68,14 @@ namespace osu.Game.Rulesets.Mania.Mods }); } } + maniaBeatmap.HitObjects = maniaBeatmap.HitObjects.OfType().Concat(newObjects).OrderBy(h => h.StartTime).ToList(); } - public static double getNoteValue(HoldNote holdNote, ManiaBeatmap beatmap) { - var bpmAtNoteTime = beatmap.ControlPointInfo.TimingPointAt(holdNote.StartTime).BPM; - var noteValue = (60*holdNote.Duration)/(1000*bpmAtNoteTime); - return noteValue; + private static double getNoteValue(HoldNote holdNote, ManiaBeatmap beatmap) + { + double bpmAtNoteTime = beatmap.ControlPointInfo.TimingPointAt(holdNote.StartTime).BPM; + return (60 * holdNote.Duration) / (1000 * bpmAtNoteTime); } public enum BeatDivisors @@ -84,4 +87,4 @@ namespace osu.Game.Rulesets.Mania.Mods Sixteenth } } -} \ No newline at end of file +} From 035a84e75c4b84f655ad8fadd27b24677bd66ae2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 29 Jan 2022 14:05:23 +0900 Subject: [PATCH 757/996] Rename function and make `public` again for test usage --- .../Mods/TestSceneManiaModHoldOff.cs | 54 ++++++++++++------- .../Mods/ManiaModHoldOff.cs | 4 +- 2 files changed, 37 insertions(+), 21 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs index e157cb50b1..ddf09aab33 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs @@ -32,9 +32,12 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods { var testBeatmap = createRawBeatmap(); var noteValues = new List(testBeatmap.HitObjects.OfType().Count()); - foreach (HoldNote h in testBeatmap.HitObjects.OfType()) { - noteValues.Add(ManiaModHoldOff.getNoteValue(h, (ManiaBeatmap)testBeatmap)); + + foreach (HoldNote h in testBeatmap.HitObjects.OfType()) + { + noteValues.Add(ManiaModHoldOff.GetNoteDurationInBeatLength(h, (ManiaBeatmap)testBeatmap)); } + noteValues.Sort(); Assert.AreEqual(noteValues, new List { 0.125, 0.250, 0.500, 1.000, 2.000 }); } @@ -43,7 +46,8 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods [TestCase(ManiaModHoldOff.BeatDivisors.Half)] [TestCase(ManiaModHoldOff.BeatDivisors.Quarter)] [TestCase(ManiaModHoldOff.BeatDivisors.Eighth)] - public void TestCorrectObjectCount(ManiaModHoldOff.BeatDivisors minBeatSnap) { + public void TestCorrectObjectCount(ManiaModHoldOff.BeatDivisors minBeatSnap) + { /* This test is to ensure that, given that end notes are enabled, the mod produces the expected number of objects when the mod is applied. @@ -55,14 +59,19 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods // Calculate expected number of objects int expectedObjectCount = 0; - double beatSnapValue = 1/(Math.Pow(2, (int)minBeatSnap)); + double beatSnapValue = 1 / (Math.Pow(2, (int)minBeatSnap)); - foreach (ManiaHitObject h in rawBeatmap.HitObjects) { + foreach (ManiaHitObject h in rawBeatmap.HitObjects) + { // Both notes and hold notes account for at least one object expectedObjectCount++; - if (h.GetType() == typeof(HoldNote)) { - var noteValue = ManiaModHoldOff.getNoteValue((HoldNote)h, (ManiaBeatmap)rawBeatmap); - if (noteValue >= beatSnapValue) { + + if (h.GetType() == typeof(HoldNote)) + { + var noteValue = ManiaModHoldOff.GetNoteDurationInBeatLength((HoldNote)h, (ManiaBeatmap)rawBeatmap); + + if (noteValue >= beatSnapValue) + { // Should generate an end note if it's longer than the minimum note value expectedObjectCount++; } @@ -73,10 +82,12 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods } [Test] - public void TestDifficultyIncrease() { + public void TestDifficultyIncrease() + { // A lower minimum beat snap divisor should only make the map harder, never easier // (as more notes can be spawned) - var beatmaps = new ManiaBeatmap[] { + var beatmaps = new ManiaBeatmap[] + { createModdedBeatmap(ManiaModHoldOff.BeatDivisors.Whole), createModdedBeatmap(ManiaModHoldOff.BeatDivisors.Half), createModdedBeatmap(ManiaModHoldOff.BeatDivisors.Quarter), @@ -85,18 +96,22 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods }; var mapDifficulties = new double[beatmaps.Length]; - for (int i = 0; i < mapDifficulties.Length; i++) { + + for (int i = 0; i < mapDifficulties.Length; i++) + { var workingBeatmap = new TestWorkingBeatmap(beatmaps[i]); var difficultyCalculator = new ManiaDifficultyCalculator(new ManiaRuleset().RulesetInfo, workingBeatmap); mapDifficulties[i] = difficultyCalculator.Calculate().StarRating; - if (i > 0) { - Assert.LessOrEqual(mapDifficulties[i-1], mapDifficulties[i]); - Assert.LessOrEqual(beatmaps[i-1].HitObjects.Count, beatmaps[i].HitObjects.Count); + + if (i > 0) + { + Assert.LessOrEqual(mapDifficulties[i - 1], mapDifficulties[i]); + Assert.LessOrEqual(beatmaps[i - 1].HitObjects.Count, beatmaps[i].HitObjects.Count); } } } - private static ManiaBeatmap createModdedBeatmap(ManiaModHoldOff.BeatDivisors minBeatSnap=ManiaModHoldOff.BeatDivisors.Whole) + private static ManiaBeatmap createModdedBeatmap(ManiaModHoldOff.BeatDivisors minBeatSnap = ManiaModHoldOff.BeatDivisors.Whole) { var beatmap = createRawBeatmap(); var holdOffMod = new ManiaModHoldOff(); @@ -110,17 +125,18 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods return (ManiaBeatmap)beatmap; } + private static ManiaBeatmap createRawBeatmap() { var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 }); - beatmap.ControlPointInfo.Add(0.0, new TimingControlPoint { BeatLength = 1000 } ); // Set BPM to 60 + beatmap.ControlPointInfo.Add(0.0, new TimingControlPoint { BeatLength = 1000 }); // Set BPM to 60 // Add test hit objects beatmap.HitObjects.Add(new Note { StartTime = 4000 }); beatmap.HitObjects.Add(new Note { StartTime = 4500 }); - beatmap.HitObjects.Add(new HoldNote { StartTime = 0, EndTime = 125 }); // 1/8 note - beatmap.HitObjects.Add(new HoldNote { StartTime = 0, EndTime = 250 }); // 1/4 note - beatmap.HitObjects.Add(new HoldNote { StartTime = 0, EndTime = 500 }); // 1/2 note + beatmap.HitObjects.Add(new HoldNote { StartTime = 0, EndTime = 125 }); // 1/8 note + beatmap.HitObjects.Add(new HoldNote { StartTime = 0, EndTime = 250 }); // 1/4 note + beatmap.HitObjects.Add(new HoldNote { StartTime = 0, EndTime = 500 }); // 1/2 note beatmap.HitObjects.Add(new HoldNote { StartTime = 0, EndTime = 1000 }); // 1/1 note beatmap.HitObjects.Add(new HoldNote { StartTime = 0, EndTime = 2000 }); // 2/1 note diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs index aa447356ff..637142aed6 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs @@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Mania.Mods }); // Don't add an end note if the duration is shorter than some threshold, or end notes are disabled - double noteValue = getNoteValue(h, maniaBeatmap); // 1/1, 1/2, 1/4, etc. + double noteValue = GetNoteDurationInBeatLength(h, maniaBeatmap); // 1/1, 1/2, 1/4, etc. if (AddEndNotes.Value && noteValue >= beatSnap) { @@ -72,7 +72,7 @@ namespace osu.Game.Rulesets.Mania.Mods maniaBeatmap.HitObjects = maniaBeatmap.HitObjects.OfType().Concat(newObjects).OrderBy(h => h.StartTime).ToList(); } - private static double getNoteValue(HoldNote holdNote, ManiaBeatmap beatmap) + public static double GetNoteDurationInBeatLength(HoldNote holdNote, ManiaBeatmap beatmap) { double bpmAtNoteTime = beatmap.ControlPointInfo.TimingPointAt(holdNote.StartTime).BPM; return (60 * holdNote.Duration) / (1000 * bpmAtNoteTime); From c7580a5177eb24aad1a667bee76e098dcb60f559 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 29 Jan 2022 14:06:43 +0900 Subject: [PATCH 758/996] Fix inspections in test scene --- .../Mods/TestSceneManiaModHoldOff.cs | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs index ddf09aab33..e74f63abb3 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods foreach (HoldNote h in testBeatmap.HitObjects.OfType()) { - noteValues.Add(ManiaModHoldOff.GetNoteDurationInBeatLength(h, (ManiaBeatmap)testBeatmap)); + noteValues.Add(ManiaModHoldOff.GetNoteDurationInBeatLength(h, testBeatmap)); } noteValues.Sort(); @@ -68,7 +68,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods if (h.GetType() == typeof(HoldNote)) { - var noteValue = ManiaModHoldOff.GetNoteDurationInBeatLength((HoldNote)h, (ManiaBeatmap)rawBeatmap); + double noteValue = ManiaModHoldOff.GetNoteDurationInBeatLength((HoldNote)h, rawBeatmap); if (noteValue >= beatSnapValue) { @@ -78,7 +78,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods } } - Assert.That(testBeatmap.HitObjects.Count() == expectedObjectCount); + Assert.That(testBeatmap.HitObjects.Count == expectedObjectCount); } [Test] @@ -86,16 +86,16 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods { // A lower minimum beat snap divisor should only make the map harder, never easier // (as more notes can be spawned) - var beatmaps = new ManiaBeatmap[] + var beatmaps = new[] { - createModdedBeatmap(ManiaModHoldOff.BeatDivisors.Whole), + createModdedBeatmap(), createModdedBeatmap(ManiaModHoldOff.BeatDivisors.Half), createModdedBeatmap(ManiaModHoldOff.BeatDivisors.Quarter), createModdedBeatmap(ManiaModHoldOff.BeatDivisors.Eighth), createModdedBeatmap(ManiaModHoldOff.BeatDivisors.Sixteenth) }; - var mapDifficulties = new double[beatmaps.Length]; + double[] mapDifficulties = new double[beatmaps.Length]; for (int i = 0; i < mapDifficulties.Length; i++) { @@ -114,8 +114,10 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods private static ManiaBeatmap createModdedBeatmap(ManiaModHoldOff.BeatDivisors minBeatSnap = ManiaModHoldOff.BeatDivisors.Whole) { var beatmap = createRawBeatmap(); - var holdOffMod = new ManiaModHoldOff(); - holdOffMod.MinBeatSnap.Value = minBeatSnap; // Set the specified beat snap setting + var holdOffMod = new ManiaModHoldOff + { + MinBeatSnap = { Value = minBeatSnap } + }; Assert.AreEqual(holdOffMod.MinBeatSnap.Value, minBeatSnap); foreach (var hitObject in beatmap.HitObjects) @@ -123,7 +125,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods holdOffMod.ApplyToBeatmap(beatmap); - return (ManiaBeatmap)beatmap; + return beatmap; } private static ManiaBeatmap createRawBeatmap() From 4c97ed676fc335a8640bcf1e39085e656b4e997f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 29 Jan 2022 14:46:24 +0900 Subject: [PATCH 759/996] Fix score presentation tests not correctly entering song select before running --- osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs index 7656bf79dc..6c32171b29 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs @@ -16,6 +16,7 @@ using osu.Game.Screens; using osu.Game.Screens.Menu; using osu.Game.Screens.Play; using osu.Game.Screens.Ranking; +using osu.Game.Screens.Select; namespace osu.Game.Tests.Visual.Navigation { @@ -92,6 +93,9 @@ namespace osu.Game.Tests.Visual.Navigation [Test] public void TestFromSongSelect([Values] ScorePresentType type) { + AddStep("enter song select", () => Game.ChildrenOfType().Single().OnSolo.Invoke()); + AddUntilStep("song select is current", () => Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect && songSelect.BeatmapSetsLoaded); + var firstImport = importScore(1); presentAndConfirm(firstImport, type); @@ -102,6 +106,9 @@ namespace osu.Game.Tests.Visual.Navigation [Test] public void TestFromSongSelectDifferentRuleset([Values] ScorePresentType type) { + AddStep("enter song select", () => Game.ChildrenOfType().Single().OnSolo.Invoke()); + AddUntilStep("song select is current", () => Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect && songSelect.BeatmapSetsLoaded); + var firstImport = importScore(1); presentAndConfirm(firstImport, type); From e7823982d827b3371f80b4f74a5c567933c62551 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 29 Jan 2022 18:38:45 +0900 Subject: [PATCH 760/996] Fix ruleset value not being transferred when `FinaliseSelection` is not called --- osu.Game/Screens/Select/SongSelect.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 10150fcd9f..f5b11448f8 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -619,6 +619,10 @@ namespace osu.Game.Screens.Select public override void OnSuspending(IScreen next) { + // Handle the case where FinaliseSelection is never called (ie. when a screen is pushed externally). + // Without this, it's possible for a transfer to happen while we are not the current screen. + transferRulesetValue(); + ModSelect.SelectedMods.UnbindFrom(selectedMods); ModSelect.Hide(); From aa582fb0e1a15de97b48203a7bcaed3c84a4d6c7 Mon Sep 17 00:00:00 2001 From: Nitrous Date: Sat, 29 Jan 2022 20:38:12 +0800 Subject: [PATCH 761/996] add Alternate Mod --- osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs | 45 +++++++++++ osu.Game.Rulesets.Osu/OsuRuleset.cs | 1 + osu.Game/Rulesets/Mods/ModAlternate.cs | 77 +++++++++++++++++++ 3 files changed, 123 insertions(+) create mode 100644 osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs create mode 100644 osu.Game/Rulesets/Mods/ModAlternate.cs diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs new file mode 100644 index 0000000000..9a9074ee52 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs @@ -0,0 +1,45 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Osu.Mods +{ + public class OsuModAlternate : ModAlternate + { + private const double flash_duration = 1000; + private OsuAction? lastActionPressed; + private DrawableRuleset ruleset; + + public override void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) + { + ruleset = drawableRuleset; + base.ApplyToDrawableRuleset(drawableRuleset); + } + + protected override void Reset() + { + lastActionPressed = null; + } + + protected override bool OnPressed(OsuAction key) + { + if (lastActionPressed == key) + { + ruleset.Cursor.FlashColour(Colour4.Red, flash_duration, Easing.OutQuint); + return true; + } + + lastActionPressed = key; + + return false; + } + + protected override void OnReleased(OsuAction key) + { + } + } +} diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 18e4bb259c..7e8974b5ed 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -159,6 +159,7 @@ namespace osu.Game.Rulesets.Osu new MultiMod(new OsuModDoubleTime(), new OsuModNightcore()), new OsuModHidden(), new MultiMod(new OsuModFlashlight(), new OsuModBlinds()), + new OsuModAlternate(), }; case ModType.Conversion: diff --git a/osu.Game/Rulesets/Mods/ModAlternate.cs b/osu.Game/Rulesets/Mods/ModAlternate.cs new file mode 100644 index 0000000000..683654f605 --- /dev/null +++ b/osu.Game/Rulesets/Mods/ModAlternate.cs @@ -0,0 +1,77 @@ +// 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.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.UI; +using osu.Game.Screens.Play; + +namespace osu.Game.Rulesets.Mods +{ + public abstract class ModAlternate : Mod + { + public override string Name => @"Alternate"; + public override string Acronym => @"AL"; + public override string Description => @"Never hit the same key twice!"; + public override double ScoreMultiplier => 1.0; + public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay) }; + public override ModType Type => ModType.DifficultyIncrease; + public override IconUsage? Icon => FontAwesome.Solid.Keyboard; + } + + public abstract class ModAlternate : ModAlternate, IApplicableToDrawableRuleset, IApplicableToPlayer + where THitObject : HitObject + where TAction : struct + { + public bool CanIntercept => !isBreakTime.Value; + + private IBindable isBreakTime; + + public virtual void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) + { + drawableRuleset.KeyBindingInputManager.Add(new InputInterceptor(this)); + } + + public void ApplyToPlayer(Player player) + { + isBreakTime = player.IsBreakTime.GetBoundCopy(); + isBreakTime.ValueChanged += e => + { + if (e.NewValue) + Reset(); + }; + } + + protected abstract void Reset(); + + protected abstract bool OnPressed(TAction key); + + protected abstract void OnReleased(TAction key); + + private class InputInterceptor : Component, IKeyBindingHandler + { + private readonly ModAlternate mod; + + public InputInterceptor(ModAlternate mod) + { + this.mod = mod; + } + + public bool OnPressed(KeyBindingPressEvent e) + { + return mod.CanIntercept && mod.OnPressed(e.Action); + } + + public void OnReleased(KeyBindingReleaseEvent e) + { + if (mod.CanIntercept) + mod.OnReleased(e.Action); + } + } + } +} From 2326c36836aa317635c869610d7daff18ecbbee3 Mon Sep 17 00:00:00 2001 From: Nitrous Date: Sat, 29 Jan 2022 21:09:36 +0800 Subject: [PATCH 762/996] remove unused method and fix description --- osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs | 4 ---- osu.Game/Rulesets/Mods/ModAlternate.cs | 9 ++++----- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs index 9a9074ee52..34802bab43 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs @@ -37,9 +37,5 @@ namespace osu.Game.Rulesets.Osu.Mods return false; } - - protected override void OnReleased(OsuAction key) - { - } } } diff --git a/osu.Game/Rulesets/Mods/ModAlternate.cs b/osu.Game/Rulesets/Mods/ModAlternate.cs index 683654f605..17d8b92469 100644 --- a/osu.Game/Rulesets/Mods/ModAlternate.cs +++ b/osu.Game/Rulesets/Mods/ModAlternate.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Mods { public override string Name => @"Alternate"; public override string Acronym => @"AL"; - public override string Description => @"Never hit the same key twice!"; + public override string Description => @"Don't use the same key twice in a row!"; public override double ScoreMultiplier => 1.0; public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay) }; public override ModType Type => ModType.DifficultyIncrease; @@ -28,6 +28,9 @@ namespace osu.Game.Rulesets.Mods where THitObject : HitObject where TAction : struct { + /// + /// Whether incoming input must be checked by . + /// public bool CanIntercept => !isBreakTime.Value; private IBindable isBreakTime; @@ -51,8 +54,6 @@ namespace osu.Game.Rulesets.Mods protected abstract bool OnPressed(TAction key); - protected abstract void OnReleased(TAction key); - private class InputInterceptor : Component, IKeyBindingHandler { private readonly ModAlternate mod; @@ -69,8 +70,6 @@ namespace osu.Game.Rulesets.Mods public void OnReleased(KeyBindingReleaseEvent e) { - if (mod.CanIntercept) - mod.OnReleased(e.Action); } } } From 98d8b26a9c24dee403ba9d7199d0652910c72c0b Mon Sep 17 00:00:00 2001 From: Nitrous Date: Sat, 29 Jan 2022 21:49:40 +0800 Subject: [PATCH 763/996] move `ModAlternate` to `OsuModAlternate` and check if intro has ended --- osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs | 67 ++++++++++++++-- osu.Game/Rulesets/Mods/ModAlternate.cs | 76 ------------------- 2 files changed, 61 insertions(+), 82 deletions(-) delete mode 100644 osu.Game/Rulesets/Mods/ModAlternate.cs diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs index 34802bab43..f366481c7d 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs @@ -1,31 +1,63 @@ // 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; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; +using osu.Game.Screens.Play; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModAlternate : ModAlternate + public class OsuModAlternate : Mod, IApplicableToDrawableRuleset, IApplicableToPlayer, IUpdatableByPlayfield { + public override string Name => @"Alternate"; + public override string Acronym => @"AL"; + public override string Description => @"Don't use the same key twice in a row!"; + public override double ScoreMultiplier => 1.0; + public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay) }; + public override ModType Type => ModType.DifficultyIncrease; + public override IconUsage? Icon => FontAwesome.Solid.Keyboard; + + /// + /// Whether incoming input must be checked by . + /// + public bool CanIntercept => !isBreakTime.Value && introEnded; + + private bool introEnded; + private double earliestStartTime; + private IBindable isBreakTime; private const double flash_duration = 1000; private OsuAction? lastActionPressed; private DrawableRuleset ruleset; - public override void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { ruleset = drawableRuleset; - base.ApplyToDrawableRuleset(drawableRuleset); + drawableRuleset.KeyBindingInputManager.Add(new InputInterceptor(this)); + + var firstHitObject = ruleset.Objects.FirstOrDefault(); + earliestStartTime = (firstHitObject?.StartTime ?? 0) - (firstHitObject?.HitWindows.WindowFor(HitResult.Meh) ?? 0); } - protected override void Reset() + public void ApplyToPlayer(Player player) { - lastActionPressed = null; + isBreakTime = player.IsBreakTime.GetBoundCopy(); + isBreakTime.ValueChanged += e => + { + if (e.NewValue) + lastActionPressed = null; + }; } - protected override bool OnPressed(OsuAction key) + private bool onPressed(OsuAction key) { if (lastActionPressed == key) { @@ -37,5 +69,28 @@ namespace osu.Game.Rulesets.Osu.Mods return false; } + + public void Update(Playfield playfield) + { + if (!introEnded) + introEnded = playfield.Clock.CurrentTime > earliestStartTime; + } + + private class InputInterceptor : Component, IKeyBindingHandler + { + private readonly OsuModAlternate mod; + + public InputInterceptor(OsuModAlternate mod) + { + this.mod = mod; + } + + public bool OnPressed(KeyBindingPressEvent e) + => mod.CanIntercept && mod.onPressed(e.Action); + + public void OnReleased(KeyBindingReleaseEvent e) + { + } + } } } diff --git a/osu.Game/Rulesets/Mods/ModAlternate.cs b/osu.Game/Rulesets/Mods/ModAlternate.cs deleted file mode 100644 index 17d8b92469..0000000000 --- a/osu.Game/Rulesets/Mods/ModAlternate.cs +++ /dev/null @@ -1,76 +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.Graphics; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Input.Bindings; -using osu.Framework.Input.Events; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.UI; -using osu.Game.Screens.Play; - -namespace osu.Game.Rulesets.Mods -{ - public abstract class ModAlternate : Mod - { - public override string Name => @"Alternate"; - public override string Acronym => @"AL"; - public override string Description => @"Don't use the same key twice in a row!"; - public override double ScoreMultiplier => 1.0; - public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay) }; - public override ModType Type => ModType.DifficultyIncrease; - public override IconUsage? Icon => FontAwesome.Solid.Keyboard; - } - - public abstract class ModAlternate : ModAlternate, IApplicableToDrawableRuleset, IApplicableToPlayer - where THitObject : HitObject - where TAction : struct - { - /// - /// Whether incoming input must be checked by . - /// - public bool CanIntercept => !isBreakTime.Value; - - private IBindable isBreakTime; - - public virtual void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) - { - drawableRuleset.KeyBindingInputManager.Add(new InputInterceptor(this)); - } - - public void ApplyToPlayer(Player player) - { - isBreakTime = player.IsBreakTime.GetBoundCopy(); - isBreakTime.ValueChanged += e => - { - if (e.NewValue) - Reset(); - }; - } - - protected abstract void Reset(); - - protected abstract bool OnPressed(TAction key); - - private class InputInterceptor : Component, IKeyBindingHandler - { - private readonly ModAlternate mod; - - public InputInterceptor(ModAlternate mod) - { - this.mod = mod; - } - - public bool OnPressed(KeyBindingPressEvent e) - { - return mod.CanIntercept && mod.OnPressed(e.Action); - } - - public void OnReleased(KeyBindingReleaseEvent e) - { - } - } - } -} From d48fae11004b7142a58b8d701c0cbfd073e43992 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 29 Jan 2022 23:13:19 +0900 Subject: [PATCH 764/996] Revert "Remove all EF migrations" This reverts commit bb5b9458e8130a2a5f213e968f5590d00ede3d87. --- .../20171019041408_InitialCreate.Designer.cs | 293 ++++++++++ .../20171019041408_InitialCreate.cs | 311 +++++++++++ ...025071459_AddMissingIndexRules.Designer.cs | 299 ++++++++++ .../20171025071459_AddMissingIndexRules.cs | 80 +++ ...eatmapOnlineIDUniqueConstraint.Designer.cs | 302 ++++++++++ ...5731_AddBeatmapOnlineIDUniqueConstraint.cs | 23 + ...034410_AddRulesetInfoShortName.Designer.cs | 307 +++++++++++ .../20171209034410_AddRulesetInfoShortName.cs | 33 ++ .../20180125143340_Settings.Designer.cs | 329 +++++++++++ .../Migrations/20180125143340_Settings.cs | 55 ++ .../20180131154205_AddMuteBinding.cs | 23 + .../20180219060912_AddSkins.Designer.cs | 379 +++++++++++++ .../Migrations/20180219060912_AddSkins.cs | 71 +++ ...54_RemoveUniqueHashConstraints.Designer.cs | 377 +++++++++++++ ...80529055154_RemoveUniqueHashConstraints.cs | 51 ++ ...111_UpdateTaikoDefaultBindings.Designer.cs | 376 +++++++++++++ ...180621044111_UpdateTaikoDefaultBindings.cs | 17 + ...628011956_RemoveNegativeSetIDs.Designer.cs | 376 +++++++++++++ .../20180628011956_RemoveNegativeSetIDs.cs | 18 + .../20180913080842_AddRankStatus.Designer.cs | 380 +++++++++++++ .../20180913080842_AddRankStatus.cs | 33 ++ ...0181007180454_StandardizePaths.Designer.cs | 380 +++++++++++++ .../20181007180454_StandardizePaths.cs | 26 + ...20181128100659_AddSkinInfoHash.Designer.cs | 387 +++++++++++++ .../20181128100659_AddSkinInfoHash.cs | 41 ++ ...81130113755_AddScoreInfoTables.Designer.cs | 484 ++++++++++++++++ .../20181130113755_AddScoreInfoTables.cs | 112 ++++ ...20190225062029_AddUserIDColumn.Designer.cs | 487 +++++++++++++++++ .../20190225062029_AddUserIDColumn.cs | 22 + .../20190525060824_SkinSettings.Designer.cs | 498 +++++++++++++++++ .../Migrations/20190525060824_SkinSettings.cs | 54 ++ ...AddDateAddedColumnToBeatmapSet.Designer.cs | 489 +++++++++++++++++ ...05091246_AddDateAddedColumnToBeatmapSet.cs | 24 + ...8070844_AddBPMAndLengthColumns.Designer.cs | 504 +++++++++++++++++ .../20190708070844_AddBPMAndLengthColumns.cs | 33 ++ ...20190913104727_AddBeatmapVideo.Designer.cs | 506 +++++++++++++++++ .../20190913104727_AddBeatmapVideo.cs | 22 + ...02094919_RefreshVolumeBindings.Designer.cs | 506 +++++++++++++++++ .../20200302094919_RefreshVolumeBindings.cs | 16 + ...01019224408_AddEpilepsyWarning.Designer.cs | 508 +++++++++++++++++ .../20201019224408_AddEpilepsyWarning.cs | 23 + ...700_RefreshVolumeBindingsAgain.Designer.cs | 506 +++++++++++++++++ ...210412045700_RefreshVolumeBindingsAgain.cs | 16 + ...60743_AddSkinInstantiationInfo.Designer.cs | 508 +++++++++++++++++ ...20210511060743_AddSkinInstantiationInfo.cs | 22 + ...9_AddAuthorIdToBeatmapMetadata.Designer.cs | 511 +++++++++++++++++ ...0514062639_AddAuthorIdToBeatmapMetadata.cs | 23 + ...824185035_AddCountdownSettings.Designer.cs | 513 +++++++++++++++++ .../20210824185035_AddCountdownSettings.cs | 23 + ...11_AddSamplesMatchPlaybackRate.Designer.cs | 515 ++++++++++++++++++ ...10912144011_AddSamplesMatchPlaybackRate.cs | 23 + .../20211020081609_ResetSkinHashes.cs | 23 + .../Migrations/OsuDbContextModelSnapshot.cs | 513 +++++++++++++++++ 53 files changed, 12451 insertions(+) create mode 100644 osu.Game/Migrations/20171019041408_InitialCreate.Designer.cs create mode 100644 osu.Game/Migrations/20171019041408_InitialCreate.cs create mode 100644 osu.Game/Migrations/20171025071459_AddMissingIndexRules.Designer.cs create mode 100644 osu.Game/Migrations/20171025071459_AddMissingIndexRules.cs create mode 100644 osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.Designer.cs create mode 100644 osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.cs create mode 100644 osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.Designer.cs create mode 100644 osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.cs create mode 100644 osu.Game/Migrations/20180125143340_Settings.Designer.cs create mode 100644 osu.Game/Migrations/20180125143340_Settings.cs create mode 100644 osu.Game/Migrations/20180131154205_AddMuteBinding.cs create mode 100644 osu.Game/Migrations/20180219060912_AddSkins.Designer.cs create mode 100644 osu.Game/Migrations/20180219060912_AddSkins.cs create mode 100644 osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.Designer.cs create mode 100644 osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.cs create mode 100644 osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.Designer.cs create mode 100644 osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.cs create mode 100644 osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.Designer.cs create mode 100644 osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.cs create mode 100644 osu.Game/Migrations/20180913080842_AddRankStatus.Designer.cs create mode 100644 osu.Game/Migrations/20180913080842_AddRankStatus.cs create mode 100644 osu.Game/Migrations/20181007180454_StandardizePaths.Designer.cs create mode 100644 osu.Game/Migrations/20181007180454_StandardizePaths.cs create mode 100644 osu.Game/Migrations/20181128100659_AddSkinInfoHash.Designer.cs create mode 100644 osu.Game/Migrations/20181128100659_AddSkinInfoHash.cs create mode 100644 osu.Game/Migrations/20181130113755_AddScoreInfoTables.Designer.cs create mode 100644 osu.Game/Migrations/20181130113755_AddScoreInfoTables.cs create mode 100644 osu.Game/Migrations/20190225062029_AddUserIDColumn.Designer.cs create mode 100644 osu.Game/Migrations/20190225062029_AddUserIDColumn.cs create mode 100644 osu.Game/Migrations/20190525060824_SkinSettings.Designer.cs create mode 100644 osu.Game/Migrations/20190525060824_SkinSettings.cs create mode 100644 osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.Designer.cs create mode 100644 osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.cs create mode 100644 osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.Designer.cs create mode 100644 osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.cs create mode 100644 osu.Game/Migrations/20190913104727_AddBeatmapVideo.Designer.cs create mode 100644 osu.Game/Migrations/20190913104727_AddBeatmapVideo.cs create mode 100644 osu.Game/Migrations/20200302094919_RefreshVolumeBindings.Designer.cs create mode 100644 osu.Game/Migrations/20200302094919_RefreshVolumeBindings.cs create mode 100644 osu.Game/Migrations/20201019224408_AddEpilepsyWarning.Designer.cs create mode 100644 osu.Game/Migrations/20201019224408_AddEpilepsyWarning.cs create mode 100644 osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.Designer.cs create mode 100644 osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.cs create mode 100644 osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.Designer.cs create mode 100644 osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.cs create mode 100644 osu.Game/Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.Designer.cs create mode 100644 osu.Game/Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.cs create mode 100644 osu.Game/Migrations/20210824185035_AddCountdownSettings.Designer.cs create mode 100644 osu.Game/Migrations/20210824185035_AddCountdownSettings.cs create mode 100644 osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.Designer.cs create mode 100644 osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.cs create mode 100644 osu.Game/Migrations/20211020081609_ResetSkinHashes.cs create mode 100644 osu.Game/Migrations/OsuDbContextModelSnapshot.cs diff --git a/osu.Game/Migrations/20171019041408_InitialCreate.Designer.cs b/osu.Game/Migrations/20171019041408_InitialCreate.Designer.cs new file mode 100644 index 0000000000..c751530bf4 --- /dev/null +++ b/osu.Game/Migrations/20171019041408_InitialCreate.Designer.cs @@ -0,0 +1,293 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; +using osu.Game.Database; +using System; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20171019041408_InitialCreate")] + partial class InitialCreate + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.0.0-rtm-26452"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash"); + + b.HasIndex("MD5Hash"); + + b.HasIndex("MetadataID"); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash"); + + b.HasIndex("MetadataID"); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20171019041408_InitialCreate.cs b/osu.Game/Migrations/20171019041408_InitialCreate.cs new file mode 100644 index 0000000000..9b6881f98c --- /dev/null +++ b/osu.Game/Migrations/20171019041408_InitialCreate.cs @@ -0,0 +1,311 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class InitialCreate : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "BeatmapDifficulty", + columns: table => new + { + ID = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + ApproachRate = table.Column(type: "REAL", nullable: false), + CircleSize = table.Column(type: "REAL", nullable: false), + DrainRate = table.Column(type: "REAL", nullable: false), + OverallDifficulty = table.Column(type: "REAL", nullable: false), + SliderMultiplier = table.Column(type: "REAL", nullable: false), + SliderTickRate = table.Column(type: "REAL", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BeatmapDifficulty", x => x.ID); + }); + + migrationBuilder.CreateTable( + name: "BeatmapMetadata", + columns: table => new + { + ID = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Artist = table.Column(type: "TEXT", nullable: true), + ArtistUnicode = table.Column(type: "TEXT", nullable: true), + AudioFile = table.Column(type: "TEXT", nullable: true), + Author = table.Column(type: "TEXT", nullable: true), + BackgroundFile = table.Column(type: "TEXT", nullable: true), + PreviewTime = table.Column(type: "INTEGER", nullable: false), + Source = table.Column(type: "TEXT", nullable: true), + Tags = table.Column(type: "TEXT", nullable: true), + Title = table.Column(type: "TEXT", nullable: true), + TitleUnicode = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_BeatmapMetadata", x => x.ID); + }); + + migrationBuilder.CreateTable( + name: "FileInfo", + columns: table => new + { + ID = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Hash = table.Column(type: "TEXT", nullable: true), + ReferenceCount = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_FileInfo", x => x.ID); + }); + + migrationBuilder.CreateTable( + name: "KeyBinding", + columns: table => new + { + ID = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Action = table.Column(type: "INTEGER", nullable: false), + Keys = table.Column(type: "TEXT", nullable: true), + RulesetID = table.Column(type: "INTEGER", nullable: true), + Variant = table.Column(type: "INTEGER", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_KeyBinding", x => x.ID); + }); + + migrationBuilder.CreateTable( + name: "RulesetInfo", + columns: table => new + { + ID = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Available = table.Column(type: "INTEGER", nullable: false), + InstantiationInfo = table.Column(type: "TEXT", nullable: true), + Name = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_RulesetInfo", x => x.ID); + }); + + migrationBuilder.CreateTable( + name: "BeatmapSetInfo", + columns: table => new + { + ID = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + DeletePending = table.Column(type: "INTEGER", nullable: false), + Hash = table.Column(type: "TEXT", nullable: true), + MetadataID = table.Column(type: "INTEGER", nullable: true), + OnlineBeatmapSetID = table.Column(type: "INTEGER", nullable: true), + Protected = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BeatmapSetInfo", x => x.ID); + table.ForeignKey( + name: "FK_BeatmapSetInfo_BeatmapMetadata_MetadataID", + column: x => x.MetadataID, + principalTable: "BeatmapMetadata", + principalColumn: "ID", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "BeatmapInfo", + columns: table => new + { + ID = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + AudioLeadIn = table.Column(type: "INTEGER", nullable: false), + BaseDifficultyID = table.Column(type: "INTEGER", nullable: false), + BeatDivisor = table.Column(type: "INTEGER", nullable: false), + BeatmapSetInfoID = table.Column(type: "INTEGER", nullable: false), + Countdown = table.Column(type: "INTEGER", nullable: false), + DistanceSpacing = table.Column(type: "REAL", nullable: false), + GridSize = table.Column(type: "INTEGER", nullable: false), + Hash = table.Column(type: "TEXT", nullable: true), + Hidden = table.Column(type: "INTEGER", nullable: false), + LetterboxInBreaks = table.Column(type: "INTEGER", nullable: false), + MD5Hash = table.Column(type: "TEXT", nullable: true), + MetadataID = table.Column(type: "INTEGER", nullable: true), + OnlineBeatmapID = table.Column(type: "INTEGER", nullable: true), + Path = table.Column(type: "TEXT", nullable: true), + RulesetID = table.Column(type: "INTEGER", nullable: false), + SpecialStyle = table.Column(type: "INTEGER", nullable: false), + StackLeniency = table.Column(type: "REAL", nullable: false), + StarDifficulty = table.Column(type: "REAL", nullable: false), + StoredBookmarks = table.Column(type: "TEXT", nullable: true), + TimelineZoom = table.Column(type: "REAL", nullable: false), + Version = table.Column(type: "TEXT", nullable: true), + WidescreenStoryboard = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BeatmapInfo", x => x.ID); + table.ForeignKey( + name: "FK_BeatmapInfo_BeatmapDifficulty_BaseDifficultyID", + column: x => x.BaseDifficultyID, + principalTable: "BeatmapDifficulty", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_BeatmapInfo_BeatmapSetInfo_BeatmapSetInfoID", + column: x => x.BeatmapSetInfoID, + principalTable: "BeatmapSetInfo", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_BeatmapInfo_BeatmapMetadata_MetadataID", + column: x => x.MetadataID, + principalTable: "BeatmapMetadata", + principalColumn: "ID", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_BeatmapInfo_RulesetInfo_RulesetID", + column: x => x.RulesetID, + principalTable: "RulesetInfo", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "BeatmapSetFileInfo", + columns: table => new + { + ID = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + BeatmapSetInfoID = table.Column(type: "INTEGER", nullable: false), + FileInfoID = table.Column(type: "INTEGER", nullable: false), + Filename = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BeatmapSetFileInfo", x => x.ID); + table.ForeignKey( + name: "FK_BeatmapSetFileInfo_BeatmapSetInfo_BeatmapSetInfoID", + column: x => x.BeatmapSetInfoID, + principalTable: "BeatmapSetInfo", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_BeatmapSetFileInfo_FileInfo_FileInfoID", + column: x => x.FileInfoID, + principalTable: "FileInfo", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapInfo_BaseDifficultyID", + table: "BeatmapInfo", + column: "BaseDifficultyID"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapInfo_BeatmapSetInfoID", + table: "BeatmapInfo", + column: "BeatmapSetInfoID"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapInfo_Hash", + table: "BeatmapInfo", + column: "Hash"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapInfo_MD5Hash", + table: "BeatmapInfo", + column: "MD5Hash"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapInfo_MetadataID", + table: "BeatmapInfo", + column: "MetadataID"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapInfo_RulesetID", + table: "BeatmapInfo", + column: "RulesetID"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapSetFileInfo_BeatmapSetInfoID", + table: "BeatmapSetFileInfo", + column: "BeatmapSetInfoID"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapSetFileInfo_FileInfoID", + table: "BeatmapSetFileInfo", + column: "FileInfoID"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapSetInfo_DeletePending", + table: "BeatmapSetInfo", + column: "DeletePending"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapSetInfo_Hash", + table: "BeatmapSetInfo", + column: "Hash"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapSetInfo_MetadataID", + table: "BeatmapSetInfo", + column: "MetadataID"); + + migrationBuilder.CreateIndex( + name: "IX_FileInfo_Hash", + table: "FileInfo", + column: "Hash", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_FileInfo_ReferenceCount", + table: "FileInfo", + column: "ReferenceCount"); + + migrationBuilder.CreateIndex( + name: "IX_KeyBinding_Action", + table: "KeyBinding", + column: "Action"); + + migrationBuilder.CreateIndex( + name: "IX_KeyBinding_Variant", + table: "KeyBinding", + column: "Variant"); + + migrationBuilder.CreateIndex( + name: "IX_RulesetInfo_Available", + table: "RulesetInfo", + column: "Available"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "BeatmapInfo"); + + migrationBuilder.DropTable( + name: "BeatmapSetFileInfo"); + + migrationBuilder.DropTable( + name: "KeyBinding"); + + migrationBuilder.DropTable( + name: "BeatmapDifficulty"); + + migrationBuilder.DropTable( + name: "RulesetInfo"); + + migrationBuilder.DropTable( + name: "BeatmapSetInfo"); + + migrationBuilder.DropTable( + name: "FileInfo"); + + migrationBuilder.DropTable( + name: "BeatmapMetadata"); + } + } +} diff --git a/osu.Game/Migrations/20171025071459_AddMissingIndexRules.Designer.cs b/osu.Game/Migrations/20171025071459_AddMissingIndexRules.Designer.cs new file mode 100644 index 0000000000..4cd234f2ef --- /dev/null +++ b/osu.Game/Migrations/20171025071459_AddMissingIndexRules.Designer.cs @@ -0,0 +1,299 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; +using osu.Game.Database; +using System; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20171025071459_AddMissingIndexRules")] + partial class AddMissingIndexRules + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.0.0-rtm-26452"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MD5Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20171025071459_AddMissingIndexRules.cs b/osu.Game/Migrations/20171025071459_AddMissingIndexRules.cs new file mode 100644 index 0000000000..c9fc59c5a2 --- /dev/null +++ b/osu.Game/Migrations/20171025071459_AddMissingIndexRules.cs @@ -0,0 +1,80 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddMissingIndexRules : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_BeatmapSetInfo_Hash", + table: "BeatmapSetInfo"); + + migrationBuilder.DropIndex( + name: "IX_BeatmapInfo_Hash", + table: "BeatmapInfo"); + + migrationBuilder.DropIndex( + name: "IX_BeatmapInfo_MD5Hash", + table: "BeatmapInfo"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapSetInfo_Hash", + table: "BeatmapSetInfo", + column: "Hash", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapSetInfo_OnlineBeatmapSetID", + table: "BeatmapSetInfo", + column: "OnlineBeatmapSetID", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapInfo_Hash", + table: "BeatmapInfo", + column: "Hash", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapInfo_MD5Hash", + table: "BeatmapInfo", + column: "MD5Hash", + unique: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_BeatmapSetInfo_Hash", + table: "BeatmapSetInfo"); + + migrationBuilder.DropIndex( + name: "IX_BeatmapSetInfo_OnlineBeatmapSetID", + table: "BeatmapSetInfo"); + + migrationBuilder.DropIndex( + name: "IX_BeatmapInfo_Hash", + table: "BeatmapInfo"); + + migrationBuilder.DropIndex( + name: "IX_BeatmapInfo_MD5Hash", + table: "BeatmapInfo"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapSetInfo_Hash", + table: "BeatmapSetInfo", + column: "Hash"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapInfo_Hash", + table: "BeatmapInfo", + column: "Hash"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapInfo_MD5Hash", + table: "BeatmapInfo", + column: "MD5Hash"); + } + } +} diff --git a/osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.Designer.cs b/osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.Designer.cs new file mode 100644 index 0000000000..006acf12cd --- /dev/null +++ b/osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.Designer.cs @@ -0,0 +1,302 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; +using osu.Game.Database; +using System; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20171119065731_AddBeatmapOnlineIDUniqueConstraint")] + partial class AddBeatmapOnlineIDUniqueConstraint + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.0.0-rtm-26452"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MD5Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.cs b/osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.cs new file mode 100644 index 0000000000..084ae67940 --- /dev/null +++ b/osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.cs @@ -0,0 +1,23 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddBeatmapOnlineIDUniqueConstraint : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateIndex( + name: "IX_BeatmapInfo_OnlineBeatmapID", + table: "BeatmapInfo", + column: "OnlineBeatmapID", + unique: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_BeatmapInfo_OnlineBeatmapID", + table: "BeatmapInfo"); + } + } +} diff --git a/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.Designer.cs b/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.Designer.cs new file mode 100644 index 0000000000..fc2496bc24 --- /dev/null +++ b/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.Designer.cs @@ -0,0 +1,307 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; +using osu.Game.Database; +using System; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20171209034410_AddRulesetInfoShortName")] + partial class AddRulesetInfoShortName + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.0.0-rtm-26452"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MD5Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.cs b/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.cs new file mode 100644 index 0000000000..09cf0af89c --- /dev/null +++ b/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.cs @@ -0,0 +1,33 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddRulesetInfoShortName : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "ShortName", + table: "RulesetInfo", + type: "TEXT", + nullable: true); + + migrationBuilder.CreateIndex( + name: "IX_RulesetInfo_ShortName", + table: "RulesetInfo", + column: "ShortName", + unique: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_RulesetInfo_ShortName", + table: "RulesetInfo"); + + migrationBuilder.DropColumn( + name: "ShortName", + table: "RulesetInfo"); + } + } +} diff --git a/osu.Game/Migrations/20180125143340_Settings.Designer.cs b/osu.Game/Migrations/20180125143340_Settings.Designer.cs new file mode 100644 index 0000000000..4bb599eec1 --- /dev/null +++ b/osu.Game/Migrations/20180125143340_Settings.Designer.cs @@ -0,0 +1,329 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; +using osu.Game.Database; +using System; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20180125143340_Settings")] + partial class Settings + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.0.0-rtm-26452"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MD5Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntKey") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20180125143340_Settings.cs b/osu.Game/Migrations/20180125143340_Settings.cs new file mode 100644 index 0000000000..166d3c086d --- /dev/null +++ b/osu.Game/Migrations/20180125143340_Settings.cs @@ -0,0 +1,55 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class Settings : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_KeyBinding_Variant", + table: "KeyBinding"); + + migrationBuilder.CreateTable( + name: "Settings", + columns: table => new + { + ID = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Key = table.Column(type: "TEXT", nullable: false), + RulesetID = table.Column(type: "INTEGER", nullable: true), + Value = table.Column(type: "TEXT", nullable: true), + Variant = table.Column(type: "INTEGER", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Settings", x => x.ID); + }); + + migrationBuilder.CreateIndex( + name: "IX_KeyBinding_RulesetID_Variant", + table: "KeyBinding", + columns: new[] { "RulesetID", "Variant" }); + + migrationBuilder.CreateIndex( + name: "IX_Settings_RulesetID_Variant", + table: "Settings", + columns: new[] { "RulesetID", "Variant" }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Settings"); + + migrationBuilder.DropIndex( + name: "IX_KeyBinding_RulesetID_Variant", + table: "KeyBinding"); + + migrationBuilder.CreateIndex( + name: "IX_KeyBinding_Variant", + table: "KeyBinding", + column: "Variant"); + } + } +} diff --git a/osu.Game/Migrations/20180131154205_AddMuteBinding.cs b/osu.Game/Migrations/20180131154205_AddMuteBinding.cs new file mode 100644 index 0000000000..5564a30bbf --- /dev/null +++ b/osu.Game/Migrations/20180131154205_AddMuteBinding.cs @@ -0,0 +1,23 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Infrastructure; +using osu.Game.Database; +using osu.Game.Input.Bindings; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20180131154205_AddMuteBinding")] + public partial class AddMuteBinding : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql($"UPDATE KeyBinding SET Action = Action + 1 WHERE RulesetID IS NULL AND Variant IS NULL AND Action >= {(int)GlobalAction.ToggleMute}"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql($"DELETE FROM KeyBinding WHERE RulesetID IS NULL AND Variant IS NULL AND Action = {(int)GlobalAction.ToggleMute}"); + migrationBuilder.Sql($"UPDATE KeyBinding SET Action = Action - 1 WHERE RulesetID IS NULL AND Variant IS NULL AND Action > {(int)GlobalAction.ToggleMute}"); + } + } +} diff --git a/osu.Game/Migrations/20180219060912_AddSkins.Designer.cs b/osu.Game/Migrations/20180219060912_AddSkins.Designer.cs new file mode 100644 index 0000000000..cdc4ef2e66 --- /dev/null +++ b/osu.Game/Migrations/20180219060912_AddSkins.Designer.cs @@ -0,0 +1,379 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; +using osu.Game.Database; +using System; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20180219060912_AddSkins")] + partial class AddSkins + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.0.0-rtm-26452"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MD5Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntKey") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("SkinInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("SkinInfoID"); + + b.ToTable("SkinFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Creator"); + + b.Property("DeletePending"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.ToTable("SkinInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Files") + .HasForeignKey("SkinInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20180219060912_AddSkins.cs b/osu.Game/Migrations/20180219060912_AddSkins.cs new file mode 100644 index 0000000000..a0270ab0fd --- /dev/null +++ b/osu.Game/Migrations/20180219060912_AddSkins.cs @@ -0,0 +1,71 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddSkins : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "SkinInfo", + columns: table => new + { + ID = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Creator = table.Column(type: "TEXT", nullable: true), + DeletePending = table.Column(type: "INTEGER", nullable: false), + Name = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_SkinInfo", x => x.ID); + }); + + migrationBuilder.CreateTable( + name: "SkinFileInfo", + columns: table => new + { + ID = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + FileInfoID = table.Column(type: "INTEGER", nullable: false), + Filename = table.Column(type: "TEXT", nullable: false), + SkinInfoID = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SkinFileInfo", x => x.ID); + table.ForeignKey( + name: "FK_SkinFileInfo_FileInfo_FileInfoID", + column: x => x.FileInfoID, + principalTable: "FileInfo", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_SkinFileInfo_SkinInfo_SkinInfoID", + column: x => x.SkinInfoID, + principalTable: "SkinInfo", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_SkinFileInfo_FileInfoID", + table: "SkinFileInfo", + column: "FileInfoID"); + + migrationBuilder.CreateIndex( + name: "IX_SkinFileInfo_SkinInfoID", + table: "SkinFileInfo", + column: "SkinInfoID"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "SkinFileInfo"); + + migrationBuilder.DropTable( + name: "SkinInfo"); + } + } +} diff --git a/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.Designer.cs b/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.Designer.cs new file mode 100644 index 0000000000..f28408bfb3 --- /dev/null +++ b/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.Designer.cs @@ -0,0 +1,377 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; +using osu.Game.Database; +using System; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20180529055154_RemoveUniqueHashConstraints")] + partial class RemoveUniqueHashConstraints + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.0.3-rtm-10026"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash"); + + b.HasIndex("MD5Hash"); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntKey") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("SkinInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("SkinInfoID"); + + b.ToTable("SkinFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Creator"); + + b.Property("DeletePending"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.ToTable("SkinInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Files") + .HasForeignKey("SkinInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.cs b/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.cs new file mode 100644 index 0000000000..27269cc5fc --- /dev/null +++ b/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.cs @@ -0,0 +1,51 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class RemoveUniqueHashConstraints : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_BeatmapInfo_Hash", + table: "BeatmapInfo"); + + migrationBuilder.DropIndex( + name: "IX_BeatmapInfo_MD5Hash", + table: "BeatmapInfo"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapInfo_Hash", + table: "BeatmapInfo", + column: "Hash"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapInfo_MD5Hash", + table: "BeatmapInfo", + column: "MD5Hash"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_BeatmapInfo_Hash", + table: "BeatmapInfo"); + + migrationBuilder.DropIndex( + name: "IX_BeatmapInfo_MD5Hash", + table: "BeatmapInfo"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapInfo_Hash", + table: "BeatmapInfo", + column: "Hash", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapInfo_MD5Hash", + table: "BeatmapInfo", + column: "MD5Hash", + unique: true); + } + } +} diff --git a/osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.Designer.cs b/osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.Designer.cs new file mode 100644 index 0000000000..aaa11e88b6 --- /dev/null +++ b/osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.Designer.cs @@ -0,0 +1,376 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20180621044111_UpdateTaikoDefaultBindings")] + partial class UpdateTaikoDefaultBindings + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.1-rtm-30846"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash"); + + b.HasIndex("MD5Hash"); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntKey") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("SkinInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("SkinInfoID"); + + b.ToTable("SkinFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Creator"); + + b.Property("DeletePending"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.ToTable("SkinInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Files") + .HasForeignKey("SkinInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.cs b/osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.cs new file mode 100644 index 0000000000..71304ea979 --- /dev/null +++ b/osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.cs @@ -0,0 +1,17 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class UpdateTaikoDefaultBindings : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql("DELETE FROM KeyBinding WHERE RulesetID = 1"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + // we can't really tell if these should be restored or not, so let's just not do so. + } + } +} diff --git a/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.Designer.cs b/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.Designer.cs new file mode 100644 index 0000000000..7eeacd56d7 --- /dev/null +++ b/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.Designer.cs @@ -0,0 +1,376 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20180628011956_RemoveNegativeSetIDs")] + partial class RemoveNegativeSetIDs + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.1-rtm-30846"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash"); + + b.HasIndex("MD5Hash"); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntKey") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("SkinInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("SkinInfoID"); + + b.ToTable("SkinFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Creator"); + + b.Property("DeletePending"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.ToTable("SkinInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Files") + .HasForeignKey("SkinInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.cs b/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.cs new file mode 100644 index 0000000000..506d65f761 --- /dev/null +++ b/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.cs @@ -0,0 +1,18 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class RemoveNegativeSetIDs : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + // There was a change that beatmaps were being loaded with "-1" online IDs, which is completely incorrect. + // This ensures there will not be unique key conflicts as a result of these incorrectly imported beatmaps. + migrationBuilder.Sql("UPDATE BeatmapSetInfo SET OnlineBeatmapSetID = null WHERE OnlineBeatmapSetID <= 0"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + } + } +} diff --git a/osu.Game/Migrations/20180913080842_AddRankStatus.Designer.cs b/osu.Game/Migrations/20180913080842_AddRankStatus.Designer.cs new file mode 100644 index 0000000000..5ab43da046 --- /dev/null +++ b/osu.Game/Migrations/20180913080842_AddRankStatus.Designer.cs @@ -0,0 +1,380 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20180913080842_AddRankStatus")] + partial class AddRankStatus + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.2-rtm-30932"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("Status"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash"); + + b.HasIndex("MD5Hash"); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.Property("Status"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntKey") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("SkinInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("SkinInfoID"); + + b.ToTable("SkinFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Creator"); + + b.Property("DeletePending"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.ToTable("SkinInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Files") + .HasForeignKey("SkinInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20180913080842_AddRankStatus.cs b/osu.Game/Migrations/20180913080842_AddRankStatus.cs new file mode 100644 index 0000000000..bba4944bb7 --- /dev/null +++ b/osu.Game/Migrations/20180913080842_AddRankStatus.cs @@ -0,0 +1,33 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddRankStatus : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Status", + table: "BeatmapSetInfo", + nullable: false, + defaultValue: -3); // NONE + + migrationBuilder.AddColumn( + name: "Status", + table: "BeatmapInfo", + nullable: false, + defaultValue: -3); // NONE + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Status", + table: "BeatmapSetInfo"); + + migrationBuilder.DropColumn( + name: "Status", + table: "BeatmapInfo"); + } + } +} diff --git a/osu.Game/Migrations/20181007180454_StandardizePaths.Designer.cs b/osu.Game/Migrations/20181007180454_StandardizePaths.Designer.cs new file mode 100644 index 0000000000..b387a45ecf --- /dev/null +++ b/osu.Game/Migrations/20181007180454_StandardizePaths.Designer.cs @@ -0,0 +1,380 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20181007180454_StandardizePaths")] + partial class StandardizePaths + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.3-rtm-32065"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("Status"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash"); + + b.HasIndex("MD5Hash"); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.Property("Status"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntKey") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("SkinInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("SkinInfoID"); + + b.ToTable("SkinFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Creator"); + + b.Property("DeletePending"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.ToTable("SkinInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Files") + .HasForeignKey("SkinInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20181007180454_StandardizePaths.cs b/osu.Game/Migrations/20181007180454_StandardizePaths.cs new file mode 100644 index 0000000000..274b8030a9 --- /dev/null +++ b/osu.Game/Migrations/20181007180454_StandardizePaths.cs @@ -0,0 +1,26 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using System.IO; + +namespace osu.Game.Migrations +{ + public partial class StandardizePaths : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + string windowsStyle = @"\"; + string standardized = "/"; + + // Escaping \ does not seem to be needed. + migrationBuilder.Sql($"UPDATE `BeatmapInfo` SET `Path` = REPLACE(`Path`, '{windowsStyle}', '{standardized}')"); + migrationBuilder.Sql($"UPDATE `BeatmapMetadata` SET `AudioFile` = REPLACE(`AudioFile`, '{windowsStyle}', '{standardized}')"); + migrationBuilder.Sql($"UPDATE `BeatmapMetadata` SET `BackgroundFile` = REPLACE(`BackgroundFile`, '{windowsStyle}', '{standardized}')"); + migrationBuilder.Sql($"UPDATE `BeatmapSetFileInfo` SET `Filename` = REPLACE(`Filename`, '{windowsStyle}', '{standardized}')"); + migrationBuilder.Sql($"UPDATE `SkinFileInfo` SET `Filename` = REPLACE(`Filename`, '{windowsStyle}', '{standardized}')"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + } + } +} diff --git a/osu.Game/Migrations/20181128100659_AddSkinInfoHash.Designer.cs b/osu.Game/Migrations/20181128100659_AddSkinInfoHash.Designer.cs new file mode 100644 index 0000000000..120674671a --- /dev/null +++ b/osu.Game/Migrations/20181128100659_AddSkinInfoHash.Designer.cs @@ -0,0 +1,387 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20181128100659_AddSkinInfoHash")] + partial class AddSkinInfoHash + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.4-rtm-31024"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("Status"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash"); + + b.HasIndex("MD5Hash"); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.Property("Status"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntKey") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("SkinInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("SkinInfoID"); + + b.ToTable("SkinFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Creator"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.ToTable("SkinInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Files") + .HasForeignKey("SkinInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20181128100659_AddSkinInfoHash.cs b/osu.Game/Migrations/20181128100659_AddSkinInfoHash.cs new file mode 100644 index 0000000000..860264a7dd --- /dev/null +++ b/osu.Game/Migrations/20181128100659_AddSkinInfoHash.cs @@ -0,0 +1,41 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddSkinInfoHash : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Hash", + table: "SkinInfo", + nullable: true); + + migrationBuilder.CreateIndex( + name: "IX_SkinInfo_DeletePending", + table: "SkinInfo", + column: "DeletePending"); + + migrationBuilder.CreateIndex( + name: "IX_SkinInfo_Hash", + table: "SkinInfo", + column: "Hash", + unique: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_SkinInfo_DeletePending", + table: "SkinInfo"); + + migrationBuilder.DropIndex( + name: "IX_SkinInfo_Hash", + table: "SkinInfo"); + + migrationBuilder.DropColumn( + name: "Hash", + table: "SkinInfo"); + } + } +} diff --git a/osu.Game/Migrations/20181130113755_AddScoreInfoTables.Designer.cs b/osu.Game/Migrations/20181130113755_AddScoreInfoTables.Designer.cs new file mode 100644 index 0000000000..eee53182ce --- /dev/null +++ b/osu.Game/Migrations/20181130113755_AddScoreInfoTables.Designer.cs @@ -0,0 +1,484 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20181130113755_AddScoreInfoTables")] + partial class AddScoreInfoTables + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.4-rtm-31024"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("Status"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash"); + + b.HasIndex("MD5Hash"); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.Property("Status"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntKey") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("ScoreInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("ScoreInfoID"); + + b.ToTable("ScoreFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Accuracy") + .HasColumnType("DECIMAL(1,4)"); + + b.Property("BeatmapInfoID"); + + b.Property("Combo"); + + b.Property("Date"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MaxCombo"); + + b.Property("ModsJson") + .HasColumnName("Mods"); + + b.Property("OnlineScoreID"); + + b.Property("PP"); + + b.Property("Rank"); + + b.Property("RulesetID"); + + b.Property("StatisticsJson") + .HasColumnName("Statistics"); + + b.Property("TotalScore"); + + b.Property("UserString") + .HasColumnName("User"); + + b.HasKey("ID"); + + b.HasIndex("BeatmapInfoID"); + + b.HasIndex("OnlineScoreID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("ScoreInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("SkinInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("SkinInfoID"); + + b.ToTable("SkinFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Creator"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.ToTable("SkinInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Scoring.ScoreInfo") + .WithMany("Files") + .HasForeignKey("ScoreInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") + .WithMany() + .HasForeignKey("BeatmapInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Files") + .HasForeignKey("SkinInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20181130113755_AddScoreInfoTables.cs b/osu.Game/Migrations/20181130113755_AddScoreInfoTables.cs new file mode 100644 index 0000000000..2b6f94c5a4 --- /dev/null +++ b/osu.Game/Migrations/20181130113755_AddScoreInfoTables.cs @@ -0,0 +1,112 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddScoreInfoTables : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "ScoreInfo", + columns: table => new + { + ID = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Rank = table.Column(nullable: false), + TotalScore = table.Column(nullable: false), + Accuracy = table.Column(type: "DECIMAL(1,4)", nullable: false), + PP = table.Column(nullable: true), + MaxCombo = table.Column(nullable: false), + Combo = table.Column(nullable: false), + RulesetID = table.Column(nullable: false), + Mods = table.Column(nullable: true), + User = table.Column(nullable: true), + BeatmapInfoID = table.Column(nullable: false), + OnlineScoreID = table.Column(nullable: true), + Date = table.Column(nullable: false), + Statistics = table.Column(nullable: true), + Hash = table.Column(nullable: true), + DeletePending = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ScoreInfo", x => x.ID); + table.ForeignKey( + name: "FK_ScoreInfo_BeatmapInfo_BeatmapInfoID", + column: x => x.BeatmapInfoID, + principalTable: "BeatmapInfo", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_ScoreInfo_RulesetInfo_RulesetID", + column: x => x.RulesetID, + principalTable: "RulesetInfo", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ScoreFileInfo", + columns: table => new + { + ID = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + FileInfoID = table.Column(nullable: false), + Filename = table.Column(nullable: false), + ScoreInfoID = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_ScoreFileInfo", x => x.ID); + table.ForeignKey( + name: "FK_ScoreFileInfo_FileInfo_FileInfoID", + column: x => x.FileInfoID, + principalTable: "FileInfo", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_ScoreFileInfo_ScoreInfo_ScoreInfoID", + column: x => x.ScoreInfoID, + principalTable: "ScoreInfo", + principalColumn: "ID", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_ScoreFileInfo_FileInfoID", + table: "ScoreFileInfo", + column: "FileInfoID"); + + migrationBuilder.CreateIndex( + name: "IX_ScoreFileInfo_ScoreInfoID", + table: "ScoreFileInfo", + column: "ScoreInfoID"); + + migrationBuilder.CreateIndex( + name: "IX_ScoreInfo_BeatmapInfoID", + table: "ScoreInfo", + column: "BeatmapInfoID"); + + migrationBuilder.CreateIndex( + name: "IX_ScoreInfo_OnlineScoreID", + table: "ScoreInfo", + column: "OnlineScoreID", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_ScoreInfo_RulesetID", + table: "ScoreInfo", + column: "RulesetID"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "ScoreFileInfo"); + + migrationBuilder.DropTable( + name: "ScoreInfo"); + } + } +} diff --git a/osu.Game/Migrations/20190225062029_AddUserIDColumn.Designer.cs b/osu.Game/Migrations/20190225062029_AddUserIDColumn.Designer.cs new file mode 100644 index 0000000000..8e1e3a59f3 --- /dev/null +++ b/osu.Game/Migrations/20190225062029_AddUserIDColumn.Designer.cs @@ -0,0 +1,487 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20190225062029_AddUserIDColumn")] + partial class AddUserIDColumn + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.1-servicing-10028"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("Status"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash"); + + b.HasIndex("MD5Hash"); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.Property("Status"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntKey") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("ScoreInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("ScoreInfoID"); + + b.ToTable("ScoreFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Accuracy") + .HasColumnType("DECIMAL(1,4)"); + + b.Property("BeatmapInfoID"); + + b.Property("Combo"); + + b.Property("Date"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MaxCombo"); + + b.Property("ModsJson") + .HasColumnName("Mods"); + + b.Property("OnlineScoreID"); + + b.Property("PP"); + + b.Property("Rank"); + + b.Property("RulesetID"); + + b.Property("StatisticsJson") + .HasColumnName("Statistics"); + + b.Property("TotalScore"); + + b.Property("UserID") + .HasColumnName("UserID"); + + b.Property("UserString") + .HasColumnName("User"); + + b.HasKey("ID"); + + b.HasIndex("BeatmapInfoID"); + + b.HasIndex("OnlineScoreID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("ScoreInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("SkinInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("SkinInfoID"); + + b.ToTable("SkinFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Creator"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.ToTable("SkinInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Scoring.ScoreInfo") + .WithMany("Files") + .HasForeignKey("ScoreInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") + .WithMany("Scores") + .HasForeignKey("BeatmapInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Files") + .HasForeignKey("SkinInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20190225062029_AddUserIDColumn.cs b/osu.Game/Migrations/20190225062029_AddUserIDColumn.cs new file mode 100644 index 0000000000..0720e0eac7 --- /dev/null +++ b/osu.Game/Migrations/20190225062029_AddUserIDColumn.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddUserIDColumn : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "UserID", + table: "ScoreInfo", + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "UserID", + table: "ScoreInfo"); + } + } +} diff --git a/osu.Game/Migrations/20190525060824_SkinSettings.Designer.cs b/osu.Game/Migrations/20190525060824_SkinSettings.Designer.cs new file mode 100644 index 0000000000..348c42adb9 --- /dev/null +++ b/osu.Game/Migrations/20190525060824_SkinSettings.Designer.cs @@ -0,0 +1,498 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20190525060824_SkinSettings")] + partial class SkinSettings + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.4-servicing-10062"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("Status"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash"); + + b.HasIndex("MD5Hash"); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.Property("Status"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Key") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("SkinInfoID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("SkinInfoID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("ScoreInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("ScoreInfoID"); + + b.ToTable("ScoreFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Accuracy") + .HasColumnType("DECIMAL(1,4)"); + + b.Property("BeatmapInfoID"); + + b.Property("Combo"); + + b.Property("Date"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MaxCombo"); + + b.Property("ModsJson") + .HasColumnName("Mods"); + + b.Property("OnlineScoreID"); + + b.Property("PP"); + + b.Property("Rank"); + + b.Property("RulesetID"); + + b.Property("StatisticsJson") + .HasColumnName("Statistics"); + + b.Property("TotalScore"); + + b.Property("UserID") + .HasColumnName("UserID"); + + b.Property("UserString") + .HasColumnName("User"); + + b.HasKey("ID"); + + b.HasIndex("BeatmapInfoID"); + + b.HasIndex("OnlineScoreID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("ScoreInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("SkinInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("SkinInfoID"); + + b.ToTable("SkinFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Creator"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.ToTable("SkinInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Settings") + .HasForeignKey("SkinInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Scoring.ScoreInfo") + .WithMany("Files") + .HasForeignKey("ScoreInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") + .WithMany("Scores") + .HasForeignKey("BeatmapInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Files") + .HasForeignKey("SkinInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20190525060824_SkinSettings.cs b/osu.Game/Migrations/20190525060824_SkinSettings.cs new file mode 100644 index 0000000000..99237419b7 --- /dev/null +++ b/osu.Game/Migrations/20190525060824_SkinSettings.cs @@ -0,0 +1,54 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class SkinSettings : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql(@"create table Settings_dg_tmp + ( + ID INTEGER not null + constraint PK_Settings + primary key autoincrement, + Key TEXT not null, + RulesetID INTEGER, + Value TEXT, + Variant INTEGER, + SkinInfoID int + constraint Settings_SkinInfo_ID_fk + references SkinInfo + on delete restrict + ); + + insert into Settings_dg_tmp(ID, Key, RulesetID, Value, Variant) select ID, Key, RulesetID, Value, Variant from Settings; + + drop table Settings; + + alter table Settings_dg_tmp rename to Settings; + + create index IX_Settings_RulesetID_Variant + on Settings (RulesetID, Variant); + + create index Settings_SkinInfoID_index + on Settings (SkinInfoID); + + "); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Settings_SkinInfo_SkinInfoID", + table: "Settings"); + + migrationBuilder.DropIndex( + name: "IX_Settings_SkinInfoID", + table: "Settings"); + + migrationBuilder.DropColumn( + name: "SkinInfoID", + table: "Settings"); + } + } +} diff --git a/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.Designer.cs b/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.Designer.cs new file mode 100644 index 0000000000..9477369aa0 --- /dev/null +++ b/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.Designer.cs @@ -0,0 +1,489 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20190605091246_AddDateAddedColumnToBeatmapSet")] + partial class AddDateAddedColumnToBeatmapSet + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.4-servicing-10062"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("Status"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash"); + + b.HasIndex("MD5Hash"); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.Property("Status"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Key") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("ScoreInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("ScoreInfoID"); + + b.ToTable("ScoreFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Accuracy") + .HasColumnType("DECIMAL(1,4)"); + + b.Property("BeatmapInfoID"); + + b.Property("Combo"); + + b.Property("Date"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MaxCombo"); + + b.Property("ModsJson") + .HasColumnName("Mods"); + + b.Property("OnlineScoreID"); + + b.Property("PP"); + + b.Property("Rank"); + + b.Property("RulesetID"); + + b.Property("StatisticsJson") + .HasColumnName("Statistics"); + + b.Property("TotalScore"); + + b.Property("UserID") + .HasColumnName("UserID"); + + b.Property("UserString") + .HasColumnName("User"); + + b.HasKey("ID"); + + b.HasIndex("BeatmapInfoID"); + + b.HasIndex("OnlineScoreID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("ScoreInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("SkinInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("SkinInfoID"); + + b.ToTable("SkinFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Creator"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.ToTable("SkinInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Scoring.ScoreInfo") + .WithMany("Files") + .HasForeignKey("ScoreInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") + .WithMany("Scores") + .HasForeignKey("BeatmapInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Files") + .HasForeignKey("SkinInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.cs b/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.cs new file mode 100644 index 0000000000..55dc18b6a3 --- /dev/null +++ b/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.cs @@ -0,0 +1,24 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddDateAddedColumnToBeatmapSet : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "DateAdded", + table: "BeatmapSetInfo", + nullable: false, + defaultValue: new DateTimeOffset(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0))); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "DateAdded", + table: "BeatmapSetInfo"); + } + } +} diff --git a/osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.Designer.cs b/osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.Designer.cs new file mode 100644 index 0000000000..c5fcc16f84 --- /dev/null +++ b/osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.Designer.cs @@ -0,0 +1,504 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20190708070844_AddBPMAndLengthColumns")] + partial class AddBPMAndLengthColumns + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.4-servicing-10062"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BPM"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("Length"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("Status"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash"); + + b.HasIndex("MD5Hash"); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.Property("Status"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Key") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("SkinInfoID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("SkinInfoID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("ScoreInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("ScoreInfoID"); + + b.ToTable("ScoreFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Accuracy") + .HasColumnType("DECIMAL(1,4)"); + + b.Property("BeatmapInfoID"); + + b.Property("Combo"); + + b.Property("Date"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MaxCombo"); + + b.Property("ModsJson") + .HasColumnName("Mods"); + + b.Property("OnlineScoreID"); + + b.Property("PP"); + + b.Property("Rank"); + + b.Property("RulesetID"); + + b.Property("StatisticsJson") + .HasColumnName("Statistics"); + + b.Property("TotalScore"); + + b.Property("UserID") + .HasColumnName("UserID"); + + b.Property("UserString") + .HasColumnName("User"); + + b.HasKey("ID"); + + b.HasIndex("BeatmapInfoID"); + + b.HasIndex("OnlineScoreID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("ScoreInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("SkinInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("SkinInfoID"); + + b.ToTable("SkinFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Creator"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.ToTable("SkinInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Settings") + .HasForeignKey("SkinInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Scoring.ScoreInfo") + .WithMany("Files") + .HasForeignKey("ScoreInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") + .WithMany("Scores") + .HasForeignKey("BeatmapInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Files") + .HasForeignKey("SkinInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.cs b/osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.cs new file mode 100644 index 0000000000..f5963ebf5e --- /dev/null +++ b/osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.cs @@ -0,0 +1,33 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddBPMAndLengthColumns : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "BPM", + table: "BeatmapInfo", + nullable: false, + defaultValue: 0.0); + + migrationBuilder.AddColumn( + name: "Length", + table: "BeatmapInfo", + nullable: false, + defaultValue: 0.0); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "BPM", + table: "BeatmapInfo"); + + migrationBuilder.DropColumn( + name: "Length", + table: "BeatmapInfo"); + } + } +} diff --git a/osu.Game/Migrations/20190913104727_AddBeatmapVideo.Designer.cs b/osu.Game/Migrations/20190913104727_AddBeatmapVideo.Designer.cs new file mode 100644 index 0000000000..826233a2b0 --- /dev/null +++ b/osu.Game/Migrations/20190913104727_AddBeatmapVideo.Designer.cs @@ -0,0 +1,506 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20190913104727_AddBeatmapVideo")] + partial class AddBeatmapVideo + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.6-servicing-10079"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BPM"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("Length"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("Status"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash"); + + b.HasIndex("MD5Hash"); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.Property("VideoFile"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.Property("Status"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Key") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("SkinInfoID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("SkinInfoID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("ScoreInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("ScoreInfoID"); + + b.ToTable("ScoreFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Accuracy") + .HasColumnType("DECIMAL(1,4)"); + + b.Property("BeatmapInfoID"); + + b.Property("Combo"); + + b.Property("Date"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MaxCombo"); + + b.Property("ModsJson") + .HasColumnName("Mods"); + + b.Property("OnlineScoreID"); + + b.Property("PP"); + + b.Property("Rank"); + + b.Property("RulesetID"); + + b.Property("StatisticsJson") + .HasColumnName("Statistics"); + + b.Property("TotalScore"); + + b.Property("UserID") + .HasColumnName("UserID"); + + b.Property("UserString") + .HasColumnName("User"); + + b.HasKey("ID"); + + b.HasIndex("BeatmapInfoID"); + + b.HasIndex("OnlineScoreID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("ScoreInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("SkinInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("SkinInfoID"); + + b.ToTable("SkinFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Creator"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.ToTable("SkinInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Settings") + .HasForeignKey("SkinInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Scoring.ScoreInfo") + .WithMany("Files") + .HasForeignKey("ScoreInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") + .WithMany("Scores") + .HasForeignKey("BeatmapInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Files") + .HasForeignKey("SkinInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20190913104727_AddBeatmapVideo.cs b/osu.Game/Migrations/20190913104727_AddBeatmapVideo.cs new file mode 100644 index 0000000000..9ed0943acd --- /dev/null +++ b/osu.Game/Migrations/20190913104727_AddBeatmapVideo.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddBeatmapVideo : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "VideoFile", + table: "BeatmapMetadata", + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "VideoFile", + table: "BeatmapMetadata"); + } + } +} diff --git a/osu.Game/Migrations/20200302094919_RefreshVolumeBindings.Designer.cs b/osu.Game/Migrations/20200302094919_RefreshVolumeBindings.Designer.cs new file mode 100644 index 0000000000..22316b0380 --- /dev/null +++ b/osu.Game/Migrations/20200302094919_RefreshVolumeBindings.Designer.cs @@ -0,0 +1,506 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20200302094919_RefreshVolumeBindings")] + partial class RefreshVolumeBindings + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.6-servicing-10079"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BPM"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("Length"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("Status"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash"); + + b.HasIndex("MD5Hash"); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.Property("VideoFile"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.Property("Status"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Key") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("SkinInfoID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("SkinInfoID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("ScoreInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("ScoreInfoID"); + + b.ToTable("ScoreFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Accuracy") + .HasColumnType("DECIMAL(1,4)"); + + b.Property("BeatmapInfoID"); + + b.Property("Combo"); + + b.Property("Date"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MaxCombo"); + + b.Property("ModsJson") + .HasColumnName("Mods"); + + b.Property("OnlineScoreID"); + + b.Property("PP"); + + b.Property("Rank"); + + b.Property("RulesetID"); + + b.Property("StatisticsJson") + .HasColumnName("Statistics"); + + b.Property("TotalScore"); + + b.Property("UserID") + .HasColumnName("UserID"); + + b.Property("UserString") + .HasColumnName("User"); + + b.HasKey("ID"); + + b.HasIndex("BeatmapInfoID"); + + b.HasIndex("OnlineScoreID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("ScoreInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("SkinInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("SkinInfoID"); + + b.ToTable("SkinFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Creator"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.ToTable("SkinInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Settings") + .HasForeignKey("SkinInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Scoring.ScoreInfo") + .WithMany("Files") + .HasForeignKey("ScoreInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") + .WithMany("Scores") + .HasForeignKey("BeatmapInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Files") + .HasForeignKey("SkinInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20200302094919_RefreshVolumeBindings.cs b/osu.Game/Migrations/20200302094919_RefreshVolumeBindings.cs new file mode 100644 index 0000000000..ec4475971c --- /dev/null +++ b/osu.Game/Migrations/20200302094919_RefreshVolumeBindings.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class RefreshVolumeBindings : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql("DELETE FROM KeyBinding WHERE action in (6,7)"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + } + } +} diff --git a/osu.Game/Migrations/20201019224408_AddEpilepsyWarning.Designer.cs b/osu.Game/Migrations/20201019224408_AddEpilepsyWarning.Designer.cs new file mode 100644 index 0000000000..1c05de832e --- /dev/null +++ b/osu.Game/Migrations/20201019224408_AddEpilepsyWarning.Designer.cs @@ -0,0 +1,508 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20201019224408_AddEpilepsyWarning")] + partial class AddEpilepsyWarning + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.6-servicing-10079"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BPM"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("EpilepsyWarning"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("Length"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("Status"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash"); + + b.HasIndex("MD5Hash"); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.Property("VideoFile"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.Property("Status"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Key") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("SkinInfoID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("SkinInfoID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("ScoreInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("ScoreInfoID"); + + b.ToTable("ScoreFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Accuracy") + .HasColumnType("DECIMAL(1,4)"); + + b.Property("BeatmapInfoID"); + + b.Property("Combo"); + + b.Property("Date"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MaxCombo"); + + b.Property("ModsJson") + .HasColumnName("Mods"); + + b.Property("OnlineScoreID"); + + b.Property("PP"); + + b.Property("Rank"); + + b.Property("RulesetID"); + + b.Property("StatisticsJson") + .HasColumnName("Statistics"); + + b.Property("TotalScore"); + + b.Property("UserID") + .HasColumnName("UserID"); + + b.Property("UserString") + .HasColumnName("User"); + + b.HasKey("ID"); + + b.HasIndex("BeatmapInfoID"); + + b.HasIndex("OnlineScoreID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("ScoreInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("SkinInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("SkinInfoID"); + + b.ToTable("SkinFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Creator"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.ToTable("SkinInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Settings") + .HasForeignKey("SkinInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Scoring.ScoreInfo") + .WithMany("Files") + .HasForeignKey("ScoreInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") + .WithMany("Scores") + .HasForeignKey("BeatmapInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Files") + .HasForeignKey("SkinInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20201019224408_AddEpilepsyWarning.cs b/osu.Game/Migrations/20201019224408_AddEpilepsyWarning.cs new file mode 100644 index 0000000000..be6968aa5d --- /dev/null +++ b/osu.Game/Migrations/20201019224408_AddEpilepsyWarning.cs @@ -0,0 +1,23 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddEpilepsyWarning : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "EpilepsyWarning", + table: "BeatmapInfo", + nullable: false, + defaultValue: false); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "EpilepsyWarning", + table: "BeatmapInfo"); + } + } +} diff --git a/osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.Designer.cs b/osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.Designer.cs new file mode 100644 index 0000000000..2c100d39b9 --- /dev/null +++ b/osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.Designer.cs @@ -0,0 +1,506 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20210412045700_RefreshVolumeBindingsAgain")] + partial class RefreshVolumeBindingsAgain + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.6-servicing-10079"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BPM"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("Length"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("Status"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash"); + + b.HasIndex("MD5Hash"); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.Property("VideoFile"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.Property("Status"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Key") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("SkinInfoID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("SkinInfoID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("ScoreInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("ScoreInfoID"); + + b.ToTable("ScoreFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Accuracy") + .HasColumnType("DECIMAL(1,4)"); + + b.Property("BeatmapInfoID"); + + b.Property("Combo"); + + b.Property("Date"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MaxCombo"); + + b.Property("ModsJson") + .HasColumnName("Mods"); + + b.Property("OnlineScoreID"); + + b.Property("PP"); + + b.Property("Rank"); + + b.Property("RulesetID"); + + b.Property("StatisticsJson") + .HasColumnName("Statistics"); + + b.Property("TotalScore"); + + b.Property("UserID") + .HasColumnName("UserID"); + + b.Property("UserString") + .HasColumnName("User"); + + b.HasKey("ID"); + + b.HasIndex("BeatmapInfoID"); + + b.HasIndex("OnlineScoreID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("ScoreInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("SkinInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("SkinInfoID"); + + b.ToTable("SkinFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Creator"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.ToTable("SkinInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Settings") + .HasForeignKey("SkinInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Scoring.ScoreInfo") + .WithMany("Files") + .HasForeignKey("ScoreInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") + .WithMany("Scores") + .HasForeignKey("BeatmapInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Files") + .HasForeignKey("SkinInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.cs b/osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.cs new file mode 100644 index 0000000000..155d6670a8 --- /dev/null +++ b/osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class RefreshVolumeBindingsAgain : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql("DELETE FROM KeyBinding WHERE action in (6,7)"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + } + } +} diff --git a/osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.Designer.cs b/osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.Designer.cs new file mode 100644 index 0000000000..b808c648da --- /dev/null +++ b/osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.Designer.cs @@ -0,0 +1,508 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20210511060743_AddSkinInstantiationInfo")] + partial class AddSkinInstantiationInfo + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.6-servicing-10079"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BPM"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("EpilepsyWarning"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("Length"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("Status"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash"); + + b.HasIndex("MD5Hash"); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.Property("Status"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Key") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("SkinInfoID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("SkinInfoID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("ScoreInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("ScoreInfoID"); + + b.ToTable("ScoreFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Accuracy") + .HasColumnType("DECIMAL(1,4)"); + + b.Property("BeatmapInfoID"); + + b.Property("Combo"); + + b.Property("Date"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MaxCombo"); + + b.Property("ModsJson") + .HasColumnName("Mods"); + + b.Property("OnlineScoreID"); + + b.Property("PP"); + + b.Property("Rank"); + + b.Property("RulesetID"); + + b.Property("StatisticsJson") + .HasColumnName("Statistics"); + + b.Property("TotalScore"); + + b.Property("UserID") + .HasColumnName("UserID"); + + b.Property("UserString") + .HasColumnName("User"); + + b.HasKey("ID"); + + b.HasIndex("BeatmapInfoID"); + + b.HasIndex("OnlineScoreID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("ScoreInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("SkinInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("SkinInfoID"); + + b.ToTable("SkinFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Creator"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.ToTable("SkinInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Settings") + .HasForeignKey("SkinInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Scoring.ScoreInfo") + .WithMany("Files") + .HasForeignKey("ScoreInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") + .WithMany("Scores") + .HasForeignKey("BeatmapInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Files") + .HasForeignKey("SkinInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.cs b/osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.cs new file mode 100644 index 0000000000..1d5b0769a4 --- /dev/null +++ b/osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddSkinInstantiationInfo : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "InstantiationInfo", + table: "SkinInfo", + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "InstantiationInfo", + table: "SkinInfo"); + } + } +} diff --git a/osu.Game/Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.Designer.cs b/osu.Game/Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.Designer.cs new file mode 100644 index 0000000000..89bab3a0fa --- /dev/null +++ b/osu.Game/Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.Designer.cs @@ -0,0 +1,511 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20210514062639_AddAuthorIdToBeatmapMetadata")] + partial class AddAuthorIdToBeatmapMetadata + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.6-servicing-10079"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BPM"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("EpilepsyWarning"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("Length"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("Status"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash"); + + b.HasIndex("MD5Hash"); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorID") + .HasColumnName("AuthorID"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.Property("Status"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Key") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("SkinInfoID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("SkinInfoID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("ScoreInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("ScoreInfoID"); + + b.ToTable("ScoreFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Accuracy") + .HasColumnType("DECIMAL(1,4)"); + + b.Property("BeatmapInfoID"); + + b.Property("Combo"); + + b.Property("Date"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MaxCombo"); + + b.Property("ModsJson") + .HasColumnName("Mods"); + + b.Property("OnlineScoreID"); + + b.Property("PP"); + + b.Property("Rank"); + + b.Property("RulesetID"); + + b.Property("StatisticsJson") + .HasColumnName("Statistics"); + + b.Property("TotalScore"); + + b.Property("UserID") + .HasColumnName("UserID"); + + b.Property("UserString") + .HasColumnName("User"); + + b.HasKey("ID"); + + b.HasIndex("BeatmapInfoID"); + + b.HasIndex("OnlineScoreID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("ScoreInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("SkinInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("SkinInfoID"); + + b.ToTable("SkinFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Creator"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.ToTable("SkinInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Settings") + .HasForeignKey("SkinInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Scoring.ScoreInfo") + .WithMany("Files") + .HasForeignKey("ScoreInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") + .WithMany("Scores") + .HasForeignKey("BeatmapInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Files") + .HasForeignKey("SkinInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.cs b/osu.Game/Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.cs new file mode 100644 index 0000000000..98fe9b5e13 --- /dev/null +++ b/osu.Game/Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.cs @@ -0,0 +1,23 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddAuthorIdToBeatmapMetadata : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "AuthorID", + table: "BeatmapMetadata", + nullable: false, + defaultValue: 0); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "AuthorID", + table: "BeatmapMetadata"); + } + } +} diff --git a/osu.Game/Migrations/20210824185035_AddCountdownSettings.Designer.cs b/osu.Game/Migrations/20210824185035_AddCountdownSettings.Designer.cs new file mode 100644 index 0000000000..afeb42130d --- /dev/null +++ b/osu.Game/Migrations/20210824185035_AddCountdownSettings.Designer.cs @@ -0,0 +1,513 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20210824185035_AddCountdownSettings")] + partial class AddCountdownSettings + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.6-servicing-10079"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BPM"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("CountdownOffset"); + + b.Property("DistanceSpacing"); + + b.Property("EpilepsyWarning"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("Length"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("Status"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash"); + + b.HasIndex("MD5Hash"); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorID") + .HasColumnName("AuthorID"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.Property("Status"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Key") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("SkinInfoID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("SkinInfoID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("ScoreInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("ScoreInfoID"); + + b.ToTable("ScoreFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Accuracy") + .HasColumnType("DECIMAL(1,4)"); + + b.Property("BeatmapInfoID"); + + b.Property("Combo"); + + b.Property("Date"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MaxCombo"); + + b.Property("ModsJson") + .HasColumnName("Mods"); + + b.Property("OnlineScoreID"); + + b.Property("PP"); + + b.Property("Rank"); + + b.Property("RulesetID"); + + b.Property("StatisticsJson") + .HasColumnName("Statistics"); + + b.Property("TotalScore"); + + b.Property("UserID") + .HasColumnName("UserID"); + + b.Property("UserString") + .HasColumnName("User"); + + b.HasKey("ID"); + + b.HasIndex("BeatmapInfoID"); + + b.HasIndex("OnlineScoreID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("ScoreInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("SkinInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("SkinInfoID"); + + b.ToTable("SkinFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Creator"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.ToTable("SkinInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Settings") + .HasForeignKey("SkinInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Scoring.ScoreInfo") + .WithMany("Files") + .HasForeignKey("ScoreInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") + .WithMany("Scores") + .HasForeignKey("BeatmapInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Files") + .HasForeignKey("SkinInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20210824185035_AddCountdownSettings.cs b/osu.Game/Migrations/20210824185035_AddCountdownSettings.cs new file mode 100644 index 0000000000..564f5f4520 --- /dev/null +++ b/osu.Game/Migrations/20210824185035_AddCountdownSettings.cs @@ -0,0 +1,23 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddCountdownSettings : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "CountdownOffset", + table: "BeatmapInfo", + nullable: false, + defaultValue: 0); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "CountdownOffset", + table: "BeatmapInfo"); + } + } +} diff --git a/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.Designer.cs b/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.Designer.cs new file mode 100644 index 0000000000..6e53d7fae0 --- /dev/null +++ b/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.Designer.cs @@ -0,0 +1,515 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20210912144011_AddSamplesMatchPlaybackRate")] + partial class AddSamplesMatchPlaybackRate + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.6-servicing-10079"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BPM"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("CountdownOffset"); + + b.Property("DistanceSpacing"); + + b.Property("EpilepsyWarning"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("Length"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SamplesMatchPlaybackRate"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("Status"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash"); + + b.HasIndex("MD5Hash"); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorID") + .HasColumnName("AuthorID"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.Property("Status"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Key") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("SkinInfoID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("SkinInfoID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("ScoreInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("ScoreInfoID"); + + b.ToTable("ScoreFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Accuracy") + .HasColumnType("DECIMAL(1,4)"); + + b.Property("BeatmapInfoID"); + + b.Property("Combo"); + + b.Property("Date"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MaxCombo"); + + b.Property("ModsJson") + .HasColumnName("Mods"); + + b.Property("OnlineScoreID"); + + b.Property("PP"); + + b.Property("Rank"); + + b.Property("RulesetID"); + + b.Property("StatisticsJson") + .HasColumnName("Statistics"); + + b.Property("TotalScore"); + + b.Property("UserID") + .HasColumnName("UserID"); + + b.Property("UserString") + .HasColumnName("User"); + + b.HasKey("ID"); + + b.HasIndex("BeatmapInfoID"); + + b.HasIndex("OnlineScoreID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("ScoreInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("SkinInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("SkinInfoID"); + + b.ToTable("SkinFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Creator"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.ToTable("SkinInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Settings") + .HasForeignKey("SkinInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Scoring.ScoreInfo") + .WithMany("Files") + .HasForeignKey("ScoreInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") + .WithMany("Scores") + .HasForeignKey("BeatmapInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Files") + .HasForeignKey("SkinInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.cs b/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.cs new file mode 100644 index 0000000000..bf3f855d5f --- /dev/null +++ b/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.cs @@ -0,0 +1,23 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddSamplesMatchPlaybackRate : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "SamplesMatchPlaybackRate", + table: "BeatmapInfo", + nullable: false, + defaultValue: false); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "SamplesMatchPlaybackRate", + table: "BeatmapInfo"); + } + } +} diff --git a/osu.Game/Migrations/20211020081609_ResetSkinHashes.cs b/osu.Game/Migrations/20211020081609_ResetSkinHashes.cs new file mode 100644 index 0000000000..6d53c019ec --- /dev/null +++ b/osu.Game/Migrations/20211020081609_ResetSkinHashes.cs @@ -0,0 +1,23 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20211020081609_ResetSkinHashes")] + public partial class ResetSkinHashes : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql($"UPDATE SkinInfo SET Hash = null"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + } + } +} diff --git a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs new file mode 100644 index 0000000000..036c26cb0a --- /dev/null +++ b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs @@ -0,0 +1,513 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + partial class OsuDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.6-servicing-10079"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BPM"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("CountdownOffset"); + + b.Property("DistanceSpacing"); + + b.Property("EpilepsyWarning"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("Length"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SamplesMatchPlaybackRate"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("Status"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash"); + + b.HasIndex("MD5Hash"); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorID") + .HasColumnName("AuthorID"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.Property("Status"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Key") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("SkinInfoID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("SkinInfoID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("ScoreInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("ScoreInfoID"); + + b.ToTable("ScoreFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Accuracy") + .HasColumnType("DECIMAL(1,4)"); + + b.Property("BeatmapInfoID"); + + b.Property("Combo"); + + b.Property("Date"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MaxCombo"); + + b.Property("ModsJson") + .HasColumnName("Mods"); + + b.Property("OnlineScoreID"); + + b.Property("PP"); + + b.Property("Rank"); + + b.Property("RulesetID"); + + b.Property("StatisticsJson") + .HasColumnName("Statistics"); + + b.Property("TotalScore"); + + b.Property("UserID") + .HasColumnName("UserID"); + + b.Property("UserString") + .HasColumnName("User"); + + b.HasKey("ID"); + + b.HasIndex("BeatmapInfoID"); + + b.HasIndex("OnlineScoreID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("ScoreInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("SkinInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("SkinInfoID"); + + b.ToTable("SkinFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Creator"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.ToTable("SkinInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Settings") + .HasForeignKey("SkinInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Scoring.ScoreInfo") + .WithMany("Files") + .HasForeignKey("ScoreInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") + .WithMany("Scores") + .HasForeignKey("BeatmapInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Files") + .HasForeignKey("SkinInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} From 1a14ccc7ee37f2daa4b3500483668621b7c7abef Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 29 Jan 2022 22:42:34 +0900 Subject: [PATCH 765/996] Run EF migrations before migrating to realm Turns out that there are more than zero users that are upgrading from old databases. I think we probably want to support this for now. Tested against database in https://github.com/ppy/osu/discussions/16700 and one other I had locally, both work correctly. --- osu.Game/Database/EFToRealmMigrator.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index a53704df9d..0bb5388d55 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -133,6 +133,8 @@ namespace osu.Game.Database r.RemoveAll(); }); + ef.Migrate(); + migrateSettings(ef); migrateSkins(ef); migrateBeatmaps(ef); From 24f9ef4005827338702e3e8766a835b755c7a5d0 Mon Sep 17 00:00:00 2001 From: Nitrous Date: Sat, 29 Jan 2022 22:31:04 +0800 Subject: [PATCH 766/996] make xmldoc more verbose --- osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs index f366481c7d..9eeb060135 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override IconUsage? Icon => FontAwesome.Solid.Keyboard; /// - /// Whether incoming input must be checked by . + /// Whether incoming input must be checked by before it is passed to gameplay. /// public bool CanIntercept => !isBreakTime.Value && introEnded; From b4e516c535032f91c70f67e0ae8aebbe156d8d3e Mon Sep 17 00:00:00 2001 From: Nitrous Date: Sat, 29 Jan 2022 22:31:24 +0800 Subject: [PATCH 767/996] allow test scenes to specify replays manually --- osu.Game/Tests/Visual/ModTestScene.cs | 33 +++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/osu.Game/Tests/Visual/ModTestScene.cs b/osu.Game/Tests/Visual/ModTestScene.cs index a71d008eb9..524482237a 100644 --- a/osu.Game/Tests/Visual/ModTestScene.cs +++ b/osu.Game/Tests/Visual/ModTestScene.cs @@ -5,8 +5,12 @@ using System; using System.Collections.Generic; using JetBrains.Annotations; using osu.Game.Beatmaps; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Replays; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Replays; +using osu.Game.Scoring; namespace osu.Game.Tests.Visual { @@ -50,18 +54,37 @@ namespace osu.Game.Tests.Visual return CreateModPlayer(ruleset); } - protected virtual TestPlayer CreateModPlayer(Ruleset ruleset) => new ModTestPlayer(AllowFail); + protected virtual TestPlayer CreateModPlayer(Ruleset ruleset) => new ModTestPlayer(currentTestData, AllowFail); protected class ModTestPlayer : TestPlayer { private readonly bool allowFail; + private ModTestData currentTestData; protected override bool CheckModsAllowFailure() => allowFail; - public ModTestPlayer(bool allowFail) + public ModTestPlayer(ModTestData data, bool allowFail) : base(false, false) { this.allowFail = allowFail; + currentTestData = data; + } + + protected override void PrepareReplay() + { + if (currentTestData.Autoplay && currentTestData.Frames?.Count > 0) + throw new InvalidOperationException(@$"{nameof(ModTestData.Autoplay)} must be false when {nameof(ModTestData.Frames)} is specified."); + + if (currentTestData.Frames != null) + { + DrawableRuleset?.SetReplayScore(new Score + { + Replay = new Replay { Frames = currentTestData.Frames }, + ScoreInfo = new ScoreInfo { User = new APIUser { Username = @"Test" } }, + }); + } + + base.PrepareReplay(); } } @@ -72,6 +95,12 @@ namespace osu.Game.Tests.Visual /// public bool Autoplay = true; + /// + /// The frames to use for replay. must be set to false. + /// + [CanBeNull] + public List Frames; + /// /// The beatmap for this test case. /// From 1087d8b1cedfaad85e048151e20638c6cf18ff18 Mon Sep 17 00:00:00 2001 From: Nitrous Date: Sat, 29 Jan 2022 22:31:31 +0800 Subject: [PATCH 768/996] add tests --- .../Mods/TestSceneOsuModAlternate.cs | 154 ++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs new file mode 100644 index 0000000000..7b3eaa7ffd --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs @@ -0,0 +1,154 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Timing; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Replays; +using osu.Game.Rulesets.Replays; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Tests.Mods +{ + public class TestSceneOsuModAlternate : OsuModTestScene + { + [Test] + public void TestInputAtIntro() => CreateModTest(new ModTestData + { + Mod = new OsuModAlternate(), + PassCondition = () => Player.ScoreProcessor.Combo.Value == 1, + Autoplay = false, + Beatmap = new Beatmap + { + HitObjects = new List + { + new HitCircle + { + StartTime = 1000, + Position = new Vector2(100), + }, + }, + }, + Frames = new List + { + new OsuReplayFrame(500, new Vector2(200), OsuAction.LeftButton), + new OsuReplayFrame(501, new Vector2(200)), + new OsuReplayFrame(1000, new Vector2(100), OsuAction.LeftButton), + } + }); + + [Test] + public void TestInputAlternating() => CreateModTest(new ModTestData + { + Mod = new OsuModAlternate(), + PassCondition = () => Player.ScoreProcessor.Combo.Value == 4, + Autoplay = false, + Beatmap = new Beatmap + { + HitObjects = new List + { + new HitCircle + { + StartTime = 500, + Position = new Vector2(100), + }, + new HitCircle + { + StartTime = 1000, + Position = new Vector2(200, 100), + }, + new HitCircle + { + StartTime = 1500, + Position = new Vector2(300, 100), + }, + new HitCircle + { + StartTime = 2000, + Position = new Vector2(400, 100), + }, + }, + }, + Frames = new List + { + new OsuReplayFrame(500, new Vector2(100), OsuAction.LeftButton), + new OsuReplayFrame(501, new Vector2(100)), + new OsuReplayFrame(1000, new Vector2(200, 100), OsuAction.RightButton), + new OsuReplayFrame(1001, new Vector2(200, 100)), + new OsuReplayFrame(1500, new Vector2(300, 100), OsuAction.LeftButton), + new OsuReplayFrame(1501, new Vector2(300, 100)), + new OsuReplayFrame(2000, new Vector2(400, 100), OsuAction.RightButton), + new OsuReplayFrame(2001, new Vector2(400, 100)), + } + }); + + [Test] + public void TestInputSingular() => CreateModTest(new ModTestData + { + Mod = new OsuModAlternate(), + PassCondition = () => Player.ScoreProcessor.Combo.Value == 0 && Player.ScoreProcessor.HighestCombo.Value == 1, + Autoplay = false, + Beatmap = new Beatmap + { + HitObjects = new List + { + new HitCircle + { + StartTime = 500, + Position = new Vector2(100), + }, + new HitCircle + { + StartTime = 1000, + Position = new Vector2(200, 100), + }, + }, + }, + Frames = new List + { + new OsuReplayFrame(500, new Vector2(100), OsuAction.LeftButton), + new OsuReplayFrame(501, new Vector2(100)), + new OsuReplayFrame(1000, new Vector2(200, 100), OsuAction.LeftButton), + } + }); + + [Test] + public void TestInputSingularWithBreak() => CreateModTest(new ModTestData + { + Mod = new OsuModAlternate(), + PassCondition = () => Player.ScoreProcessor.Combo.Value == 2, + Autoplay = false, + Beatmap = new Beatmap + { + Breaks = new List + { + new BreakPeriod(500, 2250), + }, + HitObjects = new List + { + new HitCircle + { + StartTime = 500, + Position = new Vector2(100), + }, + new HitCircle + { + StartTime = 2500, + Position = new Vector2(100), + } + } + }, + Frames = new List + { + new OsuReplayFrame(500, new Vector2(100), OsuAction.LeftButton), + new OsuReplayFrame(501, new Vector2(100)), + new OsuReplayFrame(2500, new Vector2(100), OsuAction.LeftButton), + new OsuReplayFrame(2501, new Vector2(100)), + } + }); + } +} From a8eb3f95df2501659941c8d4d242fd8d08b897c8 Mon Sep 17 00:00:00 2001 From: Nitrous Date: Sat, 29 Jan 2022 22:54:17 +0800 Subject: [PATCH 769/996] add readonly modifier --- osu.Game/Tests/Visual/ModTestScene.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/ModTestScene.cs b/osu.Game/Tests/Visual/ModTestScene.cs index 524482237a..96fafa8727 100644 --- a/osu.Game/Tests/Visual/ModTestScene.cs +++ b/osu.Game/Tests/Visual/ModTestScene.cs @@ -59,7 +59,7 @@ namespace osu.Game.Tests.Visual protected class ModTestPlayer : TestPlayer { private readonly bool allowFail; - private ModTestData currentTestData; + private readonly ModTestData currentTestData; protected override bool CheckModsAllowFailure() => allowFail; From c6d303a5b44ba2f0d0c221178e9abab50dc2fbaa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 21:16:29 +0900 Subject: [PATCH 770/996] Add xmldoc to `Leaderboard` class --- osu.Game/Online/Leaderboards/Leaderboard.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 515cc6fd73..e2a52b5db9 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -23,6 +23,12 @@ using osuTK.Graphics; namespace osu.Game.Online.Leaderboards { + /// + /// A leaderboard which displays a scrolling list of top scores, along with a single "user best" + /// for the local user. + /// + /// The scope of the leaderboard (ie. global or local). + /// The score model class. public abstract class Leaderboard : Container { private const double fade_duration = 300; From aee93934d5c42792b41ef3b13c7baa748b7c3ee0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 21:22:09 +0900 Subject: [PATCH 771/996] Rename methods to make more sense (and always run through `AddOnce`) --- .../Visual/UserInterface/TestSceneDeleteLocalScore.cs | 2 +- osu.Game/Online/Leaderboards/Leaderboard.cs | 10 +++++----- .../OnlinePlay/Match/Components/MatchLeaderboard.cs | 2 +- .../OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs | 2 +- .../Screens/Select/Leaderboards/BeatmapLeaderboard.cs | 10 +++++----- osu.Game/Screens/Select/PlayBeatmapDetailArea.cs | 2 +- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index 4826d2fb33..4938fd1271 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -132,7 +132,7 @@ namespace osu.Game.Tests.Visual.UserInterface leaderboard.FinishTransforms(true); // After setting scores, we may be waiting for transforms to expire drawables leaderboard.BeatmapInfo = beatmapInfo; - leaderboard.RefreshScores(); // Required in the case that the beatmap hasn't changed + leaderboard.RefetchScores(); // Required in the case that the beatmap hasn't changed }); [SetUpSteps] diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index e2a52b5db9..cd67fc7301 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -132,7 +132,7 @@ namespace osu.Game.Online.Leaderboards return; scope = value; - RefreshScores(); + RefetchScores(); } } @@ -160,7 +160,7 @@ namespace osu.Game.Online.Leaderboards case PlaceholderState.NetworkFailure: replacePlaceholder(new ClickablePlaceholder(@"Couldn't fetch scores!", FontAwesome.Solid.Sync) { - Action = RefreshScores + Action = RefetchScores }); break; @@ -272,15 +272,15 @@ namespace osu.Game.Online.Leaderboards case APIState.Online: case APIState.Offline: if (IsOnlineScope) - RefreshScores(); + RefetchScores(); break; } }); - public void RefreshScores() => Scheduler.AddOnce(UpdateScores); + public void RefetchScores() => Scheduler.AddOnce(refetchScores); - protected void UpdateScores() + private void refetchScores() { // don't display any scores or placeholder until the first Scores_Set has been called. // this avoids scope changes flickering a "no scores" placeholder before initialisation of song select is finished. diff --git a/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs b/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs index 134e083c42..e4ec3ac4a5 100644 --- a/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs +++ b/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs @@ -26,7 +26,7 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components return; Scores = null; - UpdateScores(); + RefetchScores(); }, true); } diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs index 4114a5e9a0..542851cb0f 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs @@ -220,7 +220,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists protected override Screen CreateGameplayScreen() => new PlayerLoader(() => new PlaylistsPlayer(Room, SelectedItem.Value) { - Exited = () => leaderboard.RefreshScores() + Exited = () => leaderboard.RefetchScores() }); } } diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 31cbe91f5c..02e4e162dd 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -53,7 +53,7 @@ namespace osu.Game.Screens.Select.Leaderboards Scores = null; if (IsOnlineScope) - UpdateScores(); + RefetchScores(); else { if (IsLoaded) @@ -77,7 +77,7 @@ namespace osu.Game.Screens.Select.Leaderboards filterMods = value; - UpdateScores(); + RefetchScores(); } } @@ -96,11 +96,11 @@ namespace osu.Game.Screens.Select.Leaderboards [BackgroundDependencyLoader] private void load() { - ruleset.ValueChanged += _ => UpdateScores(); + ruleset.ValueChanged += _ => RefetchScores(); mods.ValueChanged += _ => { if (filterMods) - UpdateScores(); + RefetchScores(); }; } @@ -127,7 +127,7 @@ namespace osu.Game.Screens.Select.Leaderboards (_, changes, ___) => { if (!IsOnlineScope) - RefreshScores(); + RefetchScores(); }); } diff --git a/osu.Game/Screens/Select/PlayBeatmapDetailArea.cs b/osu.Game/Screens/Select/PlayBeatmapDetailArea.cs index b8b8e3e4bc..09f75b7658 100644 --- a/osu.Game/Screens/Select/PlayBeatmapDetailArea.cs +++ b/osu.Game/Screens/Select/PlayBeatmapDetailArea.cs @@ -53,7 +53,7 @@ namespace osu.Game.Screens.Select { base.Refresh(); - Leaderboard.RefreshScores(); + Leaderboard.RefetchScores(); } protected override void OnTabChanged(BeatmapDetailAreaTabItem tab, bool selectedMods) From b9dac6c3b2d312bc79fefff4d5abb1c24d78c00c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 21:33:22 +0900 Subject: [PATCH 772/996] Reorder and tidy up bindable flows --- osu.Game/Online/Leaderboards/Leaderboard.cs | 81 ++++++++++++--------- 1 file changed, 45 insertions(+), 36 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index cd67fc7301..af448b7d7b 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -46,6 +46,18 @@ namespace osu.Game.Online.Leaderboards private bool scoresLoadedOnce; + private APIRequest getScoresRequest; + private ScheduledDelegate getScoresRequestCallback; + + protected abstract bool IsOnlineScope { get; } + + [Resolved(CanBeNull = true)] + private IAPIProvider api { get; set; } + + private ScheduledDelegate pendingUpdateScores; + + private readonly IBindable apiState = new Bindable(); + private readonly Container content; protected override Container Content => content; @@ -239,44 +251,34 @@ namespace osu.Game.Online.Leaderboards protected virtual void Reset() { - getScoresRequest?.Cancel(); - getScoresRequest = null; + cancelPendingWork(); + Scores = null; } - [Resolved(CanBeNull = true)] - private IAPIProvider api { get; set; } - - private ScheduledDelegate pendingUpdateScores; - - private readonly IBindable apiState = new Bindable(); - - [BackgroundDependencyLoader] - private void load() + protected override void LoadComplete() { + base.LoadComplete(); + if (api != null) - apiState.BindTo(api.State); - - apiState.BindValueChanged(onlineStateChanged, true); - } - - private APIRequest getScoresRequest; - private ScheduledDelegate getScoresRequestCallback; - - protected abstract bool IsOnlineScope { get; } - - private void onlineStateChanged(ValueChangedEvent state) => Schedule(() => - { - switch (state.NewValue) { - case APIState.Online: - case APIState.Offline: - if (IsOnlineScope) - RefetchScores(); + apiState.BindTo(api.State); + apiState.BindValueChanged(state => + { + switch (state.NewValue) + { + case APIState.Online: + case APIState.Offline: + if (IsOnlineScope) + RefetchScores(); - break; + break; + } + }); } - }); + + RefetchScores(); + } public void RefetchScores() => Scheduler.AddOnce(refetchScores); @@ -286,13 +288,8 @@ namespace osu.Game.Online.Leaderboards // this avoids scope changes flickering a "no scores" placeholder before initialisation of song select is finished. if (!scoresLoadedOnce) return; - getScoresRequest?.Cancel(); - getScoresRequest = null; + cancelPendingWork(); - getScoresRequestCallback?.Cancel(); - getScoresRequestCallback = null; - - pendingUpdateScores?.Cancel(); pendingUpdateScores = Schedule(() => { PlaceholderState = PlaceholderState.Retrieving; @@ -319,6 +316,18 @@ namespace osu.Game.Online.Leaderboards }); } + private void cancelPendingWork() + { + getScoresRequest?.Cancel(); + getScoresRequest = null; + + getScoresRequestCallback?.Cancel(); + getScoresRequestCallback = null; + + pendingUpdateScores?.Cancel(); + pendingUpdateScores = null; + } + /// /// Performs a fetch/refresh of scores to be displayed. /// From 64925b3feaafa353bbfb87af39e13ee9e762a8bb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 21:38:23 +0900 Subject: [PATCH 773/996] Remove unused `Content` override --- osu.Game/Online/Leaderboards/Leaderboard.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index af448b7d7b..785c2e2ecf 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -29,7 +29,7 @@ namespace osu.Game.Online.Leaderboards /// /// The scope of the leaderboard (ie. global or local). /// The score model class. - public abstract class Leaderboard : Container + public abstract class Leaderboard : CompositeDrawable { private const double fade_duration = 300; @@ -58,10 +58,6 @@ namespace osu.Game.Online.Leaderboards private readonly IBindable apiState = new Bindable(); - private readonly Container content; - - protected override Container Content => content; - private ICollection scores; public ICollection Scores @@ -231,7 +227,7 @@ namespace osu.Game.Online.Leaderboards }, new Drawable[] { - content = new Container + new Container { AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, From 17aa9f304000505d5aff1d04239f5fa828873871 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 21:47:28 +0900 Subject: [PATCH 774/996] Remove pointless level of schedule/cancel logic --- osu.Game/Online/Leaderboards/Leaderboard.cs | 39 ++++++++------------- 1 file changed, 15 insertions(+), 24 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 785c2e2ecf..7ab3d0343a 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -54,8 +54,6 @@ namespace osu.Game.Online.Leaderboards [Resolved(CanBeNull = true)] private IAPIProvider api { get; set; } - private ScheduledDelegate pendingUpdateScores; - private readonly IBindable apiState = new Bindable(); private ICollection scores; @@ -248,7 +246,6 @@ namespace osu.Game.Online.Leaderboards protected virtual void Reset() { cancelPendingWork(); - Scores = null; } @@ -286,30 +283,27 @@ namespace osu.Game.Online.Leaderboards cancelPendingWork(); - pendingUpdateScores = Schedule(() => + PlaceholderState = PlaceholderState.Retrieving; + loading.Show(); + + getScoresRequest = FetchScores(scores => getScoresRequestCallback = Schedule(() => { - PlaceholderState = PlaceholderState.Retrieving; - loading.Show(); + Scores = scores.ToArray(); + PlaceholderState = Scores.Any() ? PlaceholderState.Successful : PlaceholderState.NoScores; + })); - getScoresRequest = FetchScores(scores => getScoresRequestCallback = Schedule(() => - { - Scores = scores.ToArray(); - PlaceholderState = Scores.Any() ? PlaceholderState.Successful : PlaceholderState.NoScores; - })); + if (getScoresRequest == null) + return; - if (getScoresRequest == null) + getScoresRequest.Failure += e => getScoresRequestCallback = Schedule(() => + { + if (e is OperationCanceledException) return; - getScoresRequest.Failure += e => getScoresRequestCallback = Schedule(() => - { - if (e is OperationCanceledException) - return; - - PlaceholderState = PlaceholderState.NetworkFailure; - }); - - api?.Queue(getScoresRequest); + PlaceholderState = PlaceholderState.NetworkFailure; }); + + api?.Queue(getScoresRequest); } private void cancelPendingWork() @@ -319,9 +313,6 @@ namespace osu.Game.Online.Leaderboards getScoresRequestCallback?.Cancel(); getScoresRequestCallback = null; - - pendingUpdateScores?.Cancel(); - pendingUpdateScores = null; } /// From c5486586623acd425be534fced770e456493c85c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 21:49:52 +0900 Subject: [PATCH 775/996] Remove move unused pieces --- osu.Game/Online/Leaderboards/Leaderboard.cs | 29 ++++++++------------- 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 7ab3d0343a..a426d2b448 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -82,7 +82,13 @@ namespace osu.Game.Online.Leaderboards // ensure placeholder is hidden when displaying scores PlaceholderState = PlaceholderState.Successful; - var scoreFlow = CreateScoreFlow(); + var scoreFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(0f, 5f), + Padding = new MarginPadding { Top = 10, Bottom = 5 }, + }; scoreFlow.ChildrenEnumerable = scores.Select((s, index) => CreateDrawableScore(s, index + 1)); // schedule because we may not be loaded yet (LoadComponentAsync complains). @@ -118,15 +124,6 @@ namespace osu.Game.Online.Leaderboards } } - protected virtual FillFlowContainer CreateScoreFlow() - => new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Spacing = new Vector2(0f, 5f), - Padding = new MarginPadding { Top = 10, Bottom = 5 }, - }; - private TScope scope; public TScope Scope @@ -345,9 +342,6 @@ namespace osu.Game.Online.Leaderboards currentPlaceholder = placeholder; } - protected virtual bool FadeBottom => true; - protected virtual bool FadeTop => false; - protected override void UpdateAfterChildren() { base.UpdateAfterChildren(); @@ -366,22 +360,21 @@ namespace osu.Game.Online.Leaderboards float topY = c.ToSpaceOfOtherDrawable(Vector2.Zero, scrollFlow).Y; float bottomY = topY + LeaderboardScore.HEIGHT; - bool requireTopFade = FadeTop && topY <= fadeTop; - bool requireBottomFade = FadeBottom && bottomY >= fadeBottom; + bool requireBottomFade = bottomY >= fadeBottom; - if (!requireTopFade && !requireBottomFade) + if (!requireBottomFade) c.Colour = Color4.White; else if (topY > fadeBottom + LeaderboardScore.HEIGHT || bottomY < fadeTop - LeaderboardScore.HEIGHT) c.Colour = Color4.Transparent; else { - if (bottomY - fadeBottom > 0 && FadeBottom) + if (bottomY - fadeBottom > 0) { c.Colour = ColourInfo.GradientVertical( Color4.White.Opacity(Math.Min(1 - (topY - fadeBottom) / LeaderboardScore.HEIGHT, 1)), Color4.White.Opacity(Math.Min(1 - (bottomY - fadeBottom) / LeaderboardScore.HEIGHT, 1))); } - else if (FadeTop) + else { c.Colour = ColourInfo.GradientVertical( Color4.White.Opacity(Math.Min(1 - (fadeTop - topY) / LeaderboardScore.HEIGHT, 1)), From b85b2c01fb248db8a3deb773ba1ed5da930d07c2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 21:53:12 +0900 Subject: [PATCH 776/996] Reorder based on accessibility and add regions --- osu.Game/Online/Leaderboards/Leaderboard.cs | 268 ++++++++++---------- 1 file changed, 138 insertions(+), 130 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index a426d2b448..d843dcb2bb 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -139,6 +139,139 @@ namespace osu.Game.Online.Leaderboards } } + protected Leaderboard() + { + InternalChildren = new Drawable[] + { + new OsuContextMenuContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Child = new GridContainer + { + RelativeSizeAxes = Axes.Both, + RowDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.AutoSize), + }, + Content = new[] + { + new Drawable[] + { + scrollContainer = new OsuScrollContainer + { + RelativeSizeAxes = Axes.Both, + ScrollbarVisible = false, + } + }, + new Drawable[] + { + new Container + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Child = topScoreContainer = new UserTopScoreContainer(CreateDrawableTopScore) + }, + }, + }, + }, + }, + loading = new LoadingSpinner(), + placeholderContainer = new Container + { + RelativeSizeAxes = Axes.Both + }, + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + if (api != null) + { + apiState.BindTo(api.State); + apiState.BindValueChanged(state => + { + switch (state.NewValue) + { + case APIState.Online: + case APIState.Offline: + if (IsOnlineScope) + RefetchScores(); + + break; + } + }); + } + + RefetchScores(); + } + + public void RefetchScores() => Scheduler.AddOnce(refetchScores); + + protected virtual void Reset() + { + cancelPendingWork(); + Scores = null; + } + + /// + /// Performs a fetch/refresh of scores to be displayed. + /// + /// A callback which should be called when fetching is completed. Scheduling is not required. + /// An responsible for the fetch operation. This will be queued and performed automatically. + protected abstract APIRequest FetchScores(Action> scoresCallback); + + protected abstract LeaderboardScore CreateDrawableScore(TScoreInfo model, int index); + + protected abstract LeaderboardScore CreateDrawableTopScore(TScoreInfo model); + + private void refetchScores() + { + // don't display any scores or placeholder until the first Scores_Set has been called. + // this avoids scope changes flickering a "no scores" placeholder before initialisation of song select is finished. + if (!scoresLoadedOnce) return; + + cancelPendingWork(); + + PlaceholderState = PlaceholderState.Retrieving; + loading.Show(); + + getScoresRequest = FetchScores(scores => getScoresRequestCallback = Schedule(() => + { + Scores = scores.ToArray(); + PlaceholderState = Scores.Any() ? PlaceholderState.Successful : PlaceholderState.NoScores; + })); + + if (getScoresRequest == null) + return; + + getScoresRequest.Failure += e => getScoresRequestCallback = Schedule(() => + { + if (e is OperationCanceledException) + return; + + PlaceholderState = PlaceholderState.NetworkFailure; + }); + + api?.Queue(getScoresRequest); + } + + private void cancelPendingWork() + { + getScoresRequest?.Cancel(); + getScoresRequest = null; + + getScoresRequestCallback?.Cancel(); + getScoresRequestCallback = null; + } + + #region Placeholder handling + + private Placeholder currentPlaceholder; + private PlaceholderState placeholderState; /// @@ -194,133 +327,6 @@ namespace osu.Game.Online.Leaderboards } } - protected Leaderboard() - { - InternalChildren = new Drawable[] - { - new OsuContextMenuContainer - { - RelativeSizeAxes = Axes.Both, - Masking = true, - Child = new GridContainer - { - RelativeSizeAxes = Axes.Both, - RowDimensions = new[] - { - new Dimension(), - new Dimension(GridSizeMode.AutoSize), - }, - Content = new[] - { - new Drawable[] - { - scrollContainer = new OsuScrollContainer - { - RelativeSizeAxes = Axes.Both, - ScrollbarVisible = false, - } - }, - new Drawable[] - { - new Container - { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Child = topScoreContainer = new UserTopScoreContainer(CreateDrawableTopScore) - }, - }, - }, - }, - }, - loading = new LoadingSpinner(), - placeholderContainer = new Container - { - RelativeSizeAxes = Axes.Both - }, - }; - } - - protected virtual void Reset() - { - cancelPendingWork(); - Scores = null; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - if (api != null) - { - apiState.BindTo(api.State); - apiState.BindValueChanged(state => - { - switch (state.NewValue) - { - case APIState.Online: - case APIState.Offline: - if (IsOnlineScope) - RefetchScores(); - - break; - } - }); - } - - RefetchScores(); - } - - public void RefetchScores() => Scheduler.AddOnce(refetchScores); - - private void refetchScores() - { - // don't display any scores or placeholder until the first Scores_Set has been called. - // this avoids scope changes flickering a "no scores" placeholder before initialisation of song select is finished. - if (!scoresLoadedOnce) return; - - cancelPendingWork(); - - PlaceholderState = PlaceholderState.Retrieving; - loading.Show(); - - getScoresRequest = FetchScores(scores => getScoresRequestCallback = Schedule(() => - { - Scores = scores.ToArray(); - PlaceholderState = Scores.Any() ? PlaceholderState.Successful : PlaceholderState.NoScores; - })); - - if (getScoresRequest == null) - return; - - getScoresRequest.Failure += e => getScoresRequestCallback = Schedule(() => - { - if (e is OperationCanceledException) - return; - - PlaceholderState = PlaceholderState.NetworkFailure; - }); - - api?.Queue(getScoresRequest); - } - - private void cancelPendingWork() - { - getScoresRequest?.Cancel(); - getScoresRequest = null; - - getScoresRequestCallback?.Cancel(); - getScoresRequestCallback = null; - } - - /// - /// Performs a fetch/refresh of scores to be displayed. - /// - /// A callback which should be called when fetching is completed. Scheduling is not required. - /// An responsible for the fetch operation. This will be queued and performed automatically. - protected abstract APIRequest FetchScores(Action> scoresCallback); - - private Placeholder currentPlaceholder; - private void replacePlaceholder(Placeholder placeholder) { if (placeholder != null && placeholder.Equals(currentPlaceholder)) @@ -342,6 +348,10 @@ namespace osu.Game.Online.Leaderboards currentPlaceholder = placeholder; } + #endregion + + #region Fade handling + protected override void UpdateAfterChildren() { base.UpdateAfterChildren(); @@ -384,8 +394,6 @@ namespace osu.Game.Online.Leaderboards } } - protected abstract LeaderboardScore CreateDrawableScore(TScoreInfo model, int index); - - protected abstract LeaderboardScore CreateDrawableTopScore(TScoreInfo model); + #endregion } } From 661fec7c8abd1fba873a5003092d9f5f45da475c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 21:59:29 +0900 Subject: [PATCH 777/996] Make score setter private --- .../Visual/SongSelect/TestSceneBeatmapLeaderboard.cs | 5 ++++- .../Visual/UserInterface/TestSceneDeleteLocalScore.cs | 3 --- osu.Game/Online/Leaderboards/Leaderboard.cs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index e31be1d51a..7ffab2eebc 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; @@ -100,7 +101,7 @@ namespace osu.Game.Tests.Visual.SongSelect public void TestGlobalScoresDisplay() { AddStep(@"Set scope", () => leaderboard.Scope = BeatmapLeaderboardScope.Global); - AddStep(@"New Scores", () => leaderboard.Scores = generateSampleScores(null)); + AddStep(@"New Scores", () => leaderboard.SetScores(generateSampleScores(null))); } [Test] @@ -422,6 +423,8 @@ namespace osu.Game.Tests.Visual.SongSelect { PlaceholderState = state; } + + public void SetScores(ICollection scores) => Scores = scores; } } } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index 4938fd1271..985ff6f251 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -128,9 +128,6 @@ namespace osu.Game.Tests.Visual.UserInterface scoreManager.Undelete(r.All().Where(s => s.DeletePending).ToList()); }); - leaderboard.Scores = null; - leaderboard.FinishTransforms(true); // After setting scores, we may be waiting for transforms to expire drawables - leaderboard.BeatmapInfo = beatmapInfo; leaderboard.RefetchScores(); // Required in the case that the beatmap hasn't changed }); diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index d843dcb2bb..0c58faa178 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -61,7 +61,7 @@ namespace osu.Game.Online.Leaderboards public ICollection Scores { get => scores; - set + protected set { scores = value; From a700ad38499ed2fbaed093e5de5570b27f5cd0c3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 22:04:34 +0900 Subject: [PATCH 778/996] Remove `scoresLoadedOnce` weirdness --- osu.Game/Online/Leaderboards/Leaderboard.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 0c58faa178..238e883d24 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -44,8 +44,6 @@ namespace osu.Game.Online.Leaderboards private ScheduledDelegate showScoresDelegate; private CancellationTokenSource showScoresCancellationSource; - private bool scoresLoadedOnce; - private APIRequest getScoresRequest; private ScheduledDelegate getScoresRequestCallback; @@ -65,8 +63,6 @@ namespace osu.Game.Online.Leaderboards { scores = value; - scoresLoadedOnce = true; - scrollFlow?.FadeOut(fade_duration, Easing.OutQuint).Expire(); scrollFlow = null; @@ -230,10 +226,6 @@ namespace osu.Game.Online.Leaderboards private void refetchScores() { - // don't display any scores or placeholder until the first Scores_Set has been called. - // this avoids scope changes flickering a "no scores" placeholder before initialisation of song select is finished. - if (!scoresLoadedOnce) return; - cancelPendingWork(); PlaceholderState = PlaceholderState.Retrieving; From c48e9f2bbdc2dba7360e5d0b7ef9e4dad12256e4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 22:13:52 +0900 Subject: [PATCH 779/996] Remove more unnecessary schedule/cancel logic --- osu.Game/Online/Leaderboards/Leaderboard.cs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 238e883d24..e8fc2d126e 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -3,10 +3,12 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Development; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; @@ -41,7 +43,6 @@ namespace osu.Game.Online.Leaderboards private readonly LoadingSpinner loading; - private ScheduledDelegate showScoresDelegate; private CancellationTokenSource showScoresCancellationSource; private APIRequest getScoresRequest; @@ -61,15 +62,16 @@ namespace osu.Game.Online.Leaderboards get => scores; protected set { + Debug.Assert(ThreadSafety.IsUpdateThread); + scores = value; scrollFlow?.FadeOut(fade_duration, Easing.OutQuint).Expire(); scrollFlow = null; - showScoresDelegate?.Cancel(); showScoresCancellationSource?.Cancel(); - if (scores == null || !scores.Any()) + if (scores?.Any() != true) { loading.Hide(); return; @@ -84,11 +86,11 @@ namespace osu.Game.Online.Leaderboards AutoSizeAxes = Axes.Y, Spacing = new Vector2(0f, 5f), Padding = new MarginPadding { Top = 10, Bottom = 5 }, + ChildrenEnumerable = scores.Select((s, index) => CreateDrawableScore(s, index + 1)) }; - scoreFlow.ChildrenEnumerable = scores.Select((s, index) => CreateDrawableScore(s, index + 1)); // schedule because we may not be loaded yet (LoadComponentAsync complains). - showScoresDelegate = Schedule(() => LoadComponentAsync(scoreFlow, _ => + LoadComponentAsync(scoreFlow, _ => { scrollContainer.Add(scrollFlow = scoreFlow); @@ -100,9 +102,9 @@ namespace osu.Game.Online.Leaderboards s.Show(); } - scrollContainer.ScrollTo(0f, false); + scrollContainer.ScrollToStart(false); loading.Hide(); - }, (showScoresCancellationSource = new CancellationTokenSource()).Token)); + }, (showScoresCancellationSource = new CancellationTokenSource()).Token); } } @@ -253,6 +255,9 @@ namespace osu.Game.Online.Leaderboards private void cancelPendingWork() { + showScoresCancellationSource?.Cancel(); + showScoresCancellationSource = null; + getScoresRequest?.Cancel(); getScoresRequest = null; From 13f445ddd5c49b2be0ccf2efeeff72c4703b1b8b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 22:28:13 +0900 Subject: [PATCH 780/996] Move score update code into own method --- osu.Game/Online/Leaderboards/Leaderboard.cs | 91 +++++++++++---------- 1 file changed, 48 insertions(+), 43 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index e8fc2d126e..0302568b34 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -33,6 +33,8 @@ namespace osu.Game.Online.Leaderboards /// The score model class. public abstract class Leaderboard : CompositeDrawable { + protected abstract bool IsOnlineScope { get; } + private const double fade_duration = 300; private readonly OsuScrollContainer scrollContainer; @@ -48,8 +50,6 @@ namespace osu.Game.Online.Leaderboards private APIRequest getScoresRequest; private ScheduledDelegate getScoresRequestCallback; - protected abstract bool IsOnlineScope { get; } - [Resolved(CanBeNull = true)] private IAPIProvider api { get; set; } @@ -62,49 +62,9 @@ namespace osu.Game.Online.Leaderboards get => scores; protected set { - Debug.Assert(ThreadSafety.IsUpdateThread); - scores = value; - scrollFlow?.FadeOut(fade_duration, Easing.OutQuint).Expire(); - scrollFlow = null; - - showScoresCancellationSource?.Cancel(); - - if (scores?.Any() != true) - { - loading.Hide(); - return; - } - - // ensure placeholder is hidden when displaying scores - PlaceholderState = PlaceholderState.Successful; - - var scoreFlow = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Spacing = new Vector2(0f, 5f), - Padding = new MarginPadding { Top = 10, Bottom = 5 }, - ChildrenEnumerable = scores.Select((s, index) => CreateDrawableScore(s, index + 1)) - }; - - // schedule because we may not be loaded yet (LoadComponentAsync complains). - LoadComponentAsync(scoreFlow, _ => - { - scrollContainer.Add(scrollFlow = scoreFlow); - - int i = 0; - - foreach (var s in scrollFlow.Children) - { - using (s.BeginDelayedSequence(i++ * 50)) - s.Show(); - } - - scrollContainer.ScrollToStart(false); - loading.Hide(); - }, (showScoresCancellationSource = new CancellationTokenSource()).Token); + updateScoresDrawables(); } } @@ -324,6 +284,51 @@ namespace osu.Game.Online.Leaderboards } } + private void updateScoresDrawables() + { + Debug.Assert(ThreadSafety.IsUpdateThread); + + scrollFlow?.FadeOut(fade_duration, Easing.OutQuint).Expire(); + scrollFlow = null; + + showScoresCancellationSource?.Cancel(); + + if (scores?.Any() != true) + { + loading.Hide(); + return; + } + + // ensure placeholder is hidden when displaying scores + PlaceholderState = PlaceholderState.Successful; + + var scoreFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(0f, 5f), + Padding = new MarginPadding { Top = 10, Bottom = 5 }, + ChildrenEnumerable = scores.Select((s, index) => CreateDrawableScore(s, index + 1)) + }; + + // schedule because we may not be loaded yet (LoadComponentAsync complains). + LoadComponentAsync(scoreFlow, _ => + { + scrollContainer.Add(scrollFlow = scoreFlow); + + int i = 0; + + foreach (var s in scrollFlow.Children) + { + using (s.BeginDelayedSequence(i++ * 50)) + s.Show(); + } + + scrollContainer.ScrollToStart(false); + loading.Hide(); + }, (showScoresCancellationSource = new CancellationTokenSource()).Token); + } + private void replacePlaceholder(Placeholder placeholder) { if (placeholder != null && placeholder.Equals(currentPlaceholder)) From 3d59bab7c61cea8d71b362bd21f9bc183c3baf7d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 22:47:45 +0900 Subject: [PATCH 781/996] Remove fetch callback logic completely --- osu.Game/Online/Leaderboards/Leaderboard.cs | 25 +--- .../Match/Components/MatchLeaderboard.cs | 6 +- .../Select/Leaderboards/BeatmapLeaderboard.cs | 130 ++++++++---------- 3 files changed, 67 insertions(+), 94 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 0302568b34..eeb814b4cb 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -3,12 +3,11 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Threading; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Development; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; @@ -178,9 +177,9 @@ namespace osu.Game.Online.Leaderboards /// /// Performs a fetch/refresh of scores to be displayed. /// - /// A callback which should be called when fetching is completed. Scheduling is not required. /// An responsible for the fetch operation. This will be queued and performed automatically. - protected abstract APIRequest FetchScores(Action> scoresCallback); + [CanBeNull] + protected abstract APIRequest FetchScores(); protected abstract LeaderboardScore CreateDrawableScore(TScoreInfo model, int index); @@ -193,11 +192,7 @@ namespace osu.Game.Online.Leaderboards PlaceholderState = PlaceholderState.Retrieving; loading.Show(); - getScoresRequest = FetchScores(scores => getScoresRequestCallback = Schedule(() => - { - Scores = scores.ToArray(); - PlaceholderState = Scores.Any() ? PlaceholderState.Successful : PlaceholderState.NoScores; - })); + getScoresRequest = FetchScores(); if (getScoresRequest == null) return; @@ -240,11 +235,6 @@ namespace osu.Game.Online.Leaderboards get => placeholderState; set { - if (value != PlaceholderState.Successful) - { - Reset(); - } - if (value == placeholderState) return; @@ -284,10 +274,8 @@ namespace osu.Game.Online.Leaderboards } } - private void updateScoresDrawables() + private void updateScoresDrawables() => Scheduler.Add(() => { - Debug.Assert(ThreadSafety.IsUpdateThread); - scrollFlow?.FadeOut(fade_duration, Easing.OutQuint).Expire(); scrollFlow = null; @@ -296,6 +284,7 @@ namespace osu.Game.Online.Leaderboards if (scores?.Any() != true) { loading.Hide(); + PlaceholderState = PlaceholderState.NoScores; return; } @@ -327,7 +316,7 @@ namespace osu.Game.Online.Leaderboards scrollContainer.ScrollToStart(false); loading.Hide(); }, (showScoresCancellationSource = new CancellationTokenSource()).Token); - } + }, false); private void replacePlaceholder(Placeholder placeholder) { diff --git a/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs b/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs index e4ec3ac4a5..5b60f64160 100644 --- a/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs +++ b/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.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. -using System; -using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Game.Online.API; @@ -32,7 +30,7 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components protected override bool IsOnlineScope => true; - protected override APIRequest FetchScores(Action> scoresCallback) + protected override APIRequest FetchScores() { if (roomId.Value == null) return null; @@ -41,7 +39,7 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components req.Success += r => { - scoresCallback?.Invoke(r.Leaderboard); + Scores = r.Leaderboard; TopScore = r.UserScore; }; diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 02e4e162dd..898b894541 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -25,12 +25,6 @@ namespace osu.Game.Screens.Select.Leaderboards { public Action ScoreSelected; - [Resolved] - private RulesetStore rulesets { get; set; } - - [Resolved] - private RealmAccess realm { get; set; } - private BeatmapInfo beatmapInfo; public BeatmapInfo BeatmapInfo @@ -52,13 +46,7 @@ namespace osu.Game.Screens.Select.Leaderboards beatmapInfo = value; Scores = null; - if (IsOnlineScope) - RefetchScores(); - else - { - if (IsLoaded) - refreshRealmSubscription(); - } + RefetchScores(); } } @@ -93,6 +81,14 @@ namespace osu.Game.Screens.Select.Leaderboards [Resolved] private IAPIProvider api { get; set; } + [Resolved] + private RulesetStore rulesets { get; set; } + + [Resolved] + private RealmAccess realm { get; set; } + + private IDisposable scoreSubscription; + [BackgroundDependencyLoader] private void load() { @@ -104,33 +100,6 @@ namespace osu.Game.Screens.Select.Leaderboards }; } - protected override void LoadComplete() - { - base.LoadComplete(); - - refreshRealmSubscription(); - } - - private IDisposable scoreSubscription; - - private void refreshRealmSubscription() - { - scoreSubscription?.Dispose(); - scoreSubscription = null; - - if (beatmapInfo == null) - return; - - scoreSubscription = realm.RegisterForNotifications(r => - r.All() - .Filter($"{nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} = $0", beatmapInfo.ID), - (_, changes, ___) => - { - if (!IsOnlineScope) - RefetchScores(); - }); - } - protected override void Reset() { base.Reset(); @@ -141,13 +110,11 @@ namespace osu.Game.Screens.Select.Leaderboards private CancellationTokenSource loadCancellationSource; - protected override APIRequest FetchScores(Action> scoresCallback) + protected override APIRequest FetchScores() { loadCancellationSource?.Cancel(); loadCancellationSource = new CancellationTokenSource(); - var cancellationToken = loadCancellationSource.Token; - var fetchBeatmapInfo = BeatmapInfo; if (fetchBeatmapInfo == null) @@ -158,31 +125,7 @@ namespace osu.Game.Screens.Select.Leaderboards if (Scope == BeatmapLeaderboardScope.Local) { - realm.Run(r => - { - var scores = r.All() - .AsEnumerable() - // TODO: update to use a realm filter directly (or at least figure out the beatmap part to reduce scope). - .Where(s => !s.DeletePending && s.BeatmapInfo.ID == fetchBeatmapInfo.ID && s.Ruleset.ShortName == ruleset.Value.ShortName); - - if (filterMods && !mods.Value.Any()) - { - // we need to filter out all scores that have any mods to get all local nomod scores - scores = scores.Where(s => !s.Mods.Any()); - } - else if (filterMods) - { - // otherwise find all the scores that have *any* of the currently selected mods (similar to how web applies mod filters) - // we're creating and using a string list representation of selected mods so that it can be translated into the DB query itself - var selectedMods = mods.Value.Select(m => m.Acronym); - scores = scores.Where(s => s.Mods.Any(m => selectedMods.Contains(m.Acronym))); - } - - scores = scores.Detach(); - - scoreManager.OrderByTotalScoreAsync(scores.ToArray(), cancellationToken) - .ContinueWith(ordered => scoresCallback?.Invoke(ordered.GetResultSafely()), TaskContinuationOptions.OnlyOnRanToCompletion); - }); + subscribeToLocalScores(); return null; } @@ -217,13 +160,13 @@ namespace osu.Game.Screens.Select.Leaderboards req.Success += r => { - scoreManager.OrderByTotalScoreAsync(r.Scores.Select(s => s.CreateScoreInfo(rulesets, fetchBeatmapInfo)).ToArray(), cancellationToken) + scoreManager.OrderByTotalScoreAsync(r.Scores.Select(s => s.CreateScoreInfo(rulesets, fetchBeatmapInfo)).ToArray(), loadCancellationSource.Token) .ContinueWith(task => Schedule(() => { - if (cancellationToken.IsCancellationRequested) + if (loadCancellationSource.IsCancellationRequested) return; - scoresCallback?.Invoke(task.GetResultSafely()); + Scores = task.GetResultSafely(); TopScore = r.UserScore?.CreateScoreInfo(rulesets, fetchBeatmapInfo); }), TaskContinuationOptions.OnlyOnRanToCompletion); }; @@ -241,10 +184,53 @@ namespace osu.Game.Screens.Select.Leaderboards Action = () => ScoreSelected?.Invoke(model) }; + private void subscribeToLocalScores() + { + scoreSubscription?.Dispose(); + scoreSubscription = null; + + if (beatmapInfo == null) + return; + + scoreSubscription = realm.RegisterForNotifications(r => + r.All().Filter($"{nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} == $0" + + $" AND {nameof(ScoreInfo.Ruleset)}.{nameof(RulesetInfo.ShortName)} == $1" + + $" AND {nameof(ScoreInfo.DeletePending)} == false" + , beatmapInfo.ID, ruleset.Value.ShortName), localScoresChanged); + } + + private void localScoresChanged(IRealmCollection sender, ChangeSet changes, Exception exception) + { + if (IsOnlineScope) + return; + + var scores = sender.AsEnumerable(); + + if (filterMods && !mods.Value.Any()) + { + // we need to filter out all scores that have any mods to get all local nomod scores + scores = scores.Where(s => !s.Mods.Any()); + } + else if (filterMods) + { + // otherwise find all the scores that have *any* of the currently selected mods (similar to how web applies mod filters) + // we're creating and using a string list representation of selected mods so that it can be translated into the DB query itself + var selectedMods = mods.Value.Select(m => m.Acronym); + scores = scores.Where(s => s.Mods.Any(m => selectedMods.Contains(m.Acronym))); + } + + scores = scores.Detach(); + + scoreManager.OrderByTotalScoreAsync(scores.ToArray(), loadCancellationSource.Token) + .ContinueWith(ordered => + { + Scores = ordered.GetResultSafely(); + }, TaskContinuationOptions.OnlyOnRanToCompletion); + } + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - scoreSubscription?.Dispose(); } } From daea13f49158b5c464f20c4b395a85bd46206991 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 23:14:26 +0900 Subject: [PATCH 782/996] Simplify flow of cancellation token --- osu.Game/Online/Leaderboards/Leaderboard.cs | 42 ++++++------ .../Match/Components/MatchLeaderboard.cs | 6 +- .../Select/Leaderboards/BeatmapLeaderboard.cs | 65 +++++++++---------- 3 files changed, 55 insertions(+), 58 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index eeb814b4cb..0d430f4903 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Threading; using JetBrains.Annotations; @@ -13,7 +14,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; -using osu.Framework.Threading; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; @@ -44,10 +44,9 @@ namespace osu.Game.Online.Leaderboards private readonly LoadingSpinner loading; - private CancellationTokenSource showScoresCancellationSource; + private CancellationTokenSource currentFetchCancellationSource; - private APIRequest getScoresRequest; - private ScheduledDelegate getScoresRequestCallback; + private APIRequest fetchScoresRequest; [Resolved(CanBeNull = true)] private IAPIProvider api { get; set; } @@ -62,7 +61,6 @@ namespace osu.Game.Online.Leaderboards protected set { scores = value; - updateScoresDrawables(); } } @@ -177,9 +175,10 @@ namespace osu.Game.Online.Leaderboards /// /// Performs a fetch/refresh of scores to be displayed. /// + /// /// An responsible for the fetch operation. This will be queued and performed automatically. [CanBeNull] - protected abstract APIRequest FetchScores(); + protected abstract APIRequest FetchScores(CancellationToken cancellationToken); protected abstract LeaderboardScore CreateDrawableScore(TScoreInfo model, int index); @@ -187,37 +186,36 @@ namespace osu.Game.Online.Leaderboards private void refetchScores() { - cancelPendingWork(); + Reset(); PlaceholderState = PlaceholderState.Retrieving; loading.Show(); - getScoresRequest = FetchScores(); + currentFetchCancellationSource = new CancellationTokenSource(); - if (getScoresRequest == null) + fetchScoresRequest = FetchScores(currentFetchCancellationSource.Token); + + if (fetchScoresRequest == null) return; - getScoresRequest.Failure += e => getScoresRequestCallback = Schedule(() => + fetchScoresRequest.Failure += e => Schedule(() => { - if (e is OperationCanceledException) + if (e is OperationCanceledException || currentFetchCancellationSource.IsCancellationRequested) return; PlaceholderState = PlaceholderState.NetworkFailure; }); - api?.Queue(getScoresRequest); + api?.Queue(fetchScoresRequest); } private void cancelPendingWork() { - showScoresCancellationSource?.Cancel(); - showScoresCancellationSource = null; + currentFetchCancellationSource?.Cancel(); + currentFetchCancellationSource = null; - getScoresRequest?.Cancel(); - getScoresRequest = null; - - getScoresRequestCallback?.Cancel(); - getScoresRequestCallback = null; + fetchScoresRequest?.Cancel(); + fetchScoresRequest = null; } #region Placeholder handling @@ -279,8 +277,6 @@ namespace osu.Game.Online.Leaderboards scrollFlow?.FadeOut(fade_duration, Easing.OutQuint).Expire(); scrollFlow = null; - showScoresCancellationSource?.Cancel(); - if (scores?.Any() != true) { loading.Hide(); @@ -288,6 +284,8 @@ namespace osu.Game.Online.Leaderboards return; } + Debug.Assert(!currentFetchCancellationSource.IsCancellationRequested); + // ensure placeholder is hidden when displaying scores PlaceholderState = PlaceholderState.Successful; @@ -315,7 +313,7 @@ namespace osu.Game.Online.Leaderboards scrollContainer.ScrollToStart(false); loading.Hide(); - }, (showScoresCancellationSource = new CancellationTokenSource()).Token); + }, currentFetchCancellationSource.Token); }, false); private void replacePlaceholder(Placeholder placeholder) diff --git a/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs b/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs index 5b60f64160..1945899a11 100644 --- a/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs +++ b/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Game.Online.API; @@ -30,7 +31,7 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components protected override bool IsOnlineScope => true; - protected override APIRequest FetchScores() + protected override APIRequest FetchScores(CancellationToken cancellationToken) { if (roomId.Value == null) return null; @@ -39,6 +40,9 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components req.Success += r => { + if (cancellationToken.IsCancellationRequested) + return; + Scores = r.Leaderboard; TopScore = r.UserScore; }; diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 898b894541..388395c9f9 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -108,13 +108,8 @@ namespace osu.Game.Screens.Select.Leaderboards protected override bool IsOnlineScope => Scope != BeatmapLeaderboardScope.Local; - private CancellationTokenSource loadCancellationSource; - - protected override APIRequest FetchScores() + protected override APIRequest FetchScores(CancellationToken cancellationToken) { - loadCancellationSource?.Cancel(); - loadCancellationSource = new CancellationTokenSource(); - var fetchBeatmapInfo = BeatmapInfo; if (fetchBeatmapInfo == null) @@ -125,7 +120,7 @@ namespace osu.Game.Screens.Select.Leaderboards if (Scope == BeatmapLeaderboardScope.Local) { - subscribeToLocalScores(); + subscribeToLocalScores(cancellationToken); return null; } @@ -160,10 +155,10 @@ namespace osu.Game.Screens.Select.Leaderboards req.Success += r => { - scoreManager.OrderByTotalScoreAsync(r.Scores.Select(s => s.CreateScoreInfo(rulesets, fetchBeatmapInfo)).ToArray(), loadCancellationSource.Token) + scoreManager.OrderByTotalScoreAsync(r.Scores.Select(s => s.CreateScoreInfo(rulesets, fetchBeatmapInfo)).ToArray(), cancellationToken) .ContinueWith(task => Schedule(() => { - if (loadCancellationSource.IsCancellationRequested) + if (cancellationToken.IsCancellationRequested) return; Scores = task.GetResultSafely(); @@ -184,7 +179,7 @@ namespace osu.Game.Screens.Select.Leaderboards Action = () => ScoreSelected?.Invoke(model) }; - private void subscribeToLocalScores() + private void subscribeToLocalScores(CancellationToken cancellationToken) { scoreSubscription?.Dispose(); scoreSubscription = null; @@ -197,35 +192,35 @@ namespace osu.Game.Screens.Select.Leaderboards + $" AND {nameof(ScoreInfo.Ruleset)}.{nameof(RulesetInfo.ShortName)} == $1" + $" AND {nameof(ScoreInfo.DeletePending)} == false" , beatmapInfo.ID, ruleset.Value.ShortName), localScoresChanged); - } - private void localScoresChanged(IRealmCollection sender, ChangeSet changes, Exception exception) - { - if (IsOnlineScope) - return; - - var scores = sender.AsEnumerable(); - - if (filterMods && !mods.Value.Any()) + void localScoresChanged(IRealmCollection sender, ChangeSet changes, Exception exception) { - // we need to filter out all scores that have any mods to get all local nomod scores - scores = scores.Where(s => !s.Mods.Any()); - } - else if (filterMods) - { - // otherwise find all the scores that have *any* of the currently selected mods (similar to how web applies mod filters) - // we're creating and using a string list representation of selected mods so that it can be translated into the DB query itself - var selectedMods = mods.Value.Select(m => m.Acronym); - scores = scores.Where(s => s.Mods.Any(m => selectedMods.Contains(m.Acronym))); - } + if (IsOnlineScope || cancellationToken.IsCancellationRequested) + return; - scores = scores.Detach(); + var scores = sender.AsEnumerable(); - scoreManager.OrderByTotalScoreAsync(scores.ToArray(), loadCancellationSource.Token) - .ContinueWith(ordered => - { - Scores = ordered.GetResultSafely(); - }, TaskContinuationOptions.OnlyOnRanToCompletion); + if (filterMods && !mods.Value.Any()) + { + // we need to filter out all scores that have any mods to get all local nomod scores + scores = scores.Where(s => !s.Mods.Any()); + } + else if (filterMods) + { + // otherwise find all the scores that have *any* of the currently selected mods (similar to how web applies mod filters) + // we're creating and using a string list representation of selected mods so that it can be translated into the DB query itself + var selectedMods = mods.Value.Select(m => m.Acronym); + scores = scores.Where(s => s.Mods.Any(m => selectedMods.Contains(m.Acronym))); + } + + scores = scores.Detach(); + + scoreManager.OrderByTotalScoreAsync(scores.ToArray(), cancellationToken) + .ContinueWith(ordered => + { + Scores = ordered.GetResultSafely(); + }, TaskContinuationOptions.OnlyOnRanToCompletion); + } } protected override void Dispose(bool isDisposing) From 0293d95f829f7b3596e472244e42ed040ee62717 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 23:17:06 +0900 Subject: [PATCH 783/996] Simplify `IsOnlineScope` usage --- osu.Game/Online/Leaderboards/Leaderboard.cs | 3 +++ osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 0d430f4903..6c2f0d9425 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -32,6 +32,9 @@ namespace osu.Game.Online.Leaderboards /// The score model class. public abstract class Leaderboard : CompositeDrawable { + /// + /// Whether the current scope should refetch in response to changes in API connectivity state. + /// protected abstract bool IsOnlineScope { get; } private const double fade_duration = 300; diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 388395c9f9..e55bd56e62 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -195,7 +195,7 @@ namespace osu.Game.Screens.Select.Leaderboards void localScoresChanged(IRealmCollection sender, ChangeSet changes, Exception exception) { - if (IsOnlineScope || cancellationToken.IsCancellationRequested) + if (cancellationToken.IsCancellationRequested) return; var scores = sender.AsEnumerable(); From d0b74a91fba148d56fbd0ac586e5f6e1159e2a92 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 29 Jan 2022 02:12:36 +0900 Subject: [PATCH 784/996] Fix edge cases with score drawable loading --- osu.Game/Online/Leaderboards/Leaderboard.cs | 45 ++++++++++--------- .../Select/Leaderboards/BeatmapLeaderboard.cs | 1 - 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 6c2f0d9425..a1f07c469a 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Threading; using JetBrains.Annotations; @@ -43,11 +42,12 @@ namespace osu.Game.Online.Leaderboards private readonly Container placeholderContainer; private readonly UserTopScoreContainer topScoreContainer; - private FillFlowContainer scrollFlow; + private FillFlowContainer scoreFlowContainer; private readonly LoadingSpinner loading; private CancellationTokenSource currentFetchCancellationSource; + private CancellationTokenSource currentScoresAsyncLoadCancellationSource; private APIRequest fetchScoresRequest; @@ -64,7 +64,7 @@ namespace osu.Game.Online.Leaderboards protected set { scores = value; - updateScoresDrawables(); + Scheduler.AddOnce(updateScoresDrawables); } } @@ -239,6 +239,8 @@ namespace osu.Game.Online.Leaderboards if (value == placeholderState) return; + loading.Hide(); + switch (placeholderState = value) { case PlaceholderState.NetworkFailure: @@ -275,40 +277,41 @@ namespace osu.Game.Online.Leaderboards } } - private void updateScoresDrawables() => Scheduler.Add(() => + private void updateScoresDrawables() { - scrollFlow?.FadeOut(fade_duration, Easing.OutQuint).Expire(); - scrollFlow = null; + currentScoresAsyncLoadCancellationSource?.Cancel(); + currentScoresAsyncLoadCancellationSource = new CancellationTokenSource(); + + scoreFlowContainer? + .FadeOut(fade_duration, Easing.OutQuint) + .Expire(); + scoreFlowContainer = null; + + loading.Hide(); if (scores?.Any() != true) { - loading.Hide(); PlaceholderState = PlaceholderState.NoScores; return; } - Debug.Assert(!currentFetchCancellationSource.IsCancellationRequested); - // ensure placeholder is hidden when displaying scores PlaceholderState = PlaceholderState.Successful; - var scoreFlow = new FillFlowContainer + LoadComponentAsync(new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Spacing = new Vector2(0f, 5f), Padding = new MarginPadding { Top = 10, Bottom = 5 }, ChildrenEnumerable = scores.Select((s, index) => CreateDrawableScore(s, index + 1)) - }; - - // schedule because we may not be loaded yet (LoadComponentAsync complains). - LoadComponentAsync(scoreFlow, _ => + }, newFlow => { - scrollContainer.Add(scrollFlow = scoreFlow); + scrollContainer.Add(scoreFlowContainer = newFlow); int i = 0; - foreach (var s in scrollFlow.Children) + foreach (var s in scoreFlowContainer.Children) { using (s.BeginDelayedSequence(i++ * 50)) s.Show(); @@ -316,8 +319,8 @@ namespace osu.Game.Online.Leaderboards scrollContainer.ScrollToStart(false); loading.Hide(); - }, currentFetchCancellationSource.Token); - }, false); + }, currentScoresAsyncLoadCancellationSource.Token); + } private void replacePlaceholder(Placeholder placeholder) { @@ -354,12 +357,12 @@ namespace osu.Game.Online.Leaderboards if (!scrollContainer.IsScrolledToEnd()) fadeBottom -= LeaderboardScore.HEIGHT; - if (scrollFlow == null) + if (scoreFlowContainer == null) return; - foreach (var c in scrollFlow.Children) + foreach (var c in scoreFlowContainer.Children) { - float topY = c.ToSpaceOfOtherDrawable(Vector2.Zero, scrollFlow).Y; + float topY = c.ToSpaceOfOtherDrawable(Vector2.Zero, scoreFlowContainer).Y; float bottomY = topY + LeaderboardScore.HEIGHT; bool requireBottomFade = bottomY >= fadeBottom; diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index e55bd56e62..31868624bb 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -121,7 +121,6 @@ namespace osu.Game.Screens.Select.Leaderboards if (Scope == BeatmapLeaderboardScope.Local) { subscribeToLocalScores(cancellationToken); - return null; } From 6f54f8ad7897cc9bfa31e3ad00660eff3efeef59 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 29 Jan 2022 12:18:34 +0900 Subject: [PATCH 785/996] Add more safety around `CancellationToken` usage --- osu.Game/Online/Leaderboards/Leaderboard.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index a1f07c469a..cc6d7a7b62 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -3,11 +3,13 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Threading; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Development; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; @@ -189,6 +191,8 @@ namespace osu.Game.Online.Leaderboards private void refetchScores() { + Debug.Assert(ThreadSafety.IsUpdateThread); + Reset(); PlaceholderState = PlaceholderState.Retrieving; @@ -215,10 +219,8 @@ namespace osu.Game.Online.Leaderboards private void cancelPendingWork() { currentFetchCancellationSource?.Cancel(); - currentFetchCancellationSource = null; - + currentScoresAsyncLoadCancellationSource?.Cancel(); fetchScoresRequest?.Cancel(); - fetchScoresRequest = null; } #region Placeholder handling @@ -280,7 +282,6 @@ namespace osu.Game.Online.Leaderboards private void updateScoresDrawables() { currentScoresAsyncLoadCancellationSource?.Cancel(); - currentScoresAsyncLoadCancellationSource = new CancellationTokenSource(); scoreFlowContainer? .FadeOut(fade_duration, Easing.OutQuint) @@ -319,7 +320,7 @@ namespace osu.Game.Online.Leaderboards scrollContainer.ScrollToStart(false); loading.Hide(); - }, currentScoresAsyncLoadCancellationSource.Token); + }, (currentScoresAsyncLoadCancellationSource = new CancellationTokenSource()).Token); } private void replacePlaceholder(Placeholder placeholder) From a915b9cd3093bd2ede73c3d6ef9470ff12967dc1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 29 Jan 2022 23:12:57 +0900 Subject: [PATCH 786/996] Fix occasional failures in `TestSceneDeleteLocalScore` --- .../Visual/UserInterface/TestSceneDeleteLocalScore.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index 985ff6f251..da4cf9c6e3 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -135,11 +135,9 @@ namespace osu.Game.Tests.Visual.UserInterface [SetUpSteps] public void SetupSteps() { - // Ensure the leaderboard has finished async-loading drawables - AddUntilStep("wait for drawables", () => leaderboard.ChildrenOfType().Any()); - // Ensure the leaderboard items have finished showing up AddStep("finish transforms", () => leaderboard.FinishTransforms(true)); + AddUntilStep("wait for drawables", () => leaderboard.ChildrenOfType().Any()); } [Test] From 9861c50b33f8fa036e8bdf62ff1f646cab55c8ef Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jan 2022 00:03:22 +0900 Subject: [PATCH 787/996] Remove pointless tests that no longer show anything valid --- .../Visual/SongSelect/TestSceneBeatmapLeaderboard.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 7ffab2eebc..b4b66e8afa 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -122,13 +122,6 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep(@"None selected", () => leaderboard.SetRetrievalState(PlaceholderState.NoneSelected)); } - [Test] - public void TestBeatmapStates() - { - foreach (BeatmapOnlineStatus status in Enum.GetValues(typeof(BeatmapOnlineStatus))) - AddStep($"{status} beatmap", () => showBeatmapWithStatus(status)); - } - private void showPersonalBestWithNullPosition() { leaderboard.TopScore = new ScoreInfo From e408d8ef0e1ced02cf3842c95318814e987c3303 Mon Sep 17 00:00:00 2001 From: Nitrous Date: Sat, 29 Jan 2022 23:19:51 +0800 Subject: [PATCH 788/996] rename `Frames` to `ReplayFrames` --- .../Mods/TestSceneOsuModAlternate.cs | 8 ++++---- osu.Game/Tests/Visual/ModTestScene.cs | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs index 7b3eaa7ffd..de1f61a0bd 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods }, }, }, - Frames = new List + ReplayFrames = new List { new OsuReplayFrame(500, new Vector2(200), OsuAction.LeftButton), new OsuReplayFrame(501, new Vector2(200)), @@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods }, }, }, - Frames = new List + ReplayFrames = new List { new OsuReplayFrame(500, new Vector2(100), OsuAction.LeftButton), new OsuReplayFrame(501, new Vector2(100)), @@ -108,7 +108,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods }, }, }, - Frames = new List + ReplayFrames = new List { new OsuReplayFrame(500, new Vector2(100), OsuAction.LeftButton), new OsuReplayFrame(501, new Vector2(100)), @@ -142,7 +142,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods } } }, - Frames = new List + ReplayFrames = new List { new OsuReplayFrame(500, new Vector2(100), OsuAction.LeftButton), new OsuReplayFrame(501, new Vector2(100)), diff --git a/osu.Game/Tests/Visual/ModTestScene.cs b/osu.Game/Tests/Visual/ModTestScene.cs index 96fafa8727..2505864d59 100644 --- a/osu.Game/Tests/Visual/ModTestScene.cs +++ b/osu.Game/Tests/Visual/ModTestScene.cs @@ -72,14 +72,14 @@ namespace osu.Game.Tests.Visual protected override void PrepareReplay() { - if (currentTestData.Autoplay && currentTestData.Frames?.Count > 0) - throw new InvalidOperationException(@$"{nameof(ModTestData.Autoplay)} must be false when {nameof(ModTestData.Frames)} is specified."); + if (currentTestData.Autoplay && currentTestData.ReplayFrames?.Count > 0) + throw new InvalidOperationException(@$"{nameof(ModTestData.Autoplay)} must be false when {nameof(ModTestData.ReplayFrames)} is specified."); - if (currentTestData.Frames != null) + if (currentTestData.ReplayFrames != null) { DrawableRuleset?.SetReplayScore(new Score { - Replay = new Replay { Frames = currentTestData.Frames }, + Replay = new Replay { Frames = currentTestData.ReplayFrames }, ScoreInfo = new ScoreInfo { User = new APIUser { Username = @"Test" } }, }); } @@ -99,7 +99,7 @@ namespace osu.Game.Tests.Visual /// The frames to use for replay. must be set to false. /// [CanBeNull] - public List Frames; + public List ReplayFrames; /// /// The beatmap for this test case. From 535216a0d3f5c063d5e5519ff4cd0533c3e72df5 Mon Sep 17 00:00:00 2001 From: Nitrous Date: Sat, 29 Jan 2022 23:20:31 +0800 Subject: [PATCH 789/996] rename `CanIntercept` to `ShouldAlternate` --- osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs index 9eeb060135..876742caba 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; @@ -25,11 +26,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay) }; public override ModType Type => ModType.DifficultyIncrease; public override IconUsage? Icon => FontAwesome.Solid.Keyboard; - - /// - /// Whether incoming input must be checked by before it is passed to gameplay. - /// - public bool CanIntercept => !isBreakTime.Value && introEnded; + public bool ShouldAlternate => !isBreakTime.Value && introEnded; private bool introEnded; private double earliestStartTime; @@ -86,7 +83,7 @@ namespace osu.Game.Rulesets.Osu.Mods } public bool OnPressed(KeyBindingPressEvent e) - => mod.CanIntercept && mod.onPressed(e.Action); + => mod.ShouldAlternate && mod.onPressed(e.Action); public void OnReleased(KeyBindingReleaseEvent e) { From 40f43344f16056d66d6713078ecc3b5ccf47372b Mon Sep 17 00:00:00 2001 From: Nitrous Date: Sat, 29 Jan 2022 23:31:26 +0800 Subject: [PATCH 790/996] remove unused using --- osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs index 876742caba..db8bd217f6 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; From 51acf79935724e5786ecd1b515f07657071b65fd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jan 2022 02:29:51 +0900 Subject: [PATCH 791/996] Change test exposure to property instead of method --- .../Visual/SongSelect/TestSceneBeatmapLeaderboard.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index b4b66e8afa..91ec1de3ad 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -101,7 +101,7 @@ namespace osu.Game.Tests.Visual.SongSelect public void TestGlobalScoresDisplay() { AddStep(@"Set scope", () => leaderboard.Scope = BeatmapLeaderboardScope.Global); - AddStep(@"New Scores", () => leaderboard.SetScores(generateSampleScores(null))); + AddStep(@"New Scores", () => leaderboard.Scores = generateSampleScores(null)); } [Test] @@ -417,7 +417,11 @@ namespace osu.Game.Tests.Visual.SongSelect PlaceholderState = state; } - public void SetScores(ICollection scores) => Scores = scores; + public new ICollection Scores + { + get => base.Scores; + set => base.Scores = value; + } } } } From 3d771c0fc7ed0f292ec28c0257ff80c122bcd058 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jan 2022 02:34:31 +0900 Subject: [PATCH 792/996] Remove unnecessary `loading` hide call from `PlaceholderState_Set` and add more assertiveness --- osu.Game/Online/Leaderboards/Leaderboard.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index cc6d7a7b62..e20c8b5007 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -241,9 +241,11 @@ namespace osu.Game.Online.Leaderboards if (value == placeholderState) return; - loading.Hide(); + placeholderState = value; - switch (placeholderState = value) + Debug.Assert(placeholderState != PlaceholderState.Successful || scores?.Any() == true); + + switch (placeholderState) { case PlaceholderState.NetworkFailure: replacePlaceholder(new ClickablePlaceholder(@"Couldn't fetch scores!", FontAwesome.Solid.Sync) From d3cb910cf82cb208bf3ae6c3059e8b851107770e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jan 2022 02:36:50 +0900 Subject: [PATCH 793/996] Convert inline math to not so inline to make operation more explicit --- osu.Game/Online/Leaderboards/Leaderboard.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index e20c8b5007..460869fa54 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -312,12 +312,14 @@ namespace osu.Game.Online.Leaderboards { scrollContainer.Add(scoreFlowContainer = newFlow); - int i = 0; + double delay = 0; foreach (var s in scoreFlowContainer.Children) { - using (s.BeginDelayedSequence(i++ * 50)) + using (s.BeginDelayedSequence(delay)) s.Show(); + + delay += 50; } scrollContainer.ScrollToStart(false); From d21464ea61b8efcf53eb867970b8bf4a5013a109 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jan 2022 02:54:51 +0900 Subject: [PATCH 794/996] Fix assertions to work in both directions --- .../Visual/SongSelect/TestSceneBeatmapLeaderboard.cs | 1 + osu.Game/Online/Leaderboards/Leaderboard.cs | 9 +++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 91ec1de3ad..31b7b9fa8d 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -414,6 +414,7 @@ namespace osu.Game.Tests.Visual.SongSelect { public void SetRetrievalState(PlaceholderState state) { + Scores = null; PlaceholderState = state; } diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 460869fa54..7b596d8381 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -243,11 +243,10 @@ namespace osu.Game.Online.Leaderboards placeholderState = value; - Debug.Assert(placeholderState != PlaceholderState.Successful || scores?.Any() == true); - switch (placeholderState) { case PlaceholderState.NetworkFailure: + Debug.Assert(scores?.Any() != true); replacePlaceholder(new ClickablePlaceholder(@"Couldn't fetch scores!", FontAwesome.Solid.Sync) { Action = RefetchScores @@ -255,26 +254,32 @@ namespace osu.Game.Online.Leaderboards break; case PlaceholderState.NoneSelected: + Debug.Assert(scores?.Any() != true); replacePlaceholder(new MessagePlaceholder(@"Please select a beatmap!")); break; case PlaceholderState.Unavailable: + Debug.Assert(scores?.Any() != true); replacePlaceholder(new MessagePlaceholder(@"Leaderboards are not available for this beatmap!")); break; case PlaceholderState.NoScores: + Debug.Assert(scores?.Any() != true); replacePlaceholder(new MessagePlaceholder(@"No records yet!")); break; case PlaceholderState.NotLoggedIn: + Debug.Assert(scores?.Any() != true); replacePlaceholder(new LoginPlaceholder(@"Please sign in to view online leaderboards!")); break; case PlaceholderState.NotSupporter: + Debug.Assert(scores?.Any() != true); replacePlaceholder(new MessagePlaceholder(@"Please invest in an osu!supporter tag to view this leaderboard!")); break; default: + Debug.Assert(scores?.Any() == true); replacePlaceholder(null); break; } From 9b573fbc2bdc044d0702179fa9b94a82a0fe0648 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jan 2022 02:58:53 +0900 Subject: [PATCH 795/996] Add missing entries to `switch` statement and guard against out of range --- osu.Game/Online/Leaderboards/Leaderboard.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 7b596d8381..4134046320 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -278,10 +278,18 @@ namespace osu.Game.Online.Leaderboards replacePlaceholder(new MessagePlaceholder(@"Please invest in an osu!supporter tag to view this leaderboard!")); break; - default: + case PlaceholderState.Retrieving: + Debug.Assert(scores?.Any() != true); + replacePlaceholder(null); + break; + + case PlaceholderState.Successful: Debug.Assert(scores?.Any() == true); replacePlaceholder(null); break; + + default: + throw new ArgumentOutOfRangeException(); } } } From 06660ff96019000f7b8302d24fdd0cf28176f460 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jan 2022 03:02:56 +0900 Subject: [PATCH 796/996] Fix null beatmap in test scene --- osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 31b7b9fa8d..77fe3be933 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -101,7 +101,7 @@ namespace osu.Game.Tests.Visual.SongSelect public void TestGlobalScoresDisplay() { AddStep(@"Set scope", () => leaderboard.Scope = BeatmapLeaderboardScope.Global); - AddStep(@"New Scores", () => leaderboard.Scores = generateSampleScores(null)); + AddStep(@"New Scores", () => leaderboard.Scores = generateSampleScores(new BeatmapInfo())); } [Test] From dad9cc931524f6627c507d5831c9cbd3a79ba6fb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jan 2022 03:06:29 +0900 Subject: [PATCH 797/996] Ensure `Reset`/`Scores_Set` run inline where possible --- osu.Game/Online/Leaderboards/Leaderboard.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 4134046320..0ad9f46130 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -66,7 +66,7 @@ namespace osu.Game.Online.Leaderboards protected set { scores = value; - Scheduler.AddOnce(updateScoresDrawables); + Scheduler.Add(updateScoresDrawables, false); } } From b434e29a7c4f4883cb55498678933dc44b4ec185 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jan 2022 03:10:01 +0900 Subject: [PATCH 798/996] Move loading hide operation inside early return to ensure not hidden too early It should only be hidden after the async load completes. --- osu.Game/Online/Leaderboards/Leaderboard.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 0ad9f46130..8cf20ea8d1 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -303,10 +303,9 @@ namespace osu.Game.Online.Leaderboards .Expire(); scoreFlowContainer = null; - loading.Hide(); - if (scores?.Any() != true) { + loading.Hide(); PlaceholderState = PlaceholderState.NoScores; return; } From 4f4f60248faad019bc3c0501e42de854fea5dbdc Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 11 Aug 2021 12:16:25 +0300 Subject: [PATCH 799/996] Add failing test case --- .../TestSceneMultiSpectatorScreen.cs | 51 +++++++++++++++++-- .../Spectate/MultiSpectatorScreen.cs | 5 +- .../Multiplayer/Spectate/PlayerArea.cs | 13 ++++- osu.Game/Screens/Play/Player.cs | 7 +++ 4 files changed, 71 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index b84f7760e4..56cb6036c7 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -347,19 +347,44 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert($"{PLAYER_1_ID} score quit still set", () => getLeaderboardScore(PLAYER_1_ID).HasQuit.Value); } - private void loadSpectateScreen(bool waitForPlayerLoad = true) + /// + /// Tests spectating with a gameplay start time set to a negative value. + /// Simulating beatmaps with high or negative time storyboard elements. + /// + [Test] + public void TestNegativeGameplayStartTime() { - AddStep("load screen", () => + start(PLAYER_1_ID); + + loadSpectateScreen(false, -500); + + // to ensure negative gameplay start time does not affect spectator, send frames exactly after StartGameplay(). + // (similar to real spectating sessions in which the first frames get sent between StartGameplay() and player load complete) + AddStep("send frames at gameplay start", () => getInstance(PLAYER_1_ID).OnGameplayStarted += () => SpectatorClient.SendFrames(PLAYER_1_ID, 100)); + + AddUntilStep("wait for player load", () => spectatorScreen.AllPlayersLoaded); + + AddWaitStep("wait for progression", 3); + + assertNotCatchingUp(PLAYER_1_ID); + assertRunning(PLAYER_1_ID); + } + + private void loadSpectateScreen(bool waitForPlayerLoad = true, double? gameplayStartTime = null) + { + AddStep(!gameplayStartTime.HasValue ? "load screen" : $"load screen (start = {gameplayStartTime}ms)", () => { Beatmap.Value = beatmapManager.GetWorkingBeatmap(importedBeatmap); Ruleset.Value = importedBeatmap.Ruleset; - LoadScreen(spectatorScreen = new MultiSpectatorScreen(playingUsers.ToArray())); + LoadScreen(spectatorScreen = new TestMultiSpectatorScreen(playingUsers.ToArray(), gameplayStartTime)); }); AddUntilStep("wait for screen load", () => spectatorScreen.LoadState == LoadState.Loaded && (!waitForPlayerLoad || spectatorScreen.AllPlayersLoaded)); } + private void start(int userId, int? beatmapId = null) => start(new[] { userId }, beatmapId); + private void start(int[] userIds, int? beatmapId = null) { AddStep("start play", () => @@ -419,6 +444,12 @@ namespace osu.Game.Tests.Visual.Multiplayer private void assertMuted(int userId, bool muted) => AddAssert($"{userId} {(muted ? "is" : "is not")} muted", () => getInstance(userId).Mute == muted); + private void assertRunning(int userId) + => AddAssert($"{userId} clock running", () => getInstance(userId).GameplayClock.IsRunning); + + private void assertNotCatchingUp(int userId) + => AddAssert($"{userId} in sync", () => !getInstance(userId).GameplayClock.IsCatchingUp); + private void waitForCatchup(int userId) => AddUntilStep($"{userId} not catching up", () => !getInstance(userId).GameplayClock.IsCatchingUp); @@ -429,5 +460,19 @@ namespace osu.Game.Tests.Visual.Multiplayer private GameplayLeaderboardScore getLeaderboardScore(int userId) => spectatorScreen.ChildrenOfType().Single(s => s.User?.Id == userId); private int[] getPlayerIds(int count) => Enumerable.Range(PLAYER_1_ID, count).ToArray(); + + private class TestMultiSpectatorScreen : MultiSpectatorScreen + { + private readonly double? gameplayStartTime; + + public TestMultiSpectatorScreen(MultiplayerRoomUser[] users, double? gameplayStartTime = null) + : base(users) + { + this.gameplayStartTime = gameplayStartTime; + } + + protected override MasterGameplayClockContainer CreateMasterGameplayClockContainer(WorkingBeatmap beatmap) + => new MasterGameplayClockContainer(beatmap, gameplayStartTime ?? 0, gameplayStartTime.HasValue); + } } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index 7350408eba..4646f42d63 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -9,6 +9,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Online.Multiplayer; using osu.Game.Online.Spectator; @@ -68,7 +69,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate Container leaderboardContainer; Container scoreDisplayContainer; - masterClockContainer = new MasterGameplayClockContainer(Beatmap.Value, 0); + masterClockContainer = CreateMasterGameplayClockContainer(Beatmap.Value); InternalChildren = new[] { @@ -235,5 +236,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate return base.OnBackButton(); } + + protected virtual MasterGameplayClockContainer CreateMasterGameplayClockContainer(WorkingBeatmap beatmap) => new MasterGameplayClockContainer(beatmap, 0); } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs index 48f153ecbe..f5a6777a62 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs @@ -24,6 +24,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate /// public class PlayerArea : CompositeDrawable { + /// + /// Raised after is called on . + /// + public event Action OnGameplayStarted; + /// /// Whether a is loaded in the area. /// @@ -93,7 +98,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate } }; - stack.Push(new MultiSpectatorPlayerLoader(Score, () => new MultiSpectatorPlayer(Score, GameplayClock))); + stack.Push(new MultiSpectatorPlayerLoader(Score, () => + { + var player = new MultiSpectatorPlayer(Score, GameplayClock); + player.OnGameplayStarted += OnGameplayStarted; + return player; + })); + loadingLayer.Hide(); } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index cfca2d0a3d..0312789b12 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -45,6 +45,11 @@ namespace osu.Game.Screens.Play /// public const double RESULTS_DISPLAY_DELAY = 1000.0; + /// + /// Raised after is called. + /// + public event Action OnGameplayStarted; + public override bool AllowBackButton => false; // handled by HoldForMenuButton protected override UserActivity InitialActivity => new UserActivity.InSoloGame(Beatmap.Value.BeatmapInfo, Ruleset.Value); @@ -958,7 +963,9 @@ namespace osu.Game.Screens.Play updateGameplayState(); GameplayClockContainer.FadeInFromZero(750, Easing.OutQuint); + StartGameplay(); + OnGameplayStarted?.Invoke(); } /// From 3ec193d47e9655de747d6de21b042e36ed5f8211 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 29 Jan 2022 20:17:57 +0300 Subject: [PATCH 800/996] Fix spectator clock container incorrectly starting catch-up clock --- .../OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs | 2 ++ osu.Game/Screens/Play/GameplayClockContainer.cs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs index ececa1e497..615bd41f3f 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs @@ -55,6 +55,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate public SpectatorGameplayClockContainer([NotNull] IClock sourceClock) : base(sourceClock) { + // the container should initially be in a stopped state until the catch-up clock is started by the sync manager. + Stop(); } protected override void Update() diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 0c9b827a41..0fd524f976 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -101,7 +101,7 @@ namespace osu.Game.Screens.Play /// /// Stops gameplay. /// - public virtual void Stop() => IsPaused.Value = true; + public void Stop() => IsPaused.Value = true; /// /// Resets this and the source to an initial state ready for gameplay. From c401629dd84e8b91a9389fc85c16cc84f55aa013 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jan 2022 03:37:57 +0900 Subject: [PATCH 801/996] Also refactor `placeholder` logic to make more sense --- .../SongSelect/TestSceneBeatmapLeaderboard.cs | 27 +-- osu.Game/Online/Leaderboards/Leaderboard.cs | 155 +++++++++--------- ...olderState.cs => LeaderboardErrorState.cs} | 5 +- .../Select/Leaderboards/BeatmapLeaderboard.cs | 15 +- 4 files changed, 86 insertions(+), 116 deletions(-) rename osu.Game/Online/Leaderboards/{PlaceholderState.cs => LeaderboardErrorState.cs} (82%) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 77fe3be933..969bafa98b 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -114,12 +114,12 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestPlaceholderStates() { - AddStep(@"Empty Scores", () => leaderboard.SetRetrievalState(PlaceholderState.NoScores)); - AddStep(@"Network failure", () => leaderboard.SetRetrievalState(PlaceholderState.NetworkFailure)); - AddStep(@"No supporter", () => leaderboard.SetRetrievalState(PlaceholderState.NotSupporter)); - AddStep(@"Not logged in", () => leaderboard.SetRetrievalState(PlaceholderState.NotLoggedIn)); - AddStep(@"Unavailable", () => leaderboard.SetRetrievalState(PlaceholderState.Unavailable)); - AddStep(@"None selected", () => leaderboard.SetRetrievalState(PlaceholderState.NoneSelected)); + AddStep(@"Empty Scores", () => leaderboard.SetErrorState(LeaderboardErrorState.NoScores)); + AddStep(@"Network failure", () => leaderboard.SetErrorState(LeaderboardErrorState.NetworkFailure)); + AddStep(@"No supporter", () => leaderboard.SetErrorState(LeaderboardErrorState.NotSupporter)); + AddStep(@"Not logged in", () => leaderboard.SetErrorState(LeaderboardErrorState.NotLoggedIn)); + AddStep(@"Unavailable", () => leaderboard.SetErrorState(LeaderboardErrorState.Unavailable)); + AddStep(@"None selected", () => leaderboard.SetErrorState(LeaderboardErrorState.NoneSelected)); } private void showPersonalBestWithNullPosition() @@ -401,22 +401,9 @@ namespace osu.Game.Tests.Visual.SongSelect }; } - private void showBeatmapWithStatus(BeatmapOnlineStatus status) - { - leaderboard.BeatmapInfo = new BeatmapInfo - { - OnlineID = 1113057, - Status = status, - }; - } - private class FailableLeaderboard : BeatmapLeaderboard { - public void SetRetrievalState(PlaceholderState state) - { - Scores = null; - PlaceholderState = state; - } + public new void SetErrorState(LeaderboardErrorState errorState) => base.SetErrorState(errorState); public new ICollection Scores { diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 8cf20ea8d1..2e552f9774 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -53,6 +53,8 @@ namespace osu.Game.Online.Leaderboards private APIRequest fetchScoresRequest; + private LeaderboardErrorState errorState; + [Resolved(CanBeNull = true)] private IAPIProvider api { get; set; } @@ -169,14 +171,37 @@ namespace osu.Game.Online.Leaderboards RefetchScores(); } + /// + /// Perform a full refetch of scores using current criteria. + /// public void RefetchScores() => Scheduler.AddOnce(refetchScores); + /// + /// Reset the leaderboard into an empty state. + /// protected virtual void Reset() { cancelPendingWork(); Scores = null; } + /// + /// Call when a retrieval or display failure happened to show a relevant message to the user. + /// + /// The state to display. + protected void SetErrorState(LeaderboardErrorState errorState) + { + switch (errorState) + { + case LeaderboardErrorState.NoError: + throw new InvalidOperationException($"State {errorState} cannot be set by a leaderboard implementation."); + } + + Debug.Assert(scores?.Any() != true); + + setErrorState(errorState); + } + /// /// Performs a fetch/refresh of scores to be displayed. /// @@ -195,7 +220,7 @@ namespace osu.Game.Online.Leaderboards Reset(); - PlaceholderState = PlaceholderState.Retrieving; + setErrorState(LeaderboardErrorState.NoError); loading.Show(); currentFetchCancellationSource = new CancellationTokenSource(); @@ -210,7 +235,7 @@ namespace osu.Game.Online.Leaderboards if (e is OperationCanceledException || currentFetchCancellationSource.IsCancellationRequested) return; - PlaceholderState = PlaceholderState.NetworkFailure; + SetErrorState(LeaderboardErrorState.NetworkFailure); }); api?.Queue(fetchScoresRequest); @@ -223,77 +248,6 @@ namespace osu.Game.Online.Leaderboards fetchScoresRequest?.Cancel(); } - #region Placeholder handling - - private Placeholder currentPlaceholder; - - private PlaceholderState placeholderState; - - /// - /// Update the placeholder visibility. - /// Setting this to anything other than PlaceholderState.Successful will cancel all existing retrieval requests and hide scores. - /// - protected PlaceholderState PlaceholderState - { - get => placeholderState; - set - { - if (value == placeholderState) - return; - - placeholderState = value; - - switch (placeholderState) - { - case PlaceholderState.NetworkFailure: - Debug.Assert(scores?.Any() != true); - replacePlaceholder(new ClickablePlaceholder(@"Couldn't fetch scores!", FontAwesome.Solid.Sync) - { - Action = RefetchScores - }); - break; - - case PlaceholderState.NoneSelected: - Debug.Assert(scores?.Any() != true); - replacePlaceholder(new MessagePlaceholder(@"Please select a beatmap!")); - break; - - case PlaceholderState.Unavailable: - Debug.Assert(scores?.Any() != true); - replacePlaceholder(new MessagePlaceholder(@"Leaderboards are not available for this beatmap!")); - break; - - case PlaceholderState.NoScores: - Debug.Assert(scores?.Any() != true); - replacePlaceholder(new MessagePlaceholder(@"No records yet!")); - break; - - case PlaceholderState.NotLoggedIn: - Debug.Assert(scores?.Any() != true); - replacePlaceholder(new LoginPlaceholder(@"Please sign in to view online leaderboards!")); - break; - - case PlaceholderState.NotSupporter: - Debug.Assert(scores?.Any() != true); - replacePlaceholder(new MessagePlaceholder(@"Please invest in an osu!supporter tag to view this leaderboard!")); - break; - - case PlaceholderState.Retrieving: - Debug.Assert(scores?.Any() != true); - replacePlaceholder(null); - break; - - case PlaceholderState.Successful: - Debug.Assert(scores?.Any() == true); - replacePlaceholder(null); - break; - - default: - throw new ArgumentOutOfRangeException(); - } - } - } - private void updateScoresDrawables() { currentScoresAsyncLoadCancellationSource?.Cancel(); @@ -305,13 +259,14 @@ namespace osu.Game.Online.Leaderboards if (scores?.Any() != true) { + SetErrorState(LeaderboardErrorState.NoScores); loading.Hide(); - PlaceholderState = PlaceholderState.NoScores; return; } // ensure placeholder is hidden when displaying scores - PlaceholderState = PlaceholderState.Successful; + setErrorState(LeaderboardErrorState.NoError); + loading.Show(); LoadComponentAsync(new FillFlowContainer { @@ -339,25 +294,61 @@ namespace osu.Game.Online.Leaderboards }, (currentScoresAsyncLoadCancellationSource = new CancellationTokenSource()).Token); } - private void replacePlaceholder(Placeholder placeholder) + #region Placeholder handling + + private Placeholder placeholder; + + private void setErrorState(LeaderboardErrorState errorState) { - if (placeholder != null && placeholder.Equals(currentPlaceholder)) + if (errorState == this.errorState) return; - currentPlaceholder?.FadeOut(150, Easing.OutQuint).Expire(); + this.errorState = errorState; + + placeholder?.FadeOut(150, Easing.OutQuint).Expire(); + + placeholder = getPlaceholderFor(errorState); if (placeholder == null) - { - currentPlaceholder = null; return; - } placeholderContainer.Child = placeholder; placeholder.ScaleTo(0.8f).Then().ScaleTo(1, fade_duration * 3, Easing.OutQuint); placeholder.FadeInFromZero(fade_duration, Easing.OutQuint); + } - currentPlaceholder = placeholder; + private Placeholder getPlaceholderFor(LeaderboardErrorState errorState) + { + switch (errorState) + { + case LeaderboardErrorState.NetworkFailure: + return new ClickablePlaceholder(@"Couldn't fetch scores!", FontAwesome.Solid.Sync) + { + Action = RefetchScores + }; + + case LeaderboardErrorState.NoneSelected: + return new MessagePlaceholder(@"Please select a beatmap!"); + + case LeaderboardErrorState.Unavailable: + return new MessagePlaceholder(@"Leaderboards are not available for this beatmap!"); + + case LeaderboardErrorState.NoScores: + return new MessagePlaceholder(@"No records yet!"); + + case LeaderboardErrorState.NotLoggedIn: + return new LoginPlaceholder(@"Please sign in to view online leaderboards!"); + + case LeaderboardErrorState.NotSupporter: + return new MessagePlaceholder(@"Please invest in an osu!supporter tag to view this leaderboard!"); + + case LeaderboardErrorState.NoError: + return null; + + default: + throw new ArgumentOutOfRangeException(); + } } #endregion diff --git a/osu.Game/Online/Leaderboards/PlaceholderState.cs b/osu.Game/Online/Leaderboards/LeaderboardErrorState.cs similarity index 82% rename from osu.Game/Online/Leaderboards/PlaceholderState.cs rename to osu.Game/Online/Leaderboards/LeaderboardErrorState.cs index 297241fa73..17f47bb557 100644 --- a/osu.Game/Online/Leaderboards/PlaceholderState.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardErrorState.cs @@ -3,10 +3,9 @@ namespace osu.Game.Online.Leaderboards { - public enum PlaceholderState + public enum LeaderboardErrorState { - Successful, - Retrieving, + NoError, NetworkFailure, Unavailable, NoneSelected, diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 31868624bb..b0ba830076 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -33,19 +33,12 @@ namespace osu.Game.Screens.Select.Leaderboards set { if (beatmapInfo == null && value == null) - { - // always null scores to ensure a correct initial display. - // see weird `scoresLoadedOnce` logic in base implementation. - Scores = null; return; - } if (beatmapInfo?.Equals(value) == true) return; beatmapInfo = value; - Scores = null; - RefetchScores(); } } @@ -114,7 +107,7 @@ namespace osu.Game.Screens.Select.Leaderboards if (fetchBeatmapInfo == null) { - PlaceholderState = PlaceholderState.NoneSelected; + SetErrorState(LeaderboardErrorState.NoneSelected); return null; } @@ -126,19 +119,19 @@ namespace osu.Game.Screens.Select.Leaderboards if (api?.IsLoggedIn != true) { - PlaceholderState = PlaceholderState.NotLoggedIn; + SetErrorState(LeaderboardErrorState.NotLoggedIn); return null; } if (fetchBeatmapInfo.OnlineID <= 0 || fetchBeatmapInfo.Status <= BeatmapOnlineStatus.Pending) { - PlaceholderState = PlaceholderState.Unavailable; + SetErrorState(LeaderboardErrorState.Unavailable); return null; } if (!api.LocalUser.Value.IsSupporter && (Scope != BeatmapLeaderboardScope.Global || filterMods)) { - PlaceholderState = PlaceholderState.NotSupporter; + SetErrorState(LeaderboardErrorState.NotSupporter); return null; } From acc1199addc044c2673516c1738586e16a058a87 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jan 2022 16:16:00 +0900 Subject: [PATCH 802/996] Consolidate flows of `Set` operations, either result or error --- .../SongSelect/TestSceneBeatmapLeaderboard.cs | 17 ++--- osu.Game/Online/Leaderboards/Leaderboard.cs | 63 ++++++++----------- .../Match/Components/MatchLeaderboard.cs | 5 +- .../Select/Leaderboards/BeatmapLeaderboard.cs | 11 +--- 4 files changed, 37 insertions(+), 59 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 969bafa98b..e9a518b2fd 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -101,7 +101,7 @@ namespace osu.Game.Tests.Visual.SongSelect public void TestGlobalScoresDisplay() { AddStep(@"Set scope", () => leaderboard.Scope = BeatmapLeaderboardScope.Global); - AddStep(@"New Scores", () => leaderboard.Scores = generateSampleScores(new BeatmapInfo())); + AddStep(@"New Scores", () => leaderboard.SetScores(generateSampleScores(new BeatmapInfo()))); } [Test] @@ -124,7 +124,7 @@ namespace osu.Game.Tests.Visual.SongSelect private void showPersonalBestWithNullPosition() { - leaderboard.TopScore = new ScoreInfo + leaderboard.SetScores(leaderboard.Scores, new ScoreInfo { Rank = ScoreRank.XH, Accuracy = 1, @@ -142,12 +142,12 @@ namespace osu.Game.Tests.Visual.SongSelect FlagName = @"ES", }, }, - }; + }); } private void showPersonalBest() { - leaderboard.TopScore = new ScoreInfo + leaderboard.SetScores(leaderboard.Scores, new ScoreInfo { Position = 999, Rank = ScoreRank.XH, @@ -166,7 +166,7 @@ namespace osu.Game.Tests.Visual.SongSelect FlagName = @"ES", }, }, - }; + }); } private void loadMoreScores(Func beatmapInfo) @@ -404,12 +404,7 @@ namespace osu.Game.Tests.Visual.SongSelect private class FailableLeaderboard : BeatmapLeaderboard { public new void SetErrorState(LeaderboardErrorState errorState) => base.SetErrorState(errorState); - - public new ICollection Scores - { - get => base.Scores; - set => base.Scores = value; - } + public new void SetScores(IEnumerable scores, ScoreInfo userScore = default) => base.SetScores(scores, userScore); } } } diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 2e552f9774..9c46739443 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -33,6 +33,11 @@ namespace osu.Game.Online.Leaderboards /// The score model class. public abstract class Leaderboard : CompositeDrawable { + /// + /// The currently displayed scores. + /// + public IEnumerable Scores => scores; + /// /// Whether the current scope should refetch in response to changes in API connectivity state. /// @@ -42,7 +47,7 @@ namespace osu.Game.Online.Leaderboards private readonly OsuScrollContainer scrollContainer; private readonly Container placeholderContainer; - private readonly UserTopScoreContainer topScoreContainer; + private readonly UserTopScoreContainer userScoreContainer; private FillFlowContainer scoreFlowContainer; @@ -62,30 +67,6 @@ namespace osu.Game.Online.Leaderboards private ICollection scores; - public ICollection Scores - { - get => scores; - protected set - { - scores = value; - Scheduler.Add(updateScoresDrawables, false); - } - } - - public TScoreInfo TopScore - { - get => topScoreContainer.Score.Value; - set - { - topScoreContainer.Score.Value = value; - - if (value == null) - topScoreContainer.Hide(); - else - topScoreContainer.Show(); - } - } - private TScope scope; public TScope Scope @@ -133,7 +114,7 @@ namespace osu.Game.Online.Leaderboards { AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, - Child = topScoreContainer = new UserTopScoreContainer(CreateDrawableTopScore) + Child = userScoreContainer = new UserTopScoreContainer(CreateDrawableTopScore) }, }, }, @@ -176,15 +157,6 @@ namespace osu.Game.Online.Leaderboards /// public void RefetchScores() => Scheduler.AddOnce(refetchScores); - /// - /// Reset the leaderboard into an empty state. - /// - protected virtual void Reset() - { - cancelPendingWork(); - Scores = null; - } - /// /// Call when a retrieval or display failure happened to show a relevant message to the user. /// @@ -202,6 +174,24 @@ namespace osu.Game.Online.Leaderboards setErrorState(errorState); } + /// + /// Call when score retrieval is ready to be displayed. + /// + /// The scores to display. + /// The user top score, if any. + protected void SetScores(IEnumerable scores, TScoreInfo userScore = default) + { + this.scores = scores?.ToList(); + userScoreContainer.Score.Value = userScore; + + if (userScore == null) + userScoreContainer.Hide(); + else + userScoreContainer.Show(); + + Scheduler.Add(updateScoresDrawables, false); + } + /// /// Performs a fetch/refresh of scores to be displayed. /// @@ -218,7 +208,8 @@ namespace osu.Game.Online.Leaderboards { Debug.Assert(ThreadSafety.IsUpdateThread); - Reset(); + cancelPendingWork(); + SetScores(null); setErrorState(LeaderboardErrorState.NoError); loading.Show(); diff --git a/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs b/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs index 1945899a11..ea7de917e2 100644 --- a/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs +++ b/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs @@ -24,7 +24,7 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components if (id.NewValue == null) return; - Scores = null; + SetScores(null); RefetchScores(); }, true); } @@ -43,8 +43,7 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components if (cancellationToken.IsCancellationRequested) return; - Scores = r.Leaderboard; - TopScore = r.UserScore; + SetScores(r.Leaderboard, r.UserScore); }; return req; diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index b0ba830076..3521a2ef78 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -93,12 +93,6 @@ namespace osu.Game.Screens.Select.Leaderboards }; } - protected override void Reset() - { - base.Reset(); - TopScore = null; - } - protected override bool IsOnlineScope => Scope != BeatmapLeaderboardScope.Local; protected override APIRequest FetchScores(CancellationToken cancellationToken) @@ -153,8 +147,7 @@ namespace osu.Game.Screens.Select.Leaderboards if (cancellationToken.IsCancellationRequested) return; - Scores = task.GetResultSafely(); - TopScore = r.UserScore?.CreateScoreInfo(rulesets, fetchBeatmapInfo); + SetScores(task.GetResultSafely(), r.UserScore?.CreateScoreInfo(rulesets, fetchBeatmapInfo)); }), TaskContinuationOptions.OnlyOnRanToCompletion); }; @@ -210,7 +203,7 @@ namespace osu.Game.Screens.Select.Leaderboards scoreManager.OrderByTotalScoreAsync(scores.ToArray(), cancellationToken) .ContinueWith(ordered => { - Scores = ordered.GetResultSafely(); + SetScores(ordered.GetResultSafely()); }, TaskContinuationOptions.OnlyOnRanToCompletion); } } From 04dbb5d3c6cc9c8285fa5d95ac23621761e4a57c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jan 2022 16:16:33 +0900 Subject: [PATCH 803/996] Disallow setting "NoScores" externally as it is handled internally --- .../Visual/SongSelect/TestSceneBeatmapLeaderboard.cs | 3 ++- osu.Game/Online/Leaderboards/Leaderboard.cs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index e9a518b2fd..f91339e060 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -114,7 +114,8 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestPlaceholderStates() { - AddStep(@"Empty Scores", () => leaderboard.SetErrorState(LeaderboardErrorState.NoScores)); + AddStep("ensure no scores displayed", () => leaderboard.SetScores(null)); + AddStep(@"Network failure", () => leaderboard.SetErrorState(LeaderboardErrorState.NetworkFailure)); AddStep(@"No supporter", () => leaderboard.SetErrorState(LeaderboardErrorState.NotSupporter)); AddStep(@"Not logged in", () => leaderboard.SetErrorState(LeaderboardErrorState.NotLoggedIn)); diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 9c46739443..9fb0fe0e3b 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -165,6 +165,7 @@ namespace osu.Game.Online.Leaderboards { switch (errorState) { + case LeaderboardErrorState.NoScores: case LeaderboardErrorState.NoError: throw new InvalidOperationException($"State {errorState} cannot be set by a leaderboard implementation."); } @@ -250,7 +251,7 @@ namespace osu.Game.Online.Leaderboards if (scores?.Any() != true) { - SetErrorState(LeaderboardErrorState.NoScores); + setErrorState(LeaderboardErrorState.NoScores); loading.Hide(); return; } From 1dbcb5ab63a7d5656c40f370213148011d3c8fbb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jan 2022 18:51:15 +0900 Subject: [PATCH 804/996] Add test coverage of intro fail scenario --- osu.Game.Tests/Visual/Menus/IntroTestScene.cs | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/osu.Game.Tests/Visual/Menus/IntroTestScene.cs b/osu.Game.Tests/Visual/Menus/IntroTestScene.cs index bfea97410a..40b072c171 100644 --- a/osu.Game.Tests/Visual/Menus/IntroTestScene.cs +++ b/osu.Game.Tests/Visual/Menus/IntroTestScene.cs @@ -5,6 +5,8 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; +using osu.Framework.Threading; +using osu.Game.Overlays; using osu.Game.Screens; using osu.Game.Screens.Menu; using osuTK; @@ -22,6 +24,11 @@ namespace osu.Game.Tests.Visual.Menus private IntroScreen intro; + [Cached] + private NotificationOverlay notifications; + + private ScheduledDelegate trackResetDelegate; + protected IntroTestScene() { Children = new Drawable[] @@ -38,6 +45,11 @@ namespace osu.Game.Tests.Visual.Menus RelativePositionAxes = Axes.Both, Depth = float.MinValue, Position = new Vector2(0.5f), + }, + notifications = new NotificationOverlay + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, } }; } @@ -63,6 +75,39 @@ namespace osu.Game.Tests.Visual.Menus AddUntilStep("wait for menu", () => intro.DidLoadMenu); } + [Test] + public virtual void TestPlayIntroWithFailingAudioDevice() + { + AddStep("hide notifications", () => notifications.Hide()); + AddStep("restart sequence", () => + { + logo.FinishTransforms(); + logo.IsTracking = false; + + IntroStack?.Expire(); + + Add(IntroStack = new OsuScreenStack + { + RelativeSizeAxes = Axes.Both, + }); + + IntroStack.Push(intro = CreateScreen()); + }); + + AddStep("trigger failure", () => + { + trackResetDelegate = Scheduler.AddDelayed(() => + { + intro.Beatmap.Value.Track.Seek(0); + }, 0, true); + }); + + AddUntilStep("wait for menu", () => intro.DidLoadMenu); + AddUntilStep("wait for notification", () => notifications.UnreadCount.Value == 1); + + AddStep("uninstall delegate", () => trackResetDelegate?.Cancel()); + } + protected abstract IntroScreen CreateScreen(); } } From 52f1c2bfdbc349259bc817ac3e0a4925f9c78a8e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jan 2022 18:37:00 +0900 Subject: [PATCH 805/996] Add failsafe to `IntroScreen` to stop users with incorrect audio configuration getting stuck The most common case of this seems to be linux users with incorrect or unsupported audio driver configurations. It continues to be brought up in discussions as people are unsure of why their game freezes on startup, and unable to easily recover. --- osu.Game/Screens/Menu/IntroScreen.cs | 37 ++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index fceb083916..98c4b15f7f 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -18,6 +18,7 @@ using osu.Game.Configuration; using osu.Game.Database; using osu.Game.IO.Archives; using osu.Game.Overlays; +using osu.Game.Overlays.Notifications; using osu.Game.Screens.Backgrounds; using osuTK; using osuTK.Graphics; @@ -147,6 +148,36 @@ namespace osu.Game.Screens.Menu } } + public override void OnEntering(IScreen last) + { + base.OnEntering(last); + ensureEventuallyArrivingAtMenu(); + } + + [Resolved] + private NotificationOverlay notifications { get; set; } + + private void ensureEventuallyArrivingAtMenu() + { + // This intends to handle the case where an intro may get stuck. + // Historically, this could happen if the host system's audio device is in a state it can't + // play audio, causing a clock to never elapse time and the intro to never end. + // + // This safety measure gives the user a chance to fix the problem from the settings menu. + Scheduler.AddDelayed(() => + { + if (DidLoadMenu) + return; + + PrepareMenuLoad(); + LoadMenu(); + notifications.Post(new SimpleErrorNotification + { + Text = "osu! doesn't seem to be able to play audio correctly.\n\nPlease try changing your audio device to a working setting." + }); + }, 5000); + } + public override void OnResuming(IScreen last) { this.FadeIn(300); @@ -241,6 +272,9 @@ namespace osu.Game.Screens.Menu protected void PrepareMenuLoad() { + if (nextScreen != null) + return; + nextScreen = createNextScreen?.Invoke(); if (nextScreen != null) @@ -249,6 +283,9 @@ namespace osu.Game.Screens.Menu protected void LoadMenu() { + if (DidLoadMenu) + return; + beatmap.Return(); DidLoadMenu = true; From 6a21d583259b43c0d7865371f34c73d5cba04e04 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jan 2022 18:59:18 +0900 Subject: [PATCH 806/996] Avoid test failures on non-triangle intro tests --- osu.Game.Tests/Visual/Menus/IntroTestScene.cs | 6 +++++- osu.Game.Tests/Visual/Menus/TestSceneIntroCircles.cs | 1 + osu.Game.Tests/Visual/Menus/TestSceneIntroTriangles.cs | 1 + osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs | 1 + 4 files changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Menus/IntroTestScene.cs b/osu.Game.Tests/Visual/Menus/IntroTestScene.cs index 40b072c171..82accceb23 100644 --- a/osu.Game.Tests/Visual/Menus/IntroTestScene.cs +++ b/osu.Game.Tests/Visual/Menus/IntroTestScene.cs @@ -20,6 +20,8 @@ namespace osu.Game.Tests.Visual.Menus [Cached] private OsuLogo logo; + protected abstract bool IntroReliesOnTrack { get; } + protected OsuScreenStack IntroStack; private IntroScreen intro; @@ -103,7 +105,9 @@ namespace osu.Game.Tests.Visual.Menus }); AddUntilStep("wait for menu", () => intro.DidLoadMenu); - AddUntilStep("wait for notification", () => notifications.UnreadCount.Value == 1); + + if (IntroReliesOnTrack) + AddUntilStep("wait for notification", () => notifications.UnreadCount.Value == 1); AddStep("uninstall delegate", () => trackResetDelegate?.Cancel()); } diff --git a/osu.Game.Tests/Visual/Menus/TestSceneIntroCircles.cs b/osu.Game.Tests/Visual/Menus/TestSceneIntroCircles.cs index ffc99185fb..7ad49b5dcd 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneIntroCircles.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneIntroCircles.cs @@ -9,6 +9,7 @@ namespace osu.Game.Tests.Visual.Menus [TestFixture] public class TestSceneIntroCircles : IntroTestScene { + protected override bool IntroReliesOnTrack => false; protected override IntroScreen CreateScreen() => new IntroCircles(); } } diff --git a/osu.Game.Tests/Visual/Menus/TestSceneIntroTriangles.cs b/osu.Game.Tests/Visual/Menus/TestSceneIntroTriangles.cs index 8f01e0321b..abe8936330 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneIntroTriangles.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneIntroTriangles.cs @@ -9,6 +9,7 @@ namespace osu.Game.Tests.Visual.Menus [TestFixture] public class TestSceneIntroTriangles : IntroTestScene { + protected override bool IntroReliesOnTrack => true; protected override IntroScreen CreateScreen() => new IntroTriangles(); } } diff --git a/osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs b/osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs index 9081be3dd6..11cea25865 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs @@ -10,6 +10,7 @@ namespace osu.Game.Tests.Visual.Menus [TestFixture] public class TestSceneIntroWelcome : IntroTestScene { + protected override bool IntroReliesOnTrack => false; protected override IntroScreen CreateScreen() => new IntroWelcome(); public override void TestPlayIntro() From 82806d7aebc5a0a2273575f4efdbbd6d1c7c866f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jan 2022 21:32:39 +0900 Subject: [PATCH 807/996] Ensure the background is eventually displayed when `IntroTriangles` suspends --- osu.Game/Screens/Menu/IntroTriangles.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs index 10f940e9de..b6b6bf2ad7 100644 --- a/osu.Game/Screens/Menu/IntroTriangles.cs +++ b/osu.Game/Screens/Menu/IntroTriangles.cs @@ -93,6 +93,9 @@ namespace osu.Game.Screens.Menu { base.OnSuspending(next); + // 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(); } From 1cec76df74df950db682ecde9de14ceeb9c1de8a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jan 2022 23:18:40 +0900 Subject: [PATCH 808/996] Fix weird reading xmldoc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Online/Leaderboards/Leaderboard.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 9fb0fe0e3b..e96b12bc5d 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -176,7 +176,7 @@ namespace osu.Game.Online.Leaderboards } /// - /// Call when score retrieval is ready to be displayed. + /// Call when retrieved scores are ready to be displayed. /// /// The scores to display. /// The user top score, if any. From f8939af5e6d165d97ad733d0b39634ef6c5b1723 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 31 Jan 2022 01:12:03 +0900 Subject: [PATCH 809/996] Track loading via state as well --- .../SongSelect/TestSceneBeatmapLeaderboard.cs | 12 ++-- osu.Game/Online/Leaderboards/Leaderboard.cs | 66 ++++++++++--------- ...boardErrorState.cs => LeaderboardState.cs} | 5 +- .../Select/Leaderboards/BeatmapLeaderboard.cs | 8 +-- 4 files changed, 48 insertions(+), 43 deletions(-) rename osu.Game/Online/Leaderboards/{LeaderboardErrorState.cs => LeaderboardState.cs} (82%) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index f91339e060..48230ff9e9 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -116,11 +116,11 @@ namespace osu.Game.Tests.Visual.SongSelect { AddStep("ensure no scores displayed", () => leaderboard.SetScores(null)); - AddStep(@"Network failure", () => leaderboard.SetErrorState(LeaderboardErrorState.NetworkFailure)); - AddStep(@"No supporter", () => leaderboard.SetErrorState(LeaderboardErrorState.NotSupporter)); - AddStep(@"Not logged in", () => leaderboard.SetErrorState(LeaderboardErrorState.NotLoggedIn)); - AddStep(@"Unavailable", () => leaderboard.SetErrorState(LeaderboardErrorState.Unavailable)); - AddStep(@"None selected", () => leaderboard.SetErrorState(LeaderboardErrorState.NoneSelected)); + AddStep(@"Network failure", () => leaderboard.SetErrorState(LeaderboardState.NetworkFailure)); + AddStep(@"No supporter", () => leaderboard.SetErrorState(LeaderboardState.NotSupporter)); + AddStep(@"Not logged in", () => leaderboard.SetErrorState(LeaderboardState.NotLoggedIn)); + AddStep(@"Unavailable", () => leaderboard.SetErrorState(LeaderboardState.Unavailable)); + AddStep(@"None selected", () => leaderboard.SetErrorState(LeaderboardState.NoneSelected)); } private void showPersonalBestWithNullPosition() @@ -404,7 +404,7 @@ namespace osu.Game.Tests.Visual.SongSelect private class FailableLeaderboard : BeatmapLeaderboard { - public new void SetErrorState(LeaderboardErrorState errorState) => base.SetErrorState(errorState); + public new void SetErrorState(LeaderboardState state) => base.SetErrorState(state); public new void SetScores(IEnumerable scores, ScoreInfo userScore = default) => base.SetScores(scores, userScore); } } diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index e96b12bc5d..7ac05fb0c0 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -58,7 +58,7 @@ namespace osu.Game.Online.Leaderboards private APIRequest fetchScoresRequest; - private LeaderboardErrorState errorState; + private LeaderboardState state; [Resolved(CanBeNull = true)] private IAPIProvider api { get; set; } @@ -160,19 +160,20 @@ namespace osu.Game.Online.Leaderboards /// /// Call when a retrieval or display failure happened to show a relevant message to the user. /// - /// The state to display. - protected void SetErrorState(LeaderboardErrorState errorState) + /// The state to display. + protected void SetErrorState(LeaderboardState state) { - switch (errorState) + switch (state) { - case LeaderboardErrorState.NoScores: - case LeaderboardErrorState.NoError: - throw new InvalidOperationException($"State {errorState} cannot be set by a leaderboard implementation."); + case LeaderboardState.NoScores: + case LeaderboardState.Retrieving: + case LeaderboardState.Success: + throw new InvalidOperationException($"State {state} cannot be set by a leaderboard implementation."); } Debug.Assert(scores?.Any() != true); - setErrorState(errorState); + setState(state); } /// @@ -212,8 +213,7 @@ namespace osu.Game.Online.Leaderboards cancelPendingWork(); SetScores(null); - setErrorState(LeaderboardErrorState.NoError); - loading.Show(); + setState(LeaderboardState.Retrieving); currentFetchCancellationSource = new CancellationTokenSource(); @@ -227,7 +227,7 @@ namespace osu.Game.Online.Leaderboards if (e is OperationCanceledException || currentFetchCancellationSource.IsCancellationRequested) return; - SetErrorState(LeaderboardErrorState.NetworkFailure); + SetErrorState(LeaderboardState.NetworkFailure); }); api?.Queue(fetchScoresRequest); @@ -251,15 +251,10 @@ namespace osu.Game.Online.Leaderboards if (scores?.Any() != true) { - setErrorState(LeaderboardErrorState.NoScores); - loading.Hide(); + setState(LeaderboardState.NoScores); return; } - // ensure placeholder is hidden when displaying scores - setErrorState(LeaderboardErrorState.NoError); - loading.Show(); - LoadComponentAsync(new FillFlowContainer { RelativeSizeAxes = Axes.X, @@ -269,6 +264,8 @@ namespace osu.Game.Online.Leaderboards ChildrenEnumerable = scores.Select((s, index) => CreateDrawableScore(s, index + 1)) }, newFlow => { + setState(LeaderboardState.Success); + scrollContainer.Add(scoreFlowContainer = newFlow); double delay = 0; @@ -282,7 +279,6 @@ namespace osu.Game.Online.Leaderboards } scrollContainer.ScrollToStart(false); - loading.Hide(); }, (currentScoresAsyncLoadCancellationSource = new CancellationTokenSource()).Token); } @@ -290,16 +286,21 @@ namespace osu.Game.Online.Leaderboards private Placeholder placeholder; - private void setErrorState(LeaderboardErrorState errorState) + private void setState(LeaderboardState state) { - if (errorState == this.errorState) + if (state == this.state) return; - this.errorState = errorState; + if (state == LeaderboardState.Retrieving) + loading.Show(); + else + loading.Hide(); + + this.state = state; placeholder?.FadeOut(150, Easing.OutQuint).Expire(); - placeholder = getPlaceholderFor(errorState); + placeholder = getPlaceholderFor(state); if (placeholder == null) return; @@ -310,32 +311,35 @@ namespace osu.Game.Online.Leaderboards placeholder.FadeInFromZero(fade_duration, Easing.OutQuint); } - private Placeholder getPlaceholderFor(LeaderboardErrorState errorState) + private Placeholder getPlaceholderFor(LeaderboardState state) { - switch (errorState) + switch (state) { - case LeaderboardErrorState.NetworkFailure: + case LeaderboardState.NetworkFailure: return new ClickablePlaceholder(@"Couldn't fetch scores!", FontAwesome.Solid.Sync) { Action = RefetchScores }; - case LeaderboardErrorState.NoneSelected: + case LeaderboardState.NoneSelected: return new MessagePlaceholder(@"Please select a beatmap!"); - case LeaderboardErrorState.Unavailable: + case LeaderboardState.Unavailable: return new MessagePlaceholder(@"Leaderboards are not available for this beatmap!"); - case LeaderboardErrorState.NoScores: + case LeaderboardState.NoScores: return new MessagePlaceholder(@"No records yet!"); - case LeaderboardErrorState.NotLoggedIn: + case LeaderboardState.NotLoggedIn: return new LoginPlaceholder(@"Please sign in to view online leaderboards!"); - case LeaderboardErrorState.NotSupporter: + case LeaderboardState.NotSupporter: return new MessagePlaceholder(@"Please invest in an osu!supporter tag to view this leaderboard!"); - case LeaderboardErrorState.NoError: + case LeaderboardState.Retrieving: + return null; + + case LeaderboardState.Success: return null; default: diff --git a/osu.Game/Online/Leaderboards/LeaderboardErrorState.cs b/osu.Game/Online/Leaderboards/LeaderboardState.cs similarity index 82% rename from osu.Game/Online/Leaderboards/LeaderboardErrorState.cs rename to osu.Game/Online/Leaderboards/LeaderboardState.cs index 17f47bb557..75e2c6e6db 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardErrorState.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardState.cs @@ -3,9 +3,10 @@ namespace osu.Game.Online.Leaderboards { - public enum LeaderboardErrorState + public enum LeaderboardState { - NoError, + Success, + Retrieving, NetworkFailure, Unavailable, NoneSelected, diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 3521a2ef78..48eb33cc05 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -101,7 +101,7 @@ namespace osu.Game.Screens.Select.Leaderboards if (fetchBeatmapInfo == null) { - SetErrorState(LeaderboardErrorState.NoneSelected); + SetErrorState(LeaderboardState.NoneSelected); return null; } @@ -113,19 +113,19 @@ namespace osu.Game.Screens.Select.Leaderboards if (api?.IsLoggedIn != true) { - SetErrorState(LeaderboardErrorState.NotLoggedIn); + SetErrorState(LeaderboardState.NotLoggedIn); return null; } if (fetchBeatmapInfo.OnlineID <= 0 || fetchBeatmapInfo.Status <= BeatmapOnlineStatus.Pending) { - SetErrorState(LeaderboardErrorState.Unavailable); + SetErrorState(LeaderboardState.Unavailable); return null; } if (!api.LocalUser.Value.IsSupporter && (Scope != BeatmapLeaderboardScope.Global || filterMods)) { - SetErrorState(LeaderboardErrorState.NotSupporter); + SetErrorState(LeaderboardState.NotSupporter); return null; } From b52153e73d354f137b221d5c14cd505d37a8ffdd Mon Sep 17 00:00:00 2001 From: Spooghetti420 Date: Sun, 30 Jan 2022 17:40:15 +0000 Subject: [PATCH 810/996] Remove settings --- .../Mods/TestSceneManiaModHoldOff.cs | 58 +++---------------- .../Mods/ManiaModHoldOff.cs | 28 +-------- 2 files changed, 10 insertions(+), 76 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs index e74f63abb3..a113b7ac80 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Linq; using NUnit.Framework; using osu.Game.Beatmaps; @@ -11,8 +10,6 @@ using System.Collections.Generic; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Mania.Beatmaps; -using osu.Game.Rulesets.Mania.Difficulty; -using osu.Game.Tests.Beatmaps; namespace osu.Game.Rulesets.Mania.Tests.Mods { @@ -42,24 +39,17 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods Assert.AreEqual(noteValues, new List { 0.125, 0.250, 0.500, 1.000, 2.000 }); } - [TestCase(ManiaModHoldOff.BeatDivisors.Whole)] - [TestCase(ManiaModHoldOff.BeatDivisors.Half)] - [TestCase(ManiaModHoldOff.BeatDivisors.Quarter)] - [TestCase(ManiaModHoldOff.BeatDivisors.Eighth)] - public void TestCorrectObjectCount(ManiaModHoldOff.BeatDivisors minBeatSnap) + [Test] + public void TestCorrectObjectCount() { - /* - This test is to ensure that, given that end notes are enabled, - the mod produces the expected number of objects when the mod is applied. - */ + // Ensure that the mod produces the expected number of objects when applied. - // Mod settings will be set to include the correct beat snap value var rawBeatmap = createRawBeatmap(); - var testBeatmap = createModdedBeatmap(minBeatSnap); + var testBeatmap = createModdedBeatmap(); // Calculate expected number of objects int expectedObjectCount = 0; - double beatSnapValue = 1 / (Math.Pow(2, (int)minBeatSnap)); + double beatSnapValue = ManiaModHoldOff.Threshold; foreach (ManiaHitObject h in rawBeatmap.HitObjects) { @@ -81,44 +71,10 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods Assert.That(testBeatmap.HitObjects.Count == expectedObjectCount); } - [Test] - public void TestDifficultyIncrease() - { - // A lower minimum beat snap divisor should only make the map harder, never easier - // (as more notes can be spawned) - var beatmaps = new[] - { - createModdedBeatmap(), - createModdedBeatmap(ManiaModHoldOff.BeatDivisors.Half), - createModdedBeatmap(ManiaModHoldOff.BeatDivisors.Quarter), - createModdedBeatmap(ManiaModHoldOff.BeatDivisors.Eighth), - createModdedBeatmap(ManiaModHoldOff.BeatDivisors.Sixteenth) - }; - - double[] mapDifficulties = new double[beatmaps.Length]; - - for (int i = 0; i < mapDifficulties.Length; i++) - { - var workingBeatmap = new TestWorkingBeatmap(beatmaps[i]); - var difficultyCalculator = new ManiaDifficultyCalculator(new ManiaRuleset().RulesetInfo, workingBeatmap); - mapDifficulties[i] = difficultyCalculator.Calculate().StarRating; - - if (i > 0) - { - Assert.LessOrEqual(mapDifficulties[i - 1], mapDifficulties[i]); - Assert.LessOrEqual(beatmaps[i - 1].HitObjects.Count, beatmaps[i].HitObjects.Count); - } - } - } - - private static ManiaBeatmap createModdedBeatmap(ManiaModHoldOff.BeatDivisors minBeatSnap = ManiaModHoldOff.BeatDivisors.Whole) + private static ManiaBeatmap createModdedBeatmap() { var beatmap = createRawBeatmap(); - var holdOffMod = new ManiaModHoldOff - { - MinBeatSnap = { Value = minBeatSnap } - }; - Assert.AreEqual(holdOffMod.MinBeatSnap.Value, minBeatSnap); + var holdOffMod = new ManiaModHoldOff(); foreach (var hitObject in beatmap.HitObjects) hitObject.ApplyDefaults(beatmap.ControlPointInfo, new BeatmapDifficulty()); diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs index 637142aed6..5a47e1ba89 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs @@ -6,11 +6,8 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mods; using osu.Framework.Graphics.Sprites; -using System; using System.Collections.Generic; using osu.Game.Rulesets.Mania.Beatmaps; -using osu.Framework.Bindables; -using osu.Game.Configuration; namespace osu.Game.Rulesets.Mania.Mods { @@ -27,23 +24,13 @@ namespace osu.Game.Rulesets.Mania.Mods public override IconUsage? Icon => FontAwesome.Solid.DotCircle; public override ModType Type => ModType.Conversion; - - [SettingSource("Add end notes", "Also add a note at the end of a hold note")] - public BindableBool AddEndNotes { get; } = new BindableBool - { - Default = true, - Value = true - }; - - [SettingSource("Minimum end note beat snap", "Don't add end notes for hold notes shorter than this beat division")] - public Bindable MinBeatSnap { get; } = new Bindable(defaultValue: BeatDivisors.Half); + public const double Threshold = 1/2; public void ApplyToBeatmap(IBeatmap beatmap) { var maniaBeatmap = (ManiaBeatmap)beatmap; var newObjects = new List(); - double beatSnap = 1 / (Math.Pow(2, (double)MinBeatSnap.Value)); foreach (var h in beatmap.HitObjects.OfType()) { @@ -55,10 +42,10 @@ namespace osu.Game.Rulesets.Mania.Mods Samples = h.GetNodeSamples(0) }); - // Don't add an end note if the duration is shorter than some threshold, or end notes are disabled + // Don't add an end note if the duration is shorter than the threshold double noteValue = GetNoteDurationInBeatLength(h, maniaBeatmap); // 1/1, 1/2, 1/4, etc. - if (AddEndNotes.Value && noteValue >= beatSnap) + if (noteValue >= Threshold) { newObjects.Add(new Note { @@ -77,14 +64,5 @@ namespace osu.Game.Rulesets.Mania.Mods double bpmAtNoteTime = beatmap.ControlPointInfo.TimingPointAt(holdNote.StartTime).BPM; return (60 * holdNote.Duration) / (1000 * bpmAtNoteTime); } - - public enum BeatDivisors - { - Whole, - Half, - Quarter, - Eighth, - Sixteenth - } } } From 2b999f9780f4f9e96c92fbe42e4a71a86605b0ec Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 29 Jan 2022 23:39:45 +0300 Subject: [PATCH 811/996] Add failing test case --- .../TestSceneMasterGameplayClockContainer.cs | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs b/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs index 935bc07733..172531605a 100644 --- a/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs +++ b/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs @@ -2,7 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; +using osu.Framework.Allocation; +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; using osu.Game.Tests.Visual; @@ -12,6 +18,14 @@ namespace osu.Game.Tests.Gameplay [HeadlessTest] public class TestSceneMasterGameplayClockContainer : OsuTestScene { + private OsuConfigManager localConfig; + + [BackgroundDependencyLoader] + private void load() + { + Dependencies.Cache(localConfig = new OsuConfigManager(LocalStorage)); + } + [Test] public void TestStartThenElapsedTime() { @@ -54,5 +68,43 @@ namespace osu.Game.Tests.Gameplay AddAssert("current time < time at reset", () => gcc.GameplayClock.CurrentTime < timeAtReset); } + + [Test] + public void TestSeekPerformsInGameplayTime( + [Values(1.0, 0.5, 2.0)] double clockRate, + [Values(0.0, 200.0, -200.0)] double userOffset, + [Values(false, true)] bool whileStopped) + { + ClockBackedTestWorkingBeatmap working = null; + GameplayClockContainer gcc = null; + + AddStep("create container", () => + { + working = new ClockBackedTestWorkingBeatmap(new OsuRuleset().RulesetInfo, new FramedClock(new ManualClock()), Audio); + working.LoadTrack(); + + Add(gcc = new MasterGameplayClockContainer(working, 0)); + + if (whileStopped) + gcc.Stop(); + + gcc.Reset(); + }); + + AddStep($"set clock rate to {clockRate}", () => working.Track.AddAdjustment(AdjustableProperty.Frequency, new BindableDouble(clockRate))); + AddStep($"set audio offset to {userOffset}", () => localConfig.SetValue(OsuSetting.AudioOffset, userOffset)); + + AddStep("seek to 2500", () => gcc.Seek(2500)); + AddAssert("gameplay clock time = 2500", () => Precision.AlmostEquals(gcc.CurrentTime, 2500, 10f)); + + AddStep("seek to 10000", () => gcc.Seek(10000)); + AddAssert("gameplay clock time = 10000", () => Precision.AlmostEquals(gcc.CurrentTime, 10000, 10f)); + } + + protected override void Dispose(bool isDisposing) + { + localConfig?.Dispose(); + base.Dispose(isDisposing); + } } } From 6556a7e3c3533b46a1f6b97989d2d6bf4f908a4a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 29 Jan 2022 23:06:31 +0300 Subject: [PATCH 812/996] Handle different gameplay rates when seeking on master clock --- .../Play/MasterGameplayClockContainer.cs | 39 ++++++++++++++----- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index aa46522dec..200921680e 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -43,7 +43,7 @@ namespace osu.Game.Screens.Play Precision = 0.1, }; - private double totalOffset => userOffsetClock.Offset + platformOffsetClock.Offset; + private double totalAppliedOffset => userOffsetClock.RateAdjustedOffset + platformOffsetClock.RateAdjustedOffset; private readonly BindableDouble pauseFreqAdjust = new BindableDouble(1); @@ -52,8 +52,8 @@ namespace osu.Game.Screens.Play private readonly bool startAtGameplayStart; private readonly double firstHitObjectTime; - private FramedOffsetClock userOffsetClock; - private FramedOffsetClock platformOffsetClock; + private HardwareCorrectionOffsetClock userOffsetClock; + private HardwareCorrectionOffsetClock platformOffsetClock; private MasterGameplayClock masterGameplayClock; private Bindable userAudioOffset; private double startOffset; @@ -128,7 +128,7 @@ namespace osu.Game.Screens.Play { // remove the offset component here because most of the time we want the seek to be aligned to gameplay, not the audio track. // we may want to consider reversing the application of offsets in the future as it may feel more correct. - base.Seek(time - totalOffset); + base.Seek(time - totalAppliedOffset); } /// @@ -214,13 +214,25 @@ namespace osu.Game.Screens.Play private class HardwareCorrectionOffsetClock : FramedOffsetClock { - // we always want to apply the same real-time offset, so it should be adjusted by the difference in playback rate (from realtime) to achieve this. - // base implementation already adds offset at 1.0 rate, so we only add the difference from that here. - public override double CurrentTime => base.CurrentTime + offsetAdjust; - private readonly BindableDouble pauseRateAdjust; - private double offsetAdjust; + private double offset; + + public new double Offset + { + get => offset; + set + { + if (value == offset) + return; + + offset = value; + + updateOffset(); + } + } + + public double RateAdjustedOffset => base.Offset; public HardwareCorrectionOffsetClock(IClock source, BindableDouble pauseRateAdjust) : base(source) @@ -231,10 +243,17 @@ namespace osu.Game.Screens.Play public override void ProcessFrame() { base.ProcessFrame(); + updateOffset(); + } + private void updateOffset() + { // changing this during the pause transform effect will cause a potentially large offset to be suddenly applied as we approach zero rate. if (pauseRateAdjust.Value == 1) - offsetAdjust = Offset * (Rate - 1); + { + // we always want to apply the same real-time offset, so it should be adjusted by the difference in playback rate (from realtime) to achieve this. + base.Offset = Offset * Rate; + } } } From bb8dc74e88fb3fcc3ce6958d740199a9249bc11b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 31 Jan 2022 12:20:51 +0900 Subject: [PATCH 813/996] Fix constant formatting --- .../Mods/TestSceneManiaModHoldOff.cs | 3 +-- osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs | 5 +++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs index a113b7ac80..7970d5b594 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs @@ -49,7 +49,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods // Calculate expected number of objects int expectedObjectCount = 0; - double beatSnapValue = ManiaModHoldOff.Threshold; foreach (ManiaHitObject h in rawBeatmap.HitObjects) { @@ -60,7 +59,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods { double noteValue = ManiaModHoldOff.GetNoteDurationInBeatLength((HoldNote)h, rawBeatmap); - if (noteValue >= beatSnapValue) + if (noteValue >= ManiaModHoldOff.END_NOTE_ALLOW_THRESHOLD) { // Should generate an end note if it's longer than the minimum note value expectedObjectCount++; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs index 5a47e1ba89..42dc7fdc1f 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs @@ -24,7 +24,8 @@ namespace osu.Game.Rulesets.Mania.Mods public override IconUsage? Icon => FontAwesome.Solid.DotCircle; public override ModType Type => ModType.Conversion; - public const double Threshold = 1/2; + + public const double END_NOTE_ALLOW_THRESHOLD = 0.5; public void ApplyToBeatmap(IBeatmap beatmap) { @@ -45,7 +46,7 @@ namespace osu.Game.Rulesets.Mania.Mods // Don't add an end note if the duration is shorter than the threshold double noteValue = GetNoteDurationInBeatLength(h, maniaBeatmap); // 1/1, 1/2, 1/4, etc. - if (noteValue >= Threshold) + if (noteValue >= END_NOTE_ALLOW_THRESHOLD) { newObjects.Add(new Note { From 610eb9f6a45e8f3f762bac4f25a814c7169d8010 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 31 Jan 2022 13:45:49 +0900 Subject: [PATCH 814/996] Remove unnecessary container level --- osu.Game/Online/Leaderboards/Leaderboard.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 7ac05fb0c0..5dd3e46b4a 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -110,12 +110,7 @@ namespace osu.Game.Online.Leaderboards }, new Drawable[] { - new Container - { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Child = userScoreContainer = new UserTopScoreContainer(CreateDrawableTopScore) - }, + userScoreContainer = new UserTopScoreContainer(CreateDrawableTopScore) }, }, }, From 9c9fda84f3cf54073c7d15b62946251a3a27d328 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 31 Jan 2022 13:50:53 +0900 Subject: [PATCH 815/996] Add schedule and cancellation check to score ordering step --- osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 48eb33cc05..907a2c9bda 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -201,10 +201,13 @@ namespace osu.Game.Screens.Select.Leaderboards scores = scores.Detach(); scoreManager.OrderByTotalScoreAsync(scores.ToArray(), cancellationToken) - .ContinueWith(ordered => + .ContinueWith(ordered => Schedule(() => { + if (cancellationToken.IsCancellationRequested) + return; + SetScores(ordered.GetResultSafely()); - }, TaskContinuationOptions.OnlyOnRanToCompletion); + }), TaskContinuationOptions.OnlyOnRanToCompletion); } } From a06287e76aac73bbf141d07a71fa417c7dbb31d2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jan 2022 12:34:08 +0900 Subject: [PATCH 816/996] Remove `DrawableCarouselItem.Update` updating of height Marginal from a performance aspect, but reads better. --- .../Select/Carousel/DrawableCarouselBeatmap.cs | 7 +++---- .../Select/Carousel/DrawableCarouselItem.cs | 14 ++++++-------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 3576b77ae8..43ddfc79b1 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -36,9 +36,9 @@ namespace osu.Game.Screens.Select.Carousel /// /// The height of a carousel beatmap, including vertical spacing. /// - public const float HEIGHT = height + CAROUSEL_BEATMAP_SPACING; + public const float HEIGHT = header_height + CAROUSEL_BEATMAP_SPACING; - private const float height = MAX_HEIGHT * 0.6f; + private const float header_height = MAX_HEIGHT * 0.6f; private readonly BeatmapInfo beatmapInfo; @@ -67,6 +67,7 @@ namespace osu.Game.Screens.Select.Carousel private CancellationTokenSource starDifficultyCancellationSource; public DrawableCarouselBeatmap(CarouselBeatmap panel) + : base(header_height) { beatmapInfo = panel.BeatmapInfo; Item = panel; @@ -75,8 +76,6 @@ namespace osu.Game.Screens.Select.Carousel [BackgroundDependencyLoader(true)] private void load(BeatmapManager manager, SongSelect songSelect) { - Header.Height = height; - if (songSelect != null) { startRequested = b => songSelect.FinaliseSelection(b); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs index cde3edad39..8b38bb9a96 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs @@ -60,7 +60,7 @@ namespace osu.Game.Screens.Select.Carousel } } - protected DrawableCarouselItem() + protected DrawableCarouselItem(float headerHeight = MAX_HEIGHT) { RelativeSizeAxes = Axes.X; @@ -73,10 +73,14 @@ namespace osu.Game.Screens.Select.Carousel RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - Header = new CarouselHeader(), + Header = new CarouselHeader + { + Height = headerHeight, + }, Content = new Container { RelativeSizeAxes = Axes.Both, + Y = headerHeight, } } }, @@ -92,12 +96,6 @@ namespace osu.Game.Screens.Select.Carousel UpdateItem(); } - protected override void Update() - { - base.Update(); - Content.Y = Header.Height; - } - protected virtual void UpdateItem() { if (item == null) From c3e3b2019dcf169d74c435bde269ec53bf43a8fb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jan 2022 13:01:41 +0900 Subject: [PATCH 817/996] Reduce overhead of `ApplyState` by tracking previous values Even with pooling applied, there are overheads involved with transforms when quickly cycling through the carousel. The main goal here is to reduce the transforms in cases the reuse is still in the same state. Avoiding firing `FadeIn` and `FadeOut` are the main areas of saving. --- .../Carousel/DrawableCarouselBeatmap.cs | 3 + .../Select/Carousel/DrawableCarouselItem.cs | 55 ++++++++++++++----- 2 files changed, 43 insertions(+), 15 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 43ddfc79b1..a3483aa60a 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -71,6 +71,9 @@ namespace osu.Game.Screens.Select.Carousel { beatmapInfo = panel.BeatmapInfo; Item = panel; + + // Difficulty panels should start hidden for a better initial effect. + Hide(); } [BackgroundDependencyLoader(true)] diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs index 8b38bb9a96..4a23faa650 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs @@ -64,8 +64,6 @@ namespace osu.Game.Screens.Select.Carousel { RelativeSizeAxes = Axes.X; - Alpha = 0; - InternalChildren = new Drawable[] { MovementContainer = new Container @@ -119,29 +117,56 @@ namespace osu.Game.Screens.Select.Carousel private void onStateChange(ValueChangedEvent _) => Scheduler.AddOnce(ApplyState); + private CarouselItemState? lastAppliedState; + protected virtual void ApplyState() { - // Use the fact that we know the precise height of the item from the model to avoid the need for AutoSize overhead. - // Additionally, AutoSize doesn't work well due to content starting off-screen and being masked away. - Height = Item.TotalHeight; - Debug.Assert(Item != null); - switch (Item.State.Value) + if (lastAppliedState != Item.State.Value) { - case CarouselItemState.NotSelected: - Deselected(); - break; + lastAppliedState = Item.State.Value; - case CarouselItemState.Selected: - Selected(); - break; + // Use the fact that we know the precise height of the item from the model to avoid the need for AutoSize overhead. + // Additionally, AutoSize doesn't work well due to content starting off-screen and being masked away. + Height = Item.TotalHeight; + + switch (lastAppliedState) + { + case CarouselItemState.NotSelected: + Deselected(); + break; + + case CarouselItemState.Selected: + Selected(); + break; + } } if (!Item.Visible) - this.FadeOut(300, Easing.OutQuint); + Hide(); else - this.FadeIn(250); + Show(); + } + + private bool isVisible = true; + + public override void Show() + { + if (isVisible) + return; + + isVisible = true; + this.FadeIn(250); + } + + public override void Hide() + { + if (!isVisible) + return; + + isVisible = false; + this.FadeOut(300, Easing.OutQuint); } protected virtual void Selected() From 2ee0db0ebf68bc6651171c78e077418e952d71dd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jan 2022 14:07:29 +0900 Subject: [PATCH 818/996] Move fade in function local --- .../Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 618c5cf5ec..4f19e011b2 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -124,9 +124,9 @@ namespace osu.Game.Screens.Select.Carousel background.DelayedLoadComplete += fadeContentIn; mainFlow.DelayedLoadComplete += fadeContentIn; - } - private void fadeContentIn(Drawable d) => d.FadeInFromZero(750, Easing.OutQuint); + static void fadeContentIn(Drawable d) => d.FadeInFromZero(750, Easing.OutQuint); + } protected override void Deselected() { From 8917ab78f427c1dfb2e5eef47738fafec8fa746a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jan 2022 14:14:50 +0900 Subject: [PATCH 819/996] Reduce unnecessary container nesting and adjust empty state opacity slightly --- osu.Game/Screens/Select/BeatmapCarousel.cs | 6 ++-- .../Screens/Select/Carousel/CarouselHeader.cs | 29 ++++++++----------- .../Select/Carousel/DrawableCarouselItem.cs | 2 -- 3 files changed, 14 insertions(+), 23 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index c27915c383..fbb12a86d3 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -894,10 +894,8 @@ namespace osu.Game.Screens.Select // child items (difficulties) are still visible. item.Header.X = offsetX(dist, visibleHalfHeight) - (parent?.X ?? 0); - // We are applying a multiplicative alpha (which is internally done by nesting an - // additional container and setting that container's alpha) such that we can - // layer alpha transformations on top. - item.SetMultiplicativeAlpha(Math.Clamp(1.75f - 1.5f * dist, 0, 1)); + // We are applying alpha to the header here such that we can layer alpha transformations on top. + item.Header.Alpha = Math.Clamp(1.75f - 1.5f * dist, 0, 1); } private enum PendingScrollOperation diff --git a/osu.Game/Screens/Select/Carousel/CarouselHeader.cs b/osu.Game/Screens/Select/Carousel/CarouselHeader.cs index ed3aea3445..533694b265 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselHeader.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselHeader.cs @@ -21,8 +21,6 @@ namespace osu.Game.Screens.Select.Carousel { public class CarouselHeader : Container { - public Container BorderContainer; - public readonly Bindable State = new Bindable(CarouselItemState.NotSelected); private readonly HoverLayer hoverLayer; @@ -37,17 +35,14 @@ namespace osu.Game.Screens.Select.Carousel RelativeSizeAxes = Axes.X; Height = DrawableCarouselItem.MAX_HEIGHT; - InternalChild = BorderContainer = new Container + Masking = true; + CornerRadius = corner_radius; + BorderColour = new Color4(221, 255, 255, 255); + + InternalChildren = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Masking = true, - CornerRadius = corner_radius, - BorderColour = new Color4(221, 255, 255, 255), - Children = new Drawable[] - { - Content, - hoverLayer = new HoverLayer() - } + Content, + hoverLayer = new HoverLayer() }; } @@ -66,21 +61,21 @@ namespace osu.Game.Screens.Select.Carousel case CarouselItemState.NotSelected: hoverLayer.InsetForBorder = false; - BorderContainer.BorderThickness = 0; - BorderContainer.EdgeEffect = new EdgeEffectParameters + BorderThickness = 0; + EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Shadow, Offset = new Vector2(1), Radius = 10, - Colour = Color4.Black.Opacity(100), + Colour = Color4.Black.Opacity(0.5f), }; break; case CarouselItemState.Selected: hoverLayer.InsetForBorder = true; - BorderContainer.BorderThickness = border_thickness; - BorderContainer.EdgeEffect = new EdgeEffectParameters + BorderThickness = border_thickness; + EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Glow, Colour = new Color4(130, 204, 255, 150), diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs index 4a23faa650..5e7ca0825a 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs @@ -85,8 +85,6 @@ namespace osu.Game.Screens.Select.Carousel }; } - public void SetMultiplicativeAlpha(float alpha) => Header.BorderContainer.Alpha = alpha; - protected override void LoadComplete() { base.LoadComplete(); From 6bc6675fa1bff93de775e10b6cf6781af9bdb3f1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 31 Jan 2022 14:46:16 +0900 Subject: [PATCH 820/996] Adjust fade in times slightly --- .../Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 4f19e011b2..63c004f4bc 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -122,10 +122,8 @@ namespace osu.Game.Screens.Select.Carousel }, }; - background.DelayedLoadComplete += fadeContentIn; - mainFlow.DelayedLoadComplete += fadeContentIn; - - static void fadeContentIn(Drawable d) => d.FadeInFromZero(750, Easing.OutQuint); + background.DelayedLoadComplete += d => d.FadeInFromZero(750, Easing.OutQuint); + mainFlow.DelayedLoadComplete += d => d.FadeInFromZero(500, Easing.OutQuint); } protected override void Deselected() From 57f793aff0e6f562198b732dea85f9bd8c154dee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 31 Jan 2022 15:12:08 +0900 Subject: [PATCH 821/996] Rename dictionary and make `private` for added safety --- osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs | 2 +- osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index d1a25b07ec..917b3c89a8 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -215,7 +215,7 @@ namespace osu.Game.Tests.Visual.Gameplay }); // We can't access API because we're an "online" test. - AddAssert("last received frame has time = 1000", () => spectatorClient.LastReceivedFrame.First().Value.Time == 1000); + AddAssert("last received frame has time = 1000", () => spectatorClient.LastReceivedUserFrames.First().Value.Time == 1000); } private OsuFramedReplayInputHandler replayHandler => diff --git a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs index 3f3d0f5b01..1a1d493249 100644 --- a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs +++ b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs @@ -28,7 +28,10 @@ namespace osu.Game.Tests.Visual.Spectator public override IBindable IsConnected { get; } = new Bindable(true); - public readonly Dictionary LastReceivedFrame = new Dictionary(); + public IReadOnlyDictionary LastReceivedUserFrames => lastReceivedUserFrames; + + private readonly Dictionary lastReceivedUserFrames = new Dictionary(); + private readonly Dictionary userBeatmapDictionary = new Dictionary(); private readonly Dictionary userNextFrameDictionary = new Dictionary(); @@ -40,7 +43,7 @@ namespace osu.Game.Tests.Visual.Spectator OnNewFrames += (i, bundle) => { if (PlayingUsers.Contains(i)) - LastReceivedFrame[i] = bundle.Frames[^1]; + lastReceivedUserFrames[i] = bundle.Frames[^1]; }; } From 9001c3a396a7f876bc52d772e1567d62161770f1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 31 Jan 2022 15:17:06 +0900 Subject: [PATCH 822/996] Fix test failures if `DialogOverlay` is not loaded in time As seen at https://github.com/ppy/osu/runs/4999391205?check_suite_focus=true, where `DialogOverlay` hasn't loaded in single file yet. --- osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index 3be7cf7c5c..ed484e03f6 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -128,7 +128,9 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("choose clear all scores", () => InputManager.Key(Key.Number4)); + AddUntilStep("wait for dialog", () => Game.Dependencies.Get().CurrentDialog != null); AddStep("confirm deletion", () => InputManager.Key(Key.Number1)); + AddUntilStep("wait for dialog dismissed", () => Game.Dependencies.Get().CurrentDialog == null); AddAssert("ensure score is pending deletion", () => Game.Realm.Run(r => r.Find(score.ID)?.DeletePending == true)); @@ -170,7 +172,9 @@ namespace osu.Game.Tests.Visual.Navigation InputManager.Click(MouseButton.Left); }); + AddUntilStep("wait for dialog", () => Game.Dependencies.Get().CurrentDialog != null); AddStep("confirm deletion", () => InputManager.Key(Key.Number1)); + AddUntilStep("wait for dialog dismissed", () => Game.Dependencies.Get().CurrentDialog == null); AddAssert("ensure score is pending deletion", () => Game.Realm.Run(r => r.Find(score.ID)?.DeletePending == true)); From 25cbe5a0dee2ef50bdf32bdc8a2b09bc9c98446d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 31 Jan 2022 15:23:31 +0900 Subject: [PATCH 823/996] Remove acronym shortening of `GameplayClockContainer` --- .../TestSceneMasterGameplayClockContainer.cs | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs b/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs index 172531605a..77b402ad3c 100644 --- a/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs +++ b/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs @@ -29,44 +29,44 @@ namespace osu.Game.Tests.Gameplay [Test] public void TestStartThenElapsedTime() { - GameplayClockContainer gcc = null; + GameplayClockContainer gameplayClockContainer = null; AddStep("create container", () => { var working = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); working.LoadTrack(); - Add(gcc = new MasterGameplayClockContainer(working, 0)); + Add(gameplayClockContainer = new MasterGameplayClockContainer(working, 0)); }); - AddStep("start clock", () => gcc.Start()); - AddUntilStep("elapsed greater than zero", () => gcc.GameplayClock.ElapsedFrameTime > 0); + AddStep("start clock", () => gameplayClockContainer.Start()); + AddUntilStep("elapsed greater than zero", () => gameplayClockContainer.GameplayClock.ElapsedFrameTime > 0); } [Test] public void TestElapseThenReset() { - GameplayClockContainer gcc = null; + GameplayClockContainer gameplayClockContainer = null; AddStep("create container", () => { var working = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); working.LoadTrack(); - Add(gcc = new MasterGameplayClockContainer(working, 0)); + Add(gameplayClockContainer = new MasterGameplayClockContainer(working, 0)); }); - AddStep("start clock", () => gcc.Start()); - AddUntilStep("current time greater 2000", () => gcc.GameplayClock.CurrentTime > 2000); + AddStep("start clock", () => gameplayClockContainer.Start()); + AddUntilStep("current time greater 2000", () => gameplayClockContainer.GameplayClock.CurrentTime > 2000); double timeAtReset = 0; AddStep("reset clock", () => { - timeAtReset = gcc.GameplayClock.CurrentTime; - gcc.Reset(); + timeAtReset = gameplayClockContainer.GameplayClock.CurrentTime; + gameplayClockContainer.Reset(); }); - AddAssert("current time < time at reset", () => gcc.GameplayClock.CurrentTime < timeAtReset); + AddAssert("current time < time at reset", () => gameplayClockContainer.GameplayClock.CurrentTime < timeAtReset); } [Test] @@ -76,29 +76,29 @@ namespace osu.Game.Tests.Gameplay [Values(false, true)] bool whileStopped) { ClockBackedTestWorkingBeatmap working = null; - GameplayClockContainer gcc = null; + GameplayClockContainer gameplayClockContainer = null; AddStep("create container", () => { working = new ClockBackedTestWorkingBeatmap(new OsuRuleset().RulesetInfo, new FramedClock(new ManualClock()), Audio); working.LoadTrack(); - Add(gcc = new MasterGameplayClockContainer(working, 0)); + Add(gameplayClockContainer = new MasterGameplayClockContainer(working, 0)); if (whileStopped) - gcc.Stop(); + gameplayClockContainer.Stop(); - gcc.Reset(); + gameplayClockContainer.Reset(); }); AddStep($"set clock rate to {clockRate}", () => working.Track.AddAdjustment(AdjustableProperty.Frequency, new BindableDouble(clockRate))); AddStep($"set audio offset to {userOffset}", () => localConfig.SetValue(OsuSetting.AudioOffset, userOffset)); - AddStep("seek to 2500", () => gcc.Seek(2500)); - AddAssert("gameplay clock time = 2500", () => Precision.AlmostEquals(gcc.CurrentTime, 2500, 10f)); + AddStep("seek to 2500", () => gameplayClockContainer.Seek(2500)); + AddAssert("gameplay clock time = 2500", () => Precision.AlmostEquals(gameplayClockContainer.CurrentTime, 2500, 10f)); - AddStep("seek to 10000", () => gcc.Seek(10000)); - AddAssert("gameplay clock time = 10000", () => Precision.AlmostEquals(gcc.CurrentTime, 10000, 10f)); + AddStep("seek to 10000", () => gameplayClockContainer.Seek(10000)); + AddAssert("gameplay clock time = 10000", () => Precision.AlmostEquals(gameplayClockContainer.CurrentTime, 10000, 10f)); } protected override void Dispose(bool isDisposing) From cc7fb0e559b096566b2874607f2ff5f57ca8e0e3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 31 Jan 2022 16:36:56 +0900 Subject: [PATCH 824/996] Add mouse click support and increase area to full column height --- osu.Game.Rulesets.Mania/UI/Column.cs | 72 +++++++++++++++------------- 1 file changed, 40 insertions(+), 32 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index cd5f3d2170..9bcd9ad853 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -28,7 +28,6 @@ namespace osu.Game.Rulesets.Mania.UI { public const float COLUMN_WIDTH = 80; public const float SPECIAL_COLUMN_WIDTH = 70; - public const float TOUCH_AREA_HEIGHT = 100; /// /// The index of this column as part of the whole playfield. @@ -45,37 +44,6 @@ namespace osu.Game.Rulesets.Mania.UI private readonly GameplaySampleTriggerSource sampleTriggerSource; - public class ColumnTouchInputArea : Drawable - { - public ColumnTouchInputArea() - { - RelativeSizeAxes = Axes.X; - Anchor = Anchor.BottomCentre; - Origin = Anchor.BottomCentre; - Height = TOUCH_AREA_HEIGHT; - } - - private Column column => (Column)Parent; - - protected override void LoadComplete() - { - keyBindingContainer = (ManiaInputManager.RulesetKeyBindingContainer)((ManiaInputManager)GetContainingInputManager()).KeyBindingContainer; - } - - private ManiaInputManager.RulesetKeyBindingContainer keyBindingContainer { get; set; } - - protected override bool OnTouchDown(TouchDownEvent e) - { - keyBindingContainer.TriggerPressed(column.Action.Value); - return false; - } - - protected override void OnTouchUp(TouchUpEvent e) - { - keyBindingContainer.TriggerReleased(column.Action.Value); - } - } - public Column(int index) { Index = index; @@ -172,5 +140,45 @@ namespace osu.Game.Rulesets.Mania.UI public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) // This probably shouldn't exist as is, but the columns in the stage are separated by a 1px border => DrawRectangle.Inflate(new Vector2(Stage.COLUMN_SPACING / 2, 0)).Contains(ToLocalSpace(screenSpacePos)); + + public class ColumnTouchInputArea : Drawable + { + public ColumnTouchInputArea() + { + RelativeSizeAxes = Axes.Both; + } + + private Column column => (Column)Parent; + + protected override void LoadComplete() + { + keyBindingContainer = (ManiaInputManager.RulesetKeyBindingContainer)((ManiaInputManager)GetContainingInputManager()).KeyBindingContainer; + } + + private ManiaInputManager.RulesetKeyBindingContainer keyBindingContainer { get; set; } + + protected override bool OnMouseDown(MouseDownEvent e) + { + keyBindingContainer.TriggerPressed(column.Action.Value); + return base.OnMouseDown(e); + } + + protected override void OnMouseUp(MouseUpEvent e) + { + keyBindingContainer.TriggerReleased(column.Action.Value); + base.OnMouseUp(e); + } + + protected override bool OnTouchDown(TouchDownEvent e) + { + keyBindingContainer.TriggerPressed(column.Action.Value); + return true; + } + + protected override void OnTouchUp(TouchUpEvent e) + { + keyBindingContainer.TriggerReleased(column.Action.Value); + } + } } } From 9005bce0fa574ec9be7661bbeb5029e1ad8797fc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 31 Jan 2022 16:37:47 +0900 Subject: [PATCH 825/996] Add "counter" keyword for key overlay setting --- osu.Game/Overlays/Settings/Sections/Gameplay/HUDSettings.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/HUDSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/HUDSettings.cs index e1b452e322..ba9779d650 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/HUDSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/HUDSettings.cs @@ -37,7 +37,8 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay new SettingsCheckbox { LabelText = GameplaySettingsStrings.AlwaysShowKeyOverlay, - Current = config.GetBindable(OsuSetting.KeyOverlay) + Current = config.GetBindable(OsuSetting.KeyOverlay), + Keywords = new[] { "counter" }, }, }; } From 0e764538e0ea81293ef99994b25e3ab7ceed609e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 31 Jan 2022 16:47:20 +0900 Subject: [PATCH 826/996] Retrieve `KeyBindingContainer` via DI rather than traversal lookup --- osu.Game.Rulesets.Mania/ManiaInputManager.cs | 2 ++ osu.Game.Rulesets.Mania/UI/Column.cs | 17 ++++++++++------- osu.Game/Rulesets/UI/RulesetInputManager.cs | 4 ++-- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Mania/ManiaInputManager.cs b/osu.Game.Rulesets.Mania/ManiaInputManager.cs index 186fc4b15d..14ca27a11a 100644 --- a/osu.Game.Rulesets.Mania/ManiaInputManager.cs +++ b/osu.Game.Rulesets.Mania/ManiaInputManager.cs @@ -2,11 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using System.ComponentModel; +using osu.Framework.Allocation; using osu.Framework.Input.Bindings; using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Mania { + [Cached] // Used for touch input, see ColumnTouchInputArea. public class ManiaInputManager : RulesetInputManager { public ManiaInputManager(RulesetInfo ruleset, int variant) diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 9bcd9ad853..a6c64e9b2d 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -143,6 +143,11 @@ namespace osu.Game.Rulesets.Mania.UI public class ColumnTouchInputArea : Drawable { + [Resolved(canBeNull: true)] + private ManiaInputManager maniaInputManager { get; set; } + + private KeyBindingContainer keyBindingContainer; + public ColumnTouchInputArea() { RelativeSizeAxes = Axes.Both; @@ -152,32 +157,30 @@ namespace osu.Game.Rulesets.Mania.UI protected override void LoadComplete() { - keyBindingContainer = (ManiaInputManager.RulesetKeyBindingContainer)((ManiaInputManager)GetContainingInputManager()).KeyBindingContainer; + keyBindingContainer = maniaInputManager?.KeyBindingContainer; } - private ManiaInputManager.RulesetKeyBindingContainer keyBindingContainer { get; set; } - protected override bool OnMouseDown(MouseDownEvent e) { - keyBindingContainer.TriggerPressed(column.Action.Value); + keyBindingContainer?.TriggerPressed(column.Action.Value); return base.OnMouseDown(e); } protected override void OnMouseUp(MouseUpEvent e) { - keyBindingContainer.TriggerReleased(column.Action.Value); + keyBindingContainer?.TriggerReleased(column.Action.Value); base.OnMouseUp(e); } protected override bool OnTouchDown(TouchDownEvent e) { - keyBindingContainer.TriggerPressed(column.Action.Value); + keyBindingContainer?.TriggerPressed(column.Action.Value); return true; } protected override void OnTouchUp(TouchUpEvent e) { - keyBindingContainer.TriggerReleased(column.Action.Value); + keyBindingContainer?.TriggerReleased(column.Action.Value); } } } diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 160c0a2606..84e42818be 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -24,6 +24,8 @@ namespace osu.Game.Rulesets.UI public abstract class RulesetInputManager : PassThroughInputManager, ICanAttachKeyCounter, IHasReplayHandler, IHasRecordingHandler where T : struct { + public readonly KeyBindingContainer KeyBindingContainer; + private ReplayRecorder recorder; public ReplayRecorder Recorder @@ -43,8 +45,6 @@ namespace osu.Game.Rulesets.UI protected override InputState CreateInitialState() => new RulesetInputManagerInputState(base.CreateInitialState()); - public readonly KeyBindingContainer KeyBindingContainer; - protected override Container Content => content; private readonly Container content; From a49a9ed0a05d778b5af4c6328071bb0f8926d7b5 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 31 Jan 2022 17:19:04 +0900 Subject: [PATCH 827/996] Fix incorrect invoke --- osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs index f5a6777a62..4979bd906b 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs @@ -101,7 +101,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate stack.Push(new MultiSpectatorPlayerLoader(Score, () => { var player = new MultiSpectatorPlayer(Score, GameplayClock); - player.OnGameplayStarted += OnGameplayStarted; + player.OnGameplayStarted += () => OnGameplayStarted?.Invoke(); return player; })); From 4727aeda012f6c5f00f500d0eaf4c05ba8bbc58c Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 31 Jan 2022 18:32:17 +0900 Subject: [PATCH 828/996] Give last bundled replay frame the frame header --- .../Visual/Gameplay/TestSceneSpectator.cs | 16 ++++++++++++++++ osu.Game/Online/Spectator/SpectatorClient.cs | 3 +++ osu.Game/Rulesets/Replays/ReplayFrame.cs | 13 +++++++++++++ osu.Game/Screens/Play/SpectatorPlayer.cs | 1 + 4 files changed, 33 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index 917b3c89a8..a7b9d45f7a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -218,6 +218,22 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("last received frame has time = 1000", () => spectatorClient.LastReceivedUserFrames.First().Value.Time == 1000); } + [Test] + public void TestFinalFrameInBundleHasHeader() + { + FrameDataBundle lastBundle = null; + + AddStep("bind to client", () => spectatorClient.OnNewFrames += (_, bundle) => lastBundle = bundle); + + start(-1234); + sendFrames(); + finish(); + + AddUntilStep("bundle received", () => lastBundle != null); + AddAssert("first frame does not have header", () => lastBundle.Frames[0].Header == null); + AddAssert("last frame has header", () => lastBundle.Frames[^1].Header != null); + } + private OsuFramedReplayInputHandler replayHandler => (OsuFramedReplayInputHandler)Stack.ChildrenOfType().First().ReplayInputHandler; diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index fddb94fad7..67aa75727d 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -129,6 +129,9 @@ namespace osu.Game.Online.Spectator Task ISpectatorClient.UserSentFrames(int userId, FrameDataBundle data) { + if (data.Frames.Count > 0) + data.Frames[^1].Header = data.Header; + Schedule(() => OnNewFrames?.Invoke(userId, data)); return Task.CompletedTask; diff --git a/osu.Game/Rulesets/Replays/ReplayFrame.cs b/osu.Game/Rulesets/Replays/ReplayFrame.cs index 7de53211a2..2b67b60d8f 100644 --- a/osu.Game/Rulesets/Replays/ReplayFrame.cs +++ b/osu.Game/Rulesets/Replays/ReplayFrame.cs @@ -1,16 +1,29 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +#nullable enable + using MessagePack; +using osu.Game.Online.Spectator; namespace osu.Game.Rulesets.Replays { [MessagePackObject] public class ReplayFrame { + /// + /// The time at which this takes place. + /// [Key(0)] public double Time; + /// + /// A containing the state of a play after this takes place. + /// May be omitted where exact per-frame accuracy is not required. + /// + [IgnoreMember] + public FrameHeader? Header; + public ReplayFrame() { } diff --git a/osu.Game/Screens/Play/SpectatorPlayer.cs b/osu.Game/Screens/Play/SpectatorPlayer.cs index d42643c416..c415041081 100644 --- a/osu.Game/Screens/Play/SpectatorPlayer.cs +++ b/osu.Game/Screens/Play/SpectatorPlayer.cs @@ -72,6 +72,7 @@ namespace osu.Game.Screens.Play var convertedFrame = (ReplayFrame)convertibleFrame; convertedFrame.Time = frame.Time; + convertedFrame.Header = frame.Header; score.Replay.Frames.Add(convertedFrame); } From 0458d408bb36b15c00c47634926dea59b116205c Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 31 Jan 2022 18:37:51 +0900 Subject: [PATCH 829/996] Add replay statistics frames to FramedReplayInputHandler --- .../EmptyFreeformFramedReplayInputHandler.cs | 2 +- .../PippidonFramedReplayInputHandler.cs | 2 +- .../EmptyScrollingFramedReplayInputHandler.cs | 2 +- .../PippidonFramedReplayInputHandler.cs | 2 +- .../Replays/CatchFramedReplayInputHandler.cs | 2 +- .../Replays/ManiaFramedReplayInputHandler.cs | 2 +- .../Replays/OsuFramedReplayInputHandler.cs | 2 +- .../Replays/TaikoFramedReplayInputHandler.cs | 2 +- .../Gameplay/TestSceneReplayRecorder.cs | 2 +- .../Gameplay/TestSceneSpectatorPlayback.cs | 2 +- osu.Game/Input/Handlers/ReplayInputHandler.cs | 34 +++++++++++++++++++ .../Replays/FramedReplayInputHandler.cs | 15 ++++++++ 12 files changed, 59 insertions(+), 10 deletions(-) diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Replays/EmptyFreeformFramedReplayInputHandler.cs b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Replays/EmptyFreeformFramedReplayInputHandler.cs index cc4483de31..a9bc8dc10e 100644 --- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Replays/EmptyFreeformFramedReplayInputHandler.cs +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Replays/EmptyFreeformFramedReplayInputHandler.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.EmptyFreeform.Replays protected override bool IsImportant(EmptyFreeformReplayFrame frame) => frame.Actions.Any(); - public override void CollectPendingInputs(List inputs) + protected override void CollectReplayInputs(List inputs) { var position = Interpolation.ValueAt(CurrentTime, StartFrame.Position, EndFrame.Position, StartFrame.Time, EndFrame.Time); diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs index e005346e1e..dbfaf8a01d 100644 --- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Pippidon.Replays protected override bool IsImportant(PippidonReplayFrame frame) => true; - public override void CollectPendingInputs(List inputs) + protected override void CollectReplayInputs(List inputs) { var position = Interpolation.ValueAt(CurrentTime, StartFrame.Position, EndFrame.Position, StartFrame.Time, EndFrame.Time); diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Replays/EmptyScrollingFramedReplayInputHandler.cs b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Replays/EmptyScrollingFramedReplayInputHandler.cs index 4b998cfca3..1d33ab8a54 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Replays/EmptyScrollingFramedReplayInputHandler.cs +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Replays/EmptyScrollingFramedReplayInputHandler.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.EmptyScrolling.Replays protected override bool IsImportant(EmptyScrollingReplayFrame frame) => frame.Actions.Any(); - public override void CollectPendingInputs(List inputs) + protected override void CollectReplayInputs(List inputs) { inputs.Add(new ReplayState { diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs index 7652357b4d..702f6fdb04 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Pippidon.Replays protected override bool IsImportant(PippidonReplayFrame frame) => frame.Actions.Any(); - public override void CollectPendingInputs(List inputs) + protected override void CollectReplayInputs(List inputs) { inputs.Add(new ReplayState { diff --git a/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs b/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs index bd742ce6a6..b6af88a771 100644 --- a/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs +++ b/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Catch.Replays protected override bool IsImportant(CatchReplayFrame frame) => frame.Actions.Any(); - public override void CollectPendingInputs(List inputs) + protected override void CollectReplayInputs(List inputs) { float position = Interpolation.ValueAt(CurrentTime, StartFrame.Position, EndFrame.Position, StartFrame.Time, EndFrame.Time); diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs b/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs index aa0c148caf..aa164f95da 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Mania.Replays protected override bool IsImportant(ManiaReplayFrame frame) => frame.Actions.Any(); - public override void CollectPendingInputs(List inputs) + protected override void CollectReplayInputs(List inputs) { inputs.Add(new ReplayState { PressedActions = CurrentFrame?.Actions ?? new List() }); } diff --git a/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs b/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs index 7d696dfb79..ea36ecc399 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Replays protected override bool IsImportant(OsuReplayFrame frame) => frame.Actions.Any(); - public override void CollectPendingInputs(List inputs) + protected override void CollectReplayInputs(List inputs) { var position = Interpolation.ValueAt(CurrentTime, StartFrame.Position, EndFrame.Position, StartFrame.Time, EndFrame.Time); diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs index 138e8f9785..2f9b6c7f60 100644 --- a/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs +++ b/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Taiko.Replays protected override bool IsImportant(TaikoReplayFrame frame) => frame.Actions.Any(); - public override void CollectPendingInputs(List inputs) + protected override void CollectReplayInputs(List inputs) { inputs.Add(new ReplayState { PressedActions = CurrentFrame?.Actions ?? new List() }); } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs index 4eab1a21da..8df32c500e 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs @@ -210,7 +210,7 @@ namespace osu.Game.Tests.Visual.Gameplay { } - public override void CollectPendingInputs(List inputs) + protected override void CollectReplayInputs(List inputs) { inputs.Add(new MousePositionAbsoluteInput { Position = GamefieldToScreenSpace(CurrentFrame?.Position ?? Vector2.Zero) }); inputs.Add(new ReplayState { PressedActions = CurrentFrame?.Actions ?? new List() }); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs index 90abdf2ba3..4af254866a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs @@ -259,7 +259,7 @@ namespace osu.Game.Tests.Visual.Gameplay { } - public override void CollectPendingInputs(List inputs) + protected override void CollectReplayInputs(List inputs) { inputs.Add(new MousePositionAbsoluteInput { Position = GamefieldToScreenSpace(CurrentFrame?.Position ?? Vector2.Zero) }); inputs.Add(new ReplayState { PressedActions = CurrentFrame?.Actions ?? new List() }); diff --git a/osu.Game/Input/Handlers/ReplayInputHandler.cs b/osu.Game/Input/Handlers/ReplayInputHandler.cs index e4aec4edac..205a1ea1ac 100644 --- a/osu.Game/Input/Handlers/ReplayInputHandler.cs +++ b/osu.Game/Input/Handlers/ReplayInputHandler.cs @@ -9,6 +9,7 @@ using osu.Framework.Input.StateChanges; using osu.Framework.Input.StateChanges.Events; using osu.Framework.Input.States; using osu.Framework.Platform; +using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.UI; using osuTK; @@ -79,5 +80,38 @@ namespace osu.Game.Input.Handlers PressedActions = pressedActions; } } + + /// + /// An that is triggered when a frame containing replay statistics arrives. + /// + public class ReplayStatisticsFrameInput : IInput + { + /// + /// The frame containing the statistics. + /// + public ReplayFrame Frame; + + public void Apply(InputState state, IInputStateChangeHandler handler) + { + handler.HandleInputStateChange(new ReplayStatisticsFrameEvent(state, this, Frame)); + } + } + + /// + /// An that is triggered when a frame containing replay statistics arrives. + /// + public class ReplayStatisticsFrameEvent : InputStateChangeEvent + { + /// + /// The frame containing the statistics. + /// + public readonly ReplayFrame Frame; + + public ReplayStatisticsFrameEvent(InputState state, IInput input, ReplayFrame frame) + : base(state, input) + { + Frame = frame; + } + } } } diff --git a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs index d3ee10dd23..f889d15485 100644 --- a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs +++ b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Input.StateChanges; using osu.Game.Input.Handlers; using osu.Game.Replays; @@ -174,5 +175,19 @@ namespace osu.Game.Rulesets.Replays return Frames[index].Time; } + + public sealed override void CollectPendingInputs(List inputs) + { + base.CollectPendingInputs(inputs); + + CollectReplayInputs(inputs); + + if (CurrentFrame?.Header != null) + inputs.Add(new ReplayStatisticsFrameInput { Frame = CurrentFrame }); + } + + protected virtual void CollectReplayInputs(List inputs) + { + } } } From 39e1d65976ba2d35e0c7729b21ca75fdf1745864 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 31 Jan 2022 18:43:10 +0900 Subject: [PATCH 830/996] Make ScoreProcessor write all judgement types --- osu.Game/Rulesets/Scoring/HitResult.cs | 2 +- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/HitResult.cs b/osu.Game/Rulesets/Scoring/HitResult.cs index 5599ed96a3..a254f9b760 100644 --- a/osu.Game/Rulesets/Scoring/HitResult.cs +++ b/osu.Game/Rulesets/Scoring/HitResult.cs @@ -176,7 +176,7 @@ namespace osu.Game.Rulesets.Scoring /// /// An array of all scorable s. /// - public static readonly HitResult[] SCORABLE_TYPES = ((HitResult[])Enum.GetValues(typeof(HitResult))).Where(r => r.IsScorable()).ToArray(); + public static readonly HitResult[] ALL_TYPES = ((HitResult[])Enum.GetValues(typeof(HitResult))).ToArray(); /// /// Whether a is valid within a given range. diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index fc24972b8e..fc5e982521 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -125,6 +125,8 @@ namespace osu.Game.Rulesets.Scoring if (result.FailedAtJudgement) return; + scoreResultCounts[result.Type] = scoreResultCounts.GetValueOrDefault(result.Type) + 1; + if (!result.Type.IsScorable()) return; @@ -151,8 +153,6 @@ namespace osu.Game.Rulesets.Scoring rollingMaxBaseScore += result.Judgement.MaxNumericResult; } - scoreResultCounts[result.Type] = scoreResultCounts.GetValueOrDefault(result.Type) + 1; - hitEvents.Add(CreateHitEvent(result)); lastHitObject = result.HitObject; @@ -175,6 +175,8 @@ namespace osu.Game.Rulesets.Scoring if (result.FailedAtJudgement) return; + scoreResultCounts[result.Type] = scoreResultCounts.GetValueOrDefault(result.Type) - 1; + if (!result.Type.IsScorable()) return; @@ -186,8 +188,6 @@ namespace osu.Game.Rulesets.Scoring rollingMaxBaseScore -= result.Judgement.MaxNumericResult; } - scoreResultCounts[result.Type] = scoreResultCounts.GetValueOrDefault(result.Type) - 1; - Debug.Assert(hitEvents.Count > 0); lastHitObject = hitEvents[^1].LastHitObject; hitEvents.RemoveAt(hitEvents.Count - 1); @@ -346,7 +346,7 @@ namespace osu.Game.Rulesets.Scoring score.Accuracy = Accuracy.Value; score.Rank = Rank.Value; - foreach (var result in HitResultExtensions.SCORABLE_TYPES) + foreach (var result in HitResultExtensions.ALL_TYPES) score.Statistics[result] = GetStatistic(result); score.HitEvents = hitEvents; From 4fb565e15f1be7d6fd740090f24cd2d98552aabb Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 31 Jan 2022 18:54:23 +0900 Subject: [PATCH 831/996] Reset ScoreProcessor from statistics replay frames --- .../Gameplay/TestSceneScoreProcessor.cs | 37 +++++++++ .../Rulesets/Scoring/JudgementProcessor.cs | 12 +++ osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 77 +++++++++++++++++-- osu.Game/Rulesets/UI/RulesetInputManager.cs | 32 +++++--- osu.Game/Screens/Play/Player.cs | 1 + 5 files changed, 144 insertions(+), 15 deletions(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs index 432e3df95e..bcaa70d062 100644 --- a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs +++ b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs @@ -1,11 +1,17 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using System.Collections.Generic; using NUnit.Framework; using osu.Framework.Testing; using osu.Game.Beatmaps; +using osu.Game.Online.Spectator; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Replays; using osu.Game.Rulesets.Scoring; using osu.Game.Tests.Visual; @@ -42,6 +48,37 @@ namespace osu.Game.Tests.Gameplay Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(Judgement.LARGE_BONUS_SCORE)); } + [Test] + public void TestResetFromReplayFrame() + { + var beatmap = new Beatmap { HitObjects = { new HitCircle() } }; + + var scoreProcessor = new ScoreProcessor(); + scoreProcessor.ApplyBeatmap(beatmap); + + scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], new TestJudgement(HitResult.Great)) { Type = HitResult.Great }); + Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(1_000_000)); + Assert.That(scoreProcessor.JudgedHits, Is.EqualTo(1)); + + // Reset with a miss instead. + scoreProcessor.ResetFromReplayFrame(new OsuRuleset(), new OsuReplayFrame + { + Header = new FrameHeader(0, 0, 0, new Dictionary { { HitResult.Miss, 1 } }, DateTimeOffset.Now) + }); + + Assert.That(scoreProcessor.TotalScore.Value, Is.Zero); + Assert.That(scoreProcessor.JudgedHits, Is.EqualTo(1)); + + // Reset with no judged hit. + scoreProcessor.ResetFromReplayFrame(new OsuRuleset(), new OsuReplayFrame + { + Header = new FrameHeader(0, 0, 0, new Dictionary(), DateTimeOffset.Now) + }); + + Assert.That(scoreProcessor.TotalScore.Value, Is.Zero); + Assert.That(scoreProcessor.JudgedHits, Is.Zero); + } + private class TestJudgement : Judgement { public override HitResult MaxResult { get; } diff --git a/osu.Game/Rulesets/Scoring/JudgementProcessor.cs b/osu.Game/Rulesets/Scoring/JudgementProcessor.cs index c3c4a2c949..f38155240e 100644 --- a/osu.Game/Rulesets/Scoring/JudgementProcessor.cs +++ b/osu.Game/Rulesets/Scoring/JudgementProcessor.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Replays; namespace osu.Game.Rulesets.Scoring { @@ -107,6 +108,17 @@ namespace osu.Game.Rulesets.Scoring JudgedHits = 0; } + public virtual void ResetFromReplayFrame(Ruleset ruleset, ReplayFrame frame) + { + if (frame.Header == null) + return; + + JudgedHits = 0; + + foreach ((_, int count) in frame.Header.Statistics) + JudgedHits += count; + } + /// /// Creates the that represents the scoring result for a . /// diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index fc5e982521..37ee3771df 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -7,9 +7,11 @@ using System.Diagnostics; using System.Linq; using osu.Framework.Bindables; using osu.Framework.Utils; +using osu.Game.Extensions; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Replays; using osu.Game.Scoring; namespace osu.Game.Rulesets.Scoring @@ -18,6 +20,11 @@ namespace osu.Game.Rulesets.Scoring { private const double max_score = 1000000; + /// + /// Invoked when this was reset from a replay frame. + /// + public event Action OnResetFromReplayFrame; + /// /// The current total score. /// @@ -329,12 +336,6 @@ namespace osu.Game.Rulesets.Scoring HighestCombo.Value = 0; } - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - hitEvents.Clear(); - } - /// /// Retrieve a score populated with data for the current play this processor is responsible for. /// @@ -351,6 +352,70 @@ namespace osu.Game.Rulesets.Scoring score.HitEvents = hitEvents; } + + /// + /// Maximum for a normal hit (i.e. not tick/bonus) for this ruleset. Only populated via . + /// + private HitResult? maxNormalResult; + + public override void ResetFromReplayFrame(Ruleset ruleset, ReplayFrame frame) + { + base.ResetFromReplayFrame(ruleset, frame); + + if (frame.Header == null) + return; + + baseScore = 0; + rollingMaxBaseScore = 0; + HighestCombo.Value = frame.Header.MaxCombo; + + foreach ((HitResult result, int count) in frame.Header.Statistics) + { + // Bonus scores are counted separately directly from the statistics dictionary later on. + if (!result.IsScorable() || result.IsBonus()) + continue; + + // The maximum result of this judgement if it wasn't a miss. + // E.g. For a GOOD judgement, the max result is either GREAT/PERFECT depending on which one the ruleset uses (osu!: GREAT, osu!mania: PERFECT). + HitResult maxResult; + + switch (result) + { + case HitResult.LargeTickHit: + case HitResult.LargeTickMiss: + maxResult = HitResult.LargeTickHit; + break; + + case HitResult.SmallTickHit: + case HitResult.SmallTickMiss: + maxResult = HitResult.SmallTickHit; + break; + + default: + maxResult = maxNormalResult ??= ruleset.GetHitResults().OrderByDescending(kvp => Judgement.ToNumericResult(kvp.result)).First().result; + break; + } + + for (int i = 0; i < count; i++) + { + baseScore += Judgement.ToNumericResult(result); + rollingMaxBaseScore += Judgement.ToNumericResult(maxResult); + } + } + + scoreResultCounts.Clear(); + scoreResultCounts.AddRange(frame.Header.Statistics); + + updateScore(); + + OnResetFromReplayFrame?.Invoke(); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + hitEvents.Clear(); + } } public enum ScoringMode diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 370c99ffaf..050ec82071 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -16,6 +16,7 @@ using osu.Game.Configuration; using osu.Game.Input; using osu.Game.Input.Bindings; using osu.Game.Input.Handlers; +using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play; using static osu.Game.Input.Handlers.ReplayInputHandler; @@ -24,6 +25,11 @@ namespace osu.Game.Rulesets.UI public abstract class RulesetInputManager : PassThroughInputManager, ICanAttachKeyCounter, IHasReplayHandler, IHasRecordingHandler where T : struct { + private readonly Ruleset ruleset; + + [Resolved(CanBeNull = true)] + private ScoreProcessor scoreProcessor { get; set; } + private ReplayRecorder recorder; public ReplayRecorder Recorder @@ -51,6 +57,8 @@ namespace osu.Game.Rulesets.UI protected RulesetInputManager(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) { + this.ruleset = ruleset.CreateInstance(); + InternalChild = KeyBindingContainer = CreateKeyBindingContainer(ruleset, variant, unique) .WithChild(content = new Container { RelativeSizeAxes = Axes.Both }); @@ -66,17 +74,23 @@ namespace osu.Game.Rulesets.UI public override void HandleInputStateChange(InputStateChangeEvent inputStateChange) { - if (inputStateChange is ReplayStateChangeEvent replayStateChanged) + switch (inputStateChange) { - foreach (var action in replayStateChanged.ReleasedActions) - KeyBindingContainer.TriggerReleased(action); + case ReplayStateChangeEvent stateChangeEvent: + foreach (var action in stateChangeEvent.ReleasedActions) + KeyBindingContainer.TriggerReleased(action); - foreach (var action in replayStateChanged.PressedActions) - KeyBindingContainer.TriggerPressed(action); - } - else - { - base.HandleInputStateChange(inputStateChange); + foreach (var action in stateChangeEvent.PressedActions) + KeyBindingContainer.TriggerPressed(action); + break; + + case ReplayStatisticsFrameEvent statisticsStateChangeEvent: + scoreProcessor?.ResetFromReplayFrame(ruleset, statisticsStateChangeEvent.Frame); + break; + + default: + base.HandleInputStateChange(inputStateChange); + break; } } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index cfca2d0a3d..e0fcca4eb7 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -165,6 +165,7 @@ namespace osu.Game.Screens.Play PrepareReplay(); ScoreProcessor.NewJudgement += result => ScoreProcessor.PopulateScore(Score.ScoreInfo); + ScoreProcessor.OnResetFromReplayFrame += () => ScoreProcessor.PopulateScore(Score.ScoreInfo); gameActive.BindValueChanged(_ => updatePauseOnFocusLostState(), true); } From e5601772a99aba7374077f986f158a1a839f2264 Mon Sep 17 00:00:00 2001 From: Spooghetti420 Date: Mon, 31 Jan 2022 15:00:36 +0000 Subject: [PATCH 832/996] Make incompatible with --- osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs | 3 +++ osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs | 2 ++ 2 files changed, 5 insertions(+) diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs index 42dc7fdc1f..1abca970d5 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.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 osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Objects; @@ -25,6 +26,8 @@ namespace osu.Game.Rulesets.Mania.Mods public override ModType Type => ModType.Conversion; + public override Type[] IncompatibleMods => new[] { typeof(ManiaModInvert) }; + public const double END_NOTE_ALLOW_THRESHOLD = 0.5; public void ApplyToBeatmap(IBeatmap beatmap) diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs index 1ea45c295c..4cbdaee323 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs @@ -26,6 +26,8 @@ namespace osu.Game.Rulesets.Mania.Mods public override ModType Type => ModType.Conversion; + public override Type[] IncompatibleMods => new[] { typeof(ManiaModHoldOff) }; + public void ApplyToBeatmap(IBeatmap beatmap) { var maniaBeatmap = (ManiaBeatmap)beatmap; From 2f88efd3c3c2f66f0bc8104f737cbc532527251f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Feb 2022 00:53:56 +0900 Subject: [PATCH 833/996] Pass column in rather than accessing parent --- osu.Game.Rulesets.Mania/UI/Column.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index a6c64e9b2d..dc3b86ac7f 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Mania.UI }, background, TopLevelContainer = new Container { RelativeSizeAxes = Axes.Both }, - new ColumnTouchInputArea() + new ColumnTouchInputArea(this) }; hitPolicy = new OrderedHitPolicy(HitObjectContainer); @@ -143,17 +143,19 @@ namespace osu.Game.Rulesets.Mania.UI public class ColumnTouchInputArea : Drawable { + private readonly Column column; + [Resolved(canBeNull: true)] private ManiaInputManager maniaInputManager { get; set; } private KeyBindingContainer keyBindingContainer; - public ColumnTouchInputArea() + public ColumnTouchInputArea(Column column) { RelativeSizeAxes = Axes.Both; - } - private Column column => (Column)Parent; + this.column = column; + } protected override void LoadComplete() { From 9227211a441eb2df90a2e0504e6bdaa08f94bfe4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 31 Jan 2022 22:56:27 +0100 Subject: [PATCH 834/996] Privatise `shouldAlternate` --- osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs index db8bd217f6..f68f2b5bee 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs @@ -25,7 +25,6 @@ namespace osu.Game.Rulesets.Osu.Mods public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay) }; public override ModType Type => ModType.DifficultyIncrease; public override IconUsage? Icon => FontAwesome.Solid.Keyboard; - public bool ShouldAlternate => !isBreakTime.Value && introEnded; private bool introEnded; private double earliestStartTime; @@ -34,6 +33,8 @@ namespace osu.Game.Rulesets.Osu.Mods private OsuAction? lastActionPressed; private DrawableRuleset ruleset; + private bool shouldAlternate => !isBreakTime.Value && introEnded; + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { ruleset = drawableRuleset; @@ -82,7 +83,7 @@ namespace osu.Game.Rulesets.Osu.Mods } public bool OnPressed(KeyBindingPressEvent e) - => mod.ShouldAlternate && mod.onPressed(e.Action); + => mod.shouldAlternate && mod.onPressed(e.Action); public void OnReleased(KeyBindingReleaseEvent e) { From c50577e25ff3d20c307cbec120b8436558cb6aa3 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 1 Feb 2022 10:48:41 +0900 Subject: [PATCH 835/996] Apply suggestion from review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 37ee3771df..79861c0ecc 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -396,11 +396,8 @@ namespace osu.Game.Rulesets.Scoring break; } - for (int i = 0; i < count; i++) - { - baseScore += Judgement.ToNumericResult(result); - rollingMaxBaseScore += Judgement.ToNumericResult(maxResult); - } + baseScore += count * Judgement.ToNumericResult(result); + rollingMaxBaseScore += count * Judgement.ToNumericResult(maxResult); } scoreResultCounts.Clear(); From 5a6d57efb77728df1d09dab9837a75e36311f611 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Feb 2022 12:43:35 +0900 Subject: [PATCH 836/996] Update fastlane and dependencies --- Gemfile.lock | 147 +++++++++++++++++++++++++++++---------------- fastlane/README.md | 2 +- 2 files changed, 95 insertions(+), 54 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 8ac863c9a8..86c8baabe6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,53 +1,75 @@ GEM remote: https://rubygems.org/ specs: - CFPropertyList (3.0.3) - addressable (2.7.0) + CFPropertyList (3.0.5) + rexml + addressable (2.8.0) public_suffix (>= 2.0.2, < 5.0) + artifactory (3.0.15) atomos (0.1.3) - aws-eventstream (1.1.0) - aws-partitions (1.413.0) - aws-sdk-core (3.110.0) + aws-eventstream (1.2.0) + aws-partitions (1.551.0) + aws-sdk-core (3.125.5) aws-eventstream (~> 1, >= 1.0.2) - aws-partitions (~> 1, >= 1.239.0) + aws-partitions (~> 1, >= 1.525.0) aws-sigv4 (~> 1.1) jmespath (~> 1.0) - aws-sdk-kms (1.40.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-kms (1.53.0) + aws-sdk-core (~> 3, >= 3.125.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.87.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-s3 (1.111.3) + aws-sdk-core (~> 3, >= 3.125.0) aws-sdk-kms (~> 1) - aws-sigv4 (~> 1.1) - aws-sigv4 (1.2.2) + aws-sigv4 (~> 1.4) + aws-sigv4 (1.4.0) aws-eventstream (~> 1, >= 1.0.2) babosa (1.0.4) - claide (1.0.3) + claide (1.1.0) colored (1.2) colored2 (3.1.2) commander-fastlane (4.4.6) highline (~> 1.7.2) declarative (0.0.20) - declarative-option (0.1.0) - digest-crc (0.6.3) + digest-crc (0.6.4) rake (>= 12.0.0, < 14.0.0) domain_name (0.5.20190701) unf (>= 0.0.5, < 1.0.0) dotenv (2.7.6) - emoji_regex (3.2.1) - excon (0.78.1) - faraday (1.2.0) - multipart-post (>= 1.2, < 3) - ruby2_keywords + emoji_regex (3.2.3) + excon (0.90.0) + faraday (1.9.3) + faraday-em_http (~> 1.0) + faraday-em_synchrony (~> 1.0) + faraday-excon (~> 1.1) + faraday-httpclient (~> 1.0) + faraday-multipart (~> 1.0) + faraday-net_http (~> 1.0) + faraday-net_http_persistent (~> 1.0) + faraday-patron (~> 1.0) + faraday-rack (~> 1.0) + faraday-retry (~> 1.0) + ruby2_keywords (>= 0.0.4) faraday-cookie_jar (0.0.7) faraday (>= 0.8.0) http-cookie (~> 1.0.0) - faraday_middleware (1.0.0) + faraday-em_http (1.0.0) + faraday-em_synchrony (1.0.0) + faraday-excon (1.1.0) + faraday-httpclient (1.0.1) + faraday-multipart (1.0.3) + multipart-post (>= 1.2, < 3) + faraday-net_http (1.0.1) + faraday-net_http_persistent (1.2.0) + faraday-patron (1.0.0) + faraday-rack (1.0.0) + faraday-retry (1.0.3) + faraday_middleware (1.2.0) faraday (~> 1.0) - fastimage (2.2.1) - fastlane (2.170.0) + fastimage (2.2.6) + fastlane (2.181.0) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.3, < 3.0.0) + artifactory (~> 3.0) aws-sdk-s3 (~> 1.0) babosa (>= 1.0.3, < 2.0.0) bundler (>= 1.12.0, < 3.0.0) @@ -68,6 +90,7 @@ GEM jwt (>= 2.1.0, < 3) mini_magick (>= 4.9.4, < 5.0.0) multipart-post (~> 2.0.0) + naturally (~> 2.2) plist (>= 3.1.0, < 4.0.0) rubyzip (>= 2.0.0, < 3.0.0) security (= 0.1.3) @@ -94,65 +117,80 @@ GEM representable (~> 3.0) retriable (>= 2.0, < 4.0) signet (~> 0.12) - google-cloud-core (1.5.0) + google-apis-core (0.4.2) + addressable (~> 2.5, >= 2.5.1) + googleauth (>= 0.16.2, < 2.a) + httpclient (>= 2.8.1, < 3.a) + mini_mime (~> 1.0) + representable (~> 3.0) + retriable (>= 2.0, < 4.a) + rexml + webrick + google-apis-iamcredentials_v1 (0.10.0) + google-apis-core (>= 0.4, < 2.a) + google-apis-storage_v1 (0.11.0) + google-apis-core (>= 0.4, < 2.a) + google-cloud-core (1.6.0) google-cloud-env (~> 1.0) google-cloud-errors (~> 1.0) - google-cloud-env (1.4.0) + google-cloud-env (1.5.0) faraday (>= 0.17.3, < 2.0) - google-cloud-errors (1.0.1) - google-cloud-storage (1.29.2) - addressable (~> 2.5) + google-cloud-errors (1.2.0) + google-cloud-storage (1.36.0) + addressable (~> 2.8) digest-crc (~> 0.4) - google-api-client (~> 0.33) - google-cloud-core (~> 1.2) - googleauth (~> 0.9) + google-apis-iamcredentials_v1 (~> 0.1) + google-apis-storage_v1 (~> 0.1) + google-cloud-core (~> 1.6) + googleauth (>= 0.16.2, < 2.a) mini_mime (~> 1.0) - googleauth (0.14.0) + googleauth (0.17.1) faraday (>= 0.17.3, < 2.0) jwt (>= 1.4, < 3.0) memoist (~> 0.16) multi_json (~> 1.11) os (>= 0.9, < 2.0) - signet (~> 0.14) + signet (~> 0.15) highline (1.7.10) - http-cookie (1.0.3) + http-cookie (1.0.4) domain_name (~> 0.5) httpclient (2.8.3) - jmespath (1.4.0) - json (2.5.1) - jwt (2.2.2) + jmespath (1.5.0) + json (2.6.1) + jwt (2.3.0) memoist (0.16.2) mini_magick (4.11.0) - mini_mime (1.0.2) + mini_mime (1.1.2) mini_portile2 (2.4.0) multi_json (1.15.0) multipart-post (2.0.0) nanaimo (0.3.0) - naturally (2.2.0) + naturally (2.2.1) nokogiri (1.10.10) mini_portile2 (~> 2.4.0) - os (1.1.1) - plist (3.5.0) + os (1.1.4) + plist (3.6.0) public_suffix (4.0.6) - rake (13.0.3) - representable (3.0.4) + rake (13.0.6) + representable (3.1.1) declarative (< 0.1.0) - declarative-option (< 0.2.0) + trailblazer-option (>= 0.1.1, < 0.2.0) uber (< 0.2.0) retriable (3.1.2) + rexml (3.2.5) rouge (2.0.7) - ruby2_keywords (0.0.2) - rubyzip (2.3.0) + ruby2_keywords (0.0.5) + rubyzip (2.3.2) security (0.1.3) - signet (0.14.0) - addressable (~> 2.3) + signet (0.16.0) + addressable (~> 2.8) faraday (>= 0.17.3, < 2.0) jwt (>= 1.5, < 3.0) multi_json (~> 1.10) simctl (1.6.8) CFPropertyList naturally - slack-notifier (2.3.2) + slack-notifier (2.4.0) souyuz (0.9.1) fastlane (>= 1.103.0) highline (~> 1.7) @@ -160,6 +198,7 @@ GEM terminal-notifier (2.0.0) terminal-table (1.8.0) unicode-display_width (~> 1.1, >= 1.1.1) + trailblazer-option (0.1.2) tty-cursor (0.7.1) tty-screen (0.8.1) tty-spinner (0.9.3) @@ -167,18 +206,20 @@ GEM uber (0.1.0) unf (0.1.4) unf_ext - unf_ext (0.0.7.7) - unicode-display_width (1.7.0) + unf_ext (0.0.8) + unicode-display_width (1.8.0) + webrick (1.7.0) word_wrap (1.0.0) - xcodeproj (1.19.0) + xcodeproj (1.21.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) colored2 (~> 3.1) nanaimo (~> 0.3.0) + rexml (~> 3.2.4) xcpretty (0.3.0) rouge (~> 2.0.7) - xcpretty-travis-formatter (1.0.0) + xcpretty-travis-formatter (1.0.1) xcpretty (~> 0.2, >= 0.0.7) PLATFORMS diff --git a/fastlane/README.md b/fastlane/README.md index a400ed9516..8273fdaa5d 100644 --- a/fastlane/README.md +++ b/fastlane/README.md @@ -12,7 +12,7 @@ Install _fastlane_ using ``` [sudo] gem install fastlane -NV ``` -or alternatively using `brew cask install fastlane` +or alternatively using `brew install fastlane` # Available Actions ## Android From aa492270dd9738bf9cf8c7dc0c4eb7543e155f8a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Feb 2022 13:30:26 +0900 Subject: [PATCH 837/996] Ignore `FodyWeavers.xml` files in subdirectories These are created when building specific projects rather than the main solution (typically iOS / android) and of no use to us. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index de6a3ac848..5b19270ab9 100644 --- a/.gitignore +++ b/.gitignore @@ -339,3 +339,4 @@ inspectcode # Fody (pulled in by Realm) - schema file FodyWeavers.xsd +**/FodyWeavers.xml From db973fb34876e1329c215f08dcee2dc7039ae422 Mon Sep 17 00:00:00 2001 From: dekrain Date: Tue, 1 Feb 2022 06:28:18 +0100 Subject: [PATCH 838/996] Add basic tooltip for leaderboard scores --- .../Online/Leaderboards/LeaderboardScore.cs | 5 +- .../Leaderboards/LeaderboardScoreTooltip.cs | 225 ++++++++++++++++++ 2 files changed, 229 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 906e09b8c1..7779043ccd 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -32,7 +32,7 @@ using osu.Game.Utils; namespace osu.Game.Online.Leaderboards { - public class LeaderboardScore : OsuClickableContainer, IHasContextMenu + public class LeaderboardScore : OsuClickableContainer, IHasContextMenu, IHasCustomTooltip { public const float HEIGHT = 60; @@ -70,6 +70,9 @@ namespace osu.Game.Online.Leaderboards [Resolved] private Storage storage { get; set; } + public ITooltip GetCustomTooltip() => new LeaderboardScoreTooltip(); + public ScoreInfo TooltipContent => Score; + public LeaderboardScore(ScoreInfo score, int? rank, bool isOnlineScope = true) { Score = score; diff --git a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs new file mode 100644 index 0000000000..78064c33bc --- /dev/null +++ b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs @@ -0,0 +1,225 @@ +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Game.Scoring; +using osuTK; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Online.Leaderboards +{ + public class LeaderboardScoreTooltip : VisibilityContainer, ITooltip + { + private OsuSpriteText timestampLabel; + private FillFlowContainer topScoreStatistics; + private FillFlowContainer bottomScoreStatistics; + private FillFlowContainer modStatistics; + + public LeaderboardScoreTooltip() + { + AutoSizeAxes = Axes.Both; + Masking = true; + CornerRadius = 5; + + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0.7f, + Colour = Colour4.Black, + }, + new GridContainer + { + Margin = new MarginPadding(5f), + AutoSizeAxes = Axes.Both, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize), + }, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + }, + Content = new[] + { + // Info row + new Drawable[] + { + timestampLabel = new OsuSpriteText() + { + Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), + } + }, + // Mods row + new Drawable[] + { + modStatistics = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + } + }, + // Actual stats rows + new Drawable[] + { + topScoreStatistics = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + } + }, + new Drawable[] + { + bottomScoreStatistics = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + } + }, + } + } + }; + } + + private ScoreInfo currentScore; + + public void SetContent(ScoreInfo score) + { + if (currentScore == score) + return; + currentScore = score; + + timestampLabel.Text = $"Played on {score.Date.ToLocalTime():d MMMM yyyy HH:mm}"; + + modStatistics.Clear(); + topScoreStatistics.Clear(); + bottomScoreStatistics.Clear(); + + foreach (var mod in score.Mods) + { + modStatistics.Add(new ModCell(mod)); + } + + foreach (var result in score.GetStatisticsForDisplay()) + { + (result.Result > HitResult.Perfect + ? bottomScoreStatistics + : topScoreStatistics + ).Add(new HitResultCell(result)); + } + } + + protected override void PopIn() => this.FadeIn(20, Easing.OutQuint); + protected override void PopOut() => this.FadeOut(80, Easing.OutQuint); + + public void Move(Vector2 pos) => Position = pos; + + private class HitResultCell : CompositeDrawable + { + readonly private string DisplayName; + readonly private HitResult Result; + readonly private int Count; + + public HitResultCell(HitResultDisplayStatistic stat) + { + AutoSizeAxes = Axes.Both; + Padding = new MarginPadding{ Horizontal = 5f }; + + DisplayName = stat.DisplayName; + Result = stat.Result; + Count = stat.Count; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + InternalChild = new FillFlowContainer + { + Height = 12, + AutoSizeAxes = Axes.X, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(2f, 0f), + Children = new Drawable[] + { + new CircularContainer + { + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Masking = true, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4Extensions.FromHex("#222") + }, + new OsuSpriteText + { + Padding = new MarginPadding{ Horizontal = 2f }, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.Torus.With(size: 12, weight: FontWeight.SemiBold), + Text = DisplayName.ToUpperInvariant(), + Colour = colours.ForHitResult(Result), + } + } + }, + new OsuSpriteText + { + RelativeSizeAxes = Axes.Y, + Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), + Text = Count.ToString(), + }, + } + }; + } + } + + private class ModCell : CompositeDrawable + { + readonly private Mod Mod; + + public ModCell(Mod mod) + { + AutoSizeAxes = Axes.Both; + Padding = new MarginPadding{ Horizontal = 5f }; + Mod = mod; + } + + [BackgroundDependencyLoader] + private void load() + { + InternalChild = new FillFlowContainer + { + Height = 15, + AutoSizeAxes = Axes.X, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(2f, 0f), + Children = new Drawable[] + { + new ModIcon(Mod, showTooltip: false).With(icon => + { + icon.Scale = new Vector2(15f / icon.Height); + }), + new OsuSpriteText + { + RelativeSizeAxes = Axes.Y, + Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), + Text = Mod.SettingDescription, + } + } + }; + } + } + } +} From 502e6af008757204e53f04eb5cbfe7f586558989 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 26 Jan 2022 00:21:54 +0900 Subject: [PATCH 839/996] Remove PlayingUsers list from SpectatorClient --- .../Gameplay/TestSceneSpectatorPlayback.cs | 38 ------------------- ...TestSceneMultiplayerGameplayLeaderboard.cs | 2 +- osu.Game/Online/Spectator/SpectatorClient.cs | 10 ----- .../Dashboard/CurrentlyPlayingDisplay.cs | 30 +++++++-------- .../Visual/Spectator/TestSpectatorClient.cs | 7 ++-- 5 files changed, 19 insertions(+), 68 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs index 90abdf2ba3..75369661a0 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs @@ -3,12 +3,9 @@ using System; using System.Collections.Generic; -using System.Collections.Specialized; -using System.Diagnostics; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -20,7 +17,6 @@ using osu.Framework.Testing; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Graphics.Sprites; -using osu.Game.Online.API; using osu.Game.Online.Spectator; using osu.Game.Replays; using osu.Game.Replays.Legacy; @@ -47,8 +43,6 @@ namespace osu.Game.Tests.Visual.Gameplay private Replay replay; - private readonly IBindableList users = new BindableList(); - private TestReplayRecorder recorder; private ManualClock manualClock; @@ -57,9 +51,6 @@ namespace osu.Game.Tests.Visual.Gameplay private TestFramedReplayInputHandler replayHandler; - [Resolved] - private IAPIProvider api { get; set; } - [Resolved] private SpectatorClient spectatorClient { get; set; } @@ -78,35 +69,6 @@ namespace osu.Game.Tests.Visual.Gameplay spectatorClient.OnNewFrames += onNewFrames; - users.BindTo(spectatorClient.PlayingUsers); - users.BindCollectionChanged((obj, args) => - { - switch (args.Action) - { - case NotifyCollectionChangedAction.Add: - Debug.Assert(args.NewItems != null); - - foreach (int user in args.NewItems) - { - if (user == api.LocalUser.Value.Id) - spectatorClient.WatchUser(user); - } - - break; - - case NotifyCollectionChangedAction.Remove: - Debug.Assert(args.OldItems != null); - - foreach (int user in args.OldItems) - { - if (user == api.LocalUser.Value.Id) - spectatorClient.StopWatchingUser(user); - } - - break; - } - }, true); - Children = new Drawable[] { new GridContainer diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs index 9b8e67b07a..8b71dfac21 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs @@ -115,7 +115,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public void RandomlyUpdateState() { - foreach (int userId in PlayingUsers) + foreach ((int userId, _) in PlayingUserStates) { if (RNG.NextBool()) continue; diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index fddb94fad7..c3e70edcd3 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -37,9 +37,6 @@ namespace osu.Game.Online.Spectator private readonly List watchingUsers = new List(); - public IBindableList PlayingUsers => playingUsers; - private readonly BindableList playingUsers = new BindableList(); - public IBindableDictionary PlayingUserStates => playingUserStates; private readonly BindableDictionary playingUserStates = new BindableDictionary(); @@ -88,10 +85,7 @@ namespace osu.Game.Online.Spectator BeginPlayingInternal(currentState); } else - { - playingUsers.Clear(); playingUserStates.Clear(); - } }), true); } @@ -99,9 +93,6 @@ namespace osu.Game.Online.Spectator { Schedule(() => { - if (!playingUsers.Contains(userId)) - playingUsers.Add(userId); - // UserBeganPlaying() is called by the server regardless of whether the local user is watching the remote user, and is called a further time when the remote user is watched. // This may be a temporary thing (see: https://github.com/ppy/osu-server-spectator/blob/2273778e02cfdb4a9c6a934f2a46a8459cb5d29c/osu.Server.Spectator/Hubs/SpectatorHub.cs#L28-L29). // We don't want the user states to update unless the player is being watched, otherwise calling BindUserBeganPlaying() can lead to double invocations. @@ -118,7 +109,6 @@ namespace osu.Game.Online.Spectator { Schedule(() => { - playingUsers.Remove(userId); playingUserStates.Remove(userId); OnUserFinishedPlaying?.Invoke(userId, state); diff --git a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs index fde20575fc..afbfb9f7ef 100644 --- a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs +++ b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs @@ -1,7 +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.Collections.Specialized; +using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -22,7 +22,7 @@ namespace osu.Game.Overlays.Dashboard { internal class CurrentlyPlayingDisplay : CompositeDrawable { - private readonly IBindableList playingUsers = new BindableList(); + private readonly IBindableDictionary playingUserStates = new BindableDictionary(); private FillFlowContainer userFlow; @@ -51,18 +51,20 @@ namespace osu.Game.Overlays.Dashboard { base.LoadComplete(); - playingUsers.BindTo(spectatorClient.PlayingUsers); - playingUsers.BindCollectionChanged(onUsersChanged, true); + playingUserStates.BindTo(spectatorClient.PlayingUserStates); + playingUserStates.BindCollectionChanged(onUsersChanged, true); } - private void onUsersChanged(object sender, NotifyCollectionChangedEventArgs e) => Schedule(() => + private void onUsersChanged(object sender, NotifyDictionaryChangedEventArgs e) => Schedule(() => { switch (e.Action) { - case NotifyCollectionChangedAction.Add: - foreach (int id in e.NewItems.OfType().ToArray()) + case NotifyDictionaryChangedAction.Add: + Debug.Assert(e.NewItems != null); + + foreach ((int userId, _) in e.NewItems) { - users.GetUserAsync(id).ContinueWith(task => + users.GetUserAsync(userId).ContinueWith(task => { var user = task.GetResultSafely(); @@ -71,7 +73,7 @@ namespace osu.Game.Overlays.Dashboard Schedule(() => { // user may no longer be playing. - if (!playingUsers.Contains(user.Id)) + if (!playingUserStates.ContainsKey(user.Id)) return; userFlow.Add(createUserPanel(user)); @@ -81,13 +83,11 @@ namespace osu.Game.Overlays.Dashboard break; - case NotifyCollectionChangedAction.Remove: - foreach (int u in e.OldItems.OfType()) - userFlow.FirstOrDefault(card => card.User.Id == u)?.Expire(); - break; + case NotifyDictionaryChangedAction.Remove: + Debug.Assert(e.OldItems != null); - case NotifyCollectionChangedAction.Reset: - userFlow.Clear(); + foreach ((int userId, _) in e.OldItems) + userFlow.FirstOrDefault(card => card.User.Id == userId)?.Expire(); break; } }); diff --git a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs index 1a1d493249..7848a825f4 100644 --- a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs +++ b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs @@ -6,7 +6,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -42,7 +41,7 @@ namespace osu.Game.Tests.Visual.Spectator { OnNewFrames += (i, bundle) => { - if (PlayingUsers.Contains(i)) + if (PlayingUserStates.ContainsKey(i)) lastReceivedUserFrames[i] = bundle.Frames[^1]; }; } @@ -65,7 +64,7 @@ namespace osu.Game.Tests.Visual.Spectator /// The user to end play for. public void EndPlay(int userId) { - if (!PlayingUsers.Contains(userId)) + if (!PlayingUserStates.ContainsKey(userId)) return; ((ISpectatorClient)this).UserFinishedPlaying(userId, new SpectatorState @@ -131,7 +130,7 @@ namespace osu.Game.Tests.Visual.Spectator protected override Task WatchUserInternal(int userId) { // When newly watching a user, the server sends the playing state immediately. - if (PlayingUsers.Contains(userId)) + if (PlayingUserStates.ContainsKey(userId)) sendPlayingState(userId); return Task.CompletedTask; From c2b775c0a39cebce5cf2d2494266484f17223094 Mon Sep 17 00:00:00 2001 From: dekrain Date: Tue, 1 Feb 2022 06:45:59 +0100 Subject: [PATCH 840/996] Minor alignment adjustments --- .../Leaderboards/LeaderboardScoreTooltip.cs | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs index 78064c33bc..bea589e47f 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs @@ -199,7 +199,8 @@ namespace osu.Game.Online.Leaderboards [BackgroundDependencyLoader] private void load() { - InternalChild = new FillFlowContainer + FillFlowContainer container; + InternalChild = container = new FillFlowContainer { Height = 15, AutoSizeAxes = Axes.X, @@ -209,16 +210,25 @@ namespace osu.Game.Online.Leaderboards { new ModIcon(Mod, showTooltip: false).With(icon => { + icon.Origin = Anchor.CentreLeft; + icon.Anchor = Anchor.CentreLeft; icon.Scale = new Vector2(15f / icon.Height); }), - new OsuSpriteText - { - RelativeSizeAxes = Axes.Y, - Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), - Text = Mod.SettingDescription, - } } }; + + string description = Mod.SettingDescription; + if (!string.IsNullOrEmpty(description)) + { + container.Add(new OsuSpriteText + { + RelativeSizeAxes = Axes.Y, + Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), + Text = Mod.SettingDescription, + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + }); + } } } } From 781cb9f18d55e21b153967ee389c1a45ca934e0a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 26 Jan 2022 01:45:11 +0900 Subject: [PATCH 841/996] Move HasPassed/HasFailed into GameplayState --- .../TestSceneTaikoSuddenDeath.cs | 2 +- .../Visual/Gameplay/TestSceneFailAnimation.cs | 2 +- .../Visual/Gameplay/TestSceneFailJudgement.cs | 2 +- .../Visual/Gameplay/TestScenePause.cs | 12 ++++++------ .../TestScenePlayerScoreSubmission.cs | 4 ++-- .../Gameplay/TestSceneStoryboardWithOutro.cs | 2 +- .../Navigation/TestSceneScreenNavigation.cs | 2 +- .../Multiplayer/MultiplayerPlayerLoader.cs | 2 +- osu.Game/Screens/Play/GameplayState.cs | 10 ++++++++++ osu.Game/Screens/Play/Player.cs | 19 ++++++------------- 10 files changed, 30 insertions(+), 27 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs index 0be005e1c4..eec88d7bf8 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs @@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Taiko.Tests Player.ScoreProcessor.NewJudgement += b => judged = true; }); AddUntilStep("swell judged", () => judged); - AddAssert("failed", () => Player.HasFailed); + AddAssert("failed", () => Player.GameplayState.HasFailed); } } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs index 7167d3120a..744227c55e 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs @@ -29,7 +29,7 @@ namespace osu.Game.Tests.Visual.Gameplay protected override void AddCheckSteps() { - AddUntilStep("wait for fail", () => Player.HasFailed); + AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed); AddUntilStep("wait for fail overlay", () => ((FailPlayer)Player).FailOverlay.State.Value == Visibility.Visible); // The pause screen and fail animation both ramp frequency. diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs index fa27e1abdd..6430c29dfa 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs @@ -21,7 +21,7 @@ namespace osu.Game.Tests.Visual.Gameplay protected override void AddCheckSteps() { - AddUntilStep("wait for fail", () => Player.HasFailed); + AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed); AddUntilStep("wait for multiple judgements", () => ((FailPlayer)Player).ScoreProcessor.JudgedHits > 1); AddAssert("total number of results == 1", () => { diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs index 04676f656f..ea0255ab76 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs @@ -185,7 +185,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestPauseAfterFail() { - AddUntilStep("wait for fail", () => Player.HasFailed); + AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed); AddUntilStep("fail overlay shown", () => Player.FailOverlayVisible); confirmClockRunning(false); @@ -201,7 +201,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestExitFromFailedGameplayAfterFailAnimation() { - AddUntilStep("wait for fail", () => Player.HasFailed); + AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed); AddUntilStep("wait for fail overlay shown", () => Player.FailOverlayVisible); confirmClockRunning(false); @@ -213,7 +213,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestExitFromFailedGameplayDuringFailAnimation() { - AddUntilStep("wait for fail", () => Player.HasFailed); + AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed); // will finish the fail animation and show the fail/pause screen. AddStep("attempt exit via pause key", () => Player.ExitViaPause()); @@ -227,7 +227,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestQuickRetryFromFailedGameplay() { - AddUntilStep("wait for fail", () => Player.HasFailed); + AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed); AddStep("quick retry", () => Player.GameplayClockContainer.ChildrenOfType().First().Action?.Invoke()); confirmExited(); @@ -236,7 +236,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestQuickExitFromFailedGameplay() { - AddUntilStep("wait for fail", () => Player.HasFailed); + AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed); AddStep("quick exit", () => Player.GameplayClockContainer.ChildrenOfType().First().Action?.Invoke()); confirmExited(); @@ -341,7 +341,7 @@ namespace osu.Game.Tests.Visual.Gameplay { confirmClockRunning(false); confirmNotExited(); - AddAssert("player not failed", () => !Player.HasFailed); + AddAssert("player not failed", () => !Player.GameplayState.HasFailed); AddAssert("pause overlay shown", () => Player.PauseOverlayVisible); } diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs index a9675a2ee2..58b5df2612 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs @@ -159,7 +159,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("wait for token request", () => Player.TokenCreationRequested); - AddUntilStep("wait for fail", () => Player.HasFailed); + AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed); AddStep("exit", () => Player.Exit()); AddAssert("ensure no submission", () => Player.SubmittedScore == null); @@ -176,7 +176,7 @@ namespace osu.Game.Tests.Visual.Gameplay addFakeHit(); - AddUntilStep("wait for fail", () => Player.HasFailed); + AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed); AddStep("exit", () => Player.Exit()); AddAssert("ensure failing submission", () => Player.SubmittedScore?.ScoreInfo.Passed == false); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs index 69798dcb82..b87183cbc7 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs @@ -96,7 +96,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("set storyboard duration to 0.6s", () => currentStoryboardDuration = 600); }); - AddUntilStep("wait for fail", () => Player.HasFailed); + AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed); AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= currentStoryboardDuration); AddUntilStep("wait for fail overlay", () => Player.FailOverlay.State.Value == Visibility.Visible); } diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index ed484e03f6..63984a1bed 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -209,7 +209,7 @@ namespace osu.Game.Tests.Visual.Navigation return (player = Game.ScreenStack.CurrentScreen as Player) != null; }); - AddUntilStep("wait for fail", () => player.HasFailed); + AddUntilStep("wait for fail", () => player.GameplayState.HasFailed); AddUntilStep("wait for track stop", () => !Game.MusicController.IsPlaying); AddAssert("Ensure time before preview point", () => Game.MusicController.CurrentTrack.CurrentTime < beatmap().BeatmapInfo.Metadata.PreviewTime); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayerLoader.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayerLoader.cs index 470ba59a76..772651727e 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayerLoader.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayerLoader.cs @@ -9,7 +9,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { public class MultiplayerPlayerLoader : PlayerLoader { - public bool GameplayPassed => player?.GameplayPassed == true; + public bool GameplayPassed => player?.GameplayState.HasPassed == true; private Player player; diff --git a/osu.Game/Screens/Play/GameplayState.cs b/osu.Game/Screens/Play/GameplayState.cs index 83881f739d..60fbfa6644 100644 --- a/osu.Game/Screens/Play/GameplayState.cs +++ b/osu.Game/Screens/Play/GameplayState.cs @@ -39,6 +39,16 @@ namespace osu.Game.Screens.Play /// public readonly Score Score; + /// + /// Whether gameplay completed without the user failing. + /// + public bool HasPassed { get; set; } + + /// + /// Whether the user failed during gameplay. + /// + public bool HasFailed { get; set; } + /// /// A bindable tracking the last judgement result applied to any hit object. /// diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 0312789b12..814aacd2fa 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -72,15 +72,8 @@ namespace osu.Game.Screens.Play /// protected virtual bool PauseOnFocusLost => true; - /// - /// Whether gameplay has completed without the user having failed. - /// - public bool GameplayPassed { get; private set; } - public Action RestartRequested; - public bool HasFailed { get; private set; } - private Bindable mouseWheelDisabled; private readonly Bindable storyboardReplacesBackground = new Bindable(); @@ -559,7 +552,7 @@ namespace osu.Game.Screens.Play if (showDialogFirst && !pauseOrFailDialogVisible) { // if the fail animation is currently in progress, accelerate it (it will show the pause dialog on completion). - if (ValidForResume && HasFailed) + if (ValidForResume && GameplayState.HasFailed) { failAnimationLayer.FinishTransforms(true); return; @@ -678,7 +671,7 @@ namespace osu.Game.Screens.Play resultsDisplayDelegate?.Cancel(); resultsDisplayDelegate = null; - GameplayPassed = false; + GameplayState.HasPassed = false; ValidForResume = true; skipOutroOverlay.Hide(); return; @@ -688,7 +681,7 @@ namespace osu.Game.Screens.Play if (HealthProcessor.HasFailed) return; - GameplayPassed = true; + GameplayState.HasPassed = true; // Setting this early in the process means that even if something were to go wrong in the order of events following, there // is no chance that a user could return to the (already completed) Player instance from a child screen. @@ -804,7 +797,7 @@ namespace osu.Game.Screens.Play if (!CheckModsAllowFailure()) return false; - HasFailed = true; + GameplayState.HasFailed = true; Score.ScoreInfo.Passed = false; // There is a chance that we could be in a paused state as the ruleset's internal clock (see FrameStabilityContainer) @@ -859,13 +852,13 @@ namespace osu.Game.Screens.Play // replays cannot be paused and exit immediately && !DrawableRuleset.HasReplayLoaded.Value // cannot pause if we are already in a fail state - && !HasFailed; + && !GameplayState.HasFailed; private bool canResume => // cannot resume from a non-paused state GameplayClockContainer.IsPaused.Value // cannot resume if we are already in a fail state - && !HasFailed + && !GameplayState.HasFailed // already resuming && !IsResuming; From 38e075c522f72371742e4d02a948f7a53a0aac5d Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 26 Jan 2022 02:02:31 +0900 Subject: [PATCH 842/996] Add HasQuit gameplay state --- osu.Game/Screens/Play/GameplayState.cs | 5 +++++ osu.Game/Screens/Play/Player.cs | 3 +++ 2 files changed, 8 insertions(+) diff --git a/osu.Game/Screens/Play/GameplayState.cs b/osu.Game/Screens/Play/GameplayState.cs index 60fbfa6644..64e873b3bb 100644 --- a/osu.Game/Screens/Play/GameplayState.cs +++ b/osu.Game/Screens/Play/GameplayState.cs @@ -49,6 +49,11 @@ namespace osu.Game.Screens.Play /// public bool HasFailed { get; set; } + /// + /// Whether the user quit gameplay without either having either passed or failed. + /// + public bool HasQuit { get; set; } + /// /// A bindable tracking the last judgement result applied to any hit object. /// diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 814aacd2fa..f6a2310826 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -983,6 +983,9 @@ namespace osu.Game.Screens.Play public override bool OnExiting(IScreen next) { + if (!GameplayState.HasPassed && !GameplayState.HasFailed) + GameplayState.HasQuit = true; + screenSuspension?.RemoveAndDisposeImmediately(); failAnimationLayer?.RemoveFilters(); From fd287e06f2bbe0f1115961184f837f0cd30927fe Mon Sep 17 00:00:00 2001 From: dekrain Date: Tue, 1 Feb 2022 06:51:00 +0100 Subject: [PATCH 843/996] Add missing license header --- osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs index bea589e47f..ea02c238f3 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs @@ -1,3 +1,6 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Containers; From d7b939277e81d99eb36aeb25f9aa71846b8b90c6 Mon Sep 17 00:00:00 2001 From: dekrain Date: Tue, 1 Feb 2022 07:10:00 +0100 Subject: [PATCH 844/996] Code quality improvements --- .../Leaderboards/LeaderboardScoreTooltip.cs | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs index ea02c238f3..0b2de3e2c8 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs @@ -19,10 +19,10 @@ namespace osu.Game.Online.Leaderboards { public class LeaderboardScoreTooltip : VisibilityContainer, ITooltip { - private OsuSpriteText timestampLabel; - private FillFlowContainer topScoreStatistics; - private FillFlowContainer bottomScoreStatistics; - private FillFlowContainer modStatistics; + private readonly OsuSpriteText timestampLabel; + private readonly FillFlowContainer topScoreStatistics; + private readonly FillFlowContainer bottomScoreStatistics; + private readonly FillFlowContainer modStatistics; public LeaderboardScoreTooltip() { @@ -58,7 +58,7 @@ namespace osu.Game.Online.Leaderboards // Info row new Drawable[] { - timestampLabel = new OsuSpriteText() + timestampLabel = new OsuSpriteText { Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), } @@ -98,8 +98,9 @@ namespace osu.Game.Online.Leaderboards public void SetContent(ScoreInfo score) { - if (currentScore == score) + if (currentScore.Equals(score)) return; + currentScore = score; timestampLabel.Text = $"Played on {score.Date.ToLocalTime():d MMMM yyyy HH:mm}"; @@ -116,9 +117,9 @@ namespace osu.Game.Online.Leaderboards foreach (var result in score.GetStatisticsForDisplay()) { (result.Result > HitResult.Perfect - ? bottomScoreStatistics - : topScoreStatistics - ).Add(new HitResultCell(result)); + ? bottomScoreStatistics + : topScoreStatistics + ).Add(new HitResultCell(result)); } } @@ -129,9 +130,9 @@ namespace osu.Game.Online.Leaderboards private class HitResultCell : CompositeDrawable { - readonly private string DisplayName; - readonly private HitResult Result; - readonly private int Count; + private readonly string DisplayName; + private readonly HitResult Result; + private readonly int Count; public HitResultCell(HitResultDisplayStatistic stat) { @@ -190,7 +191,7 @@ namespace osu.Game.Online.Leaderboards private class ModCell : CompositeDrawable { - readonly private Mod Mod; + private readonly Mod Mod; public ModCell(Mod mod) { From e1b57c4bf61d97070021f154a25cbd802b3133dc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Feb 2022 16:07:57 +0900 Subject: [PATCH 845/996] Fix inspections --- .../Leaderboards/LeaderboardScoreTooltip.cs | 37 ++++++++++--------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs index 0b2de3e2c8..7825a50a0b 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs @@ -119,7 +119,7 @@ namespace osu.Game.Online.Leaderboards (result.Result > HitResult.Perfect ? bottomScoreStatistics : topScoreStatistics - ).Add(new HitResultCell(result)); + ).Add(new HitResultCell(result)); } } @@ -130,18 +130,18 @@ namespace osu.Game.Online.Leaderboards private class HitResultCell : CompositeDrawable { - private readonly string DisplayName; - private readonly HitResult Result; - private readonly int Count; + private readonly string displayName; + private readonly HitResult result; + private readonly int count; public HitResultCell(HitResultDisplayStatistic stat) { AutoSizeAxes = Axes.Both; - Padding = new MarginPadding{ Horizontal = 5f }; + Padding = new MarginPadding { Horizontal = 5f }; - DisplayName = stat.DisplayName; - Result = stat.Result; - Count = stat.Count; + displayName = stat.DisplayName; + result = stat.Result; + count = stat.Count; } [BackgroundDependencyLoader] @@ -169,12 +169,12 @@ namespace osu.Game.Online.Leaderboards }, new OsuSpriteText { - Padding = new MarginPadding{ Horizontal = 2f }, + Padding = new MarginPadding { Horizontal = 2f }, Anchor = Anchor.Centre, Origin = Anchor.Centre, Font = OsuFont.Torus.With(size: 12, weight: FontWeight.SemiBold), - Text = DisplayName.ToUpperInvariant(), - Colour = colours.ForHitResult(Result), + Text = displayName.ToUpperInvariant(), + Colour = colours.ForHitResult(result), } } }, @@ -182,7 +182,7 @@ namespace osu.Game.Online.Leaderboards { RelativeSizeAxes = Axes.Y, Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), - Text = Count.ToString(), + Text = count.ToString(), }, } }; @@ -191,13 +191,13 @@ namespace osu.Game.Online.Leaderboards private class ModCell : CompositeDrawable { - private readonly Mod Mod; + private readonly Mod mod; public ModCell(Mod mod) { AutoSizeAxes = Axes.Both; - Padding = new MarginPadding{ Horizontal = 5f }; - Mod = mod; + Padding = new MarginPadding { Horizontal = 5f }; + this.mod = mod; } [BackgroundDependencyLoader] @@ -212,7 +212,7 @@ namespace osu.Game.Online.Leaderboards Spacing = new Vector2(2f, 0f), Children = new Drawable[] { - new ModIcon(Mod, showTooltip: false).With(icon => + new ModIcon(mod, showTooltip: false).With(icon => { icon.Origin = Anchor.CentreLeft; icon.Anchor = Anchor.CentreLeft; @@ -221,14 +221,15 @@ namespace osu.Game.Online.Leaderboards } }; - string description = Mod.SettingDescription; + string description = mod.SettingDescription; + if (!string.IsNullOrEmpty(description)) { container.Add(new OsuSpriteText { RelativeSizeAxes = Axes.Y, Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), - Text = Mod.SettingDescription, + Text = mod.SettingDescription, Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, }); From 855135c51e63f8a2095e7ae5dc8402a5df1b1920 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Feb 2022 16:13:27 +0900 Subject: [PATCH 846/996] Fix potential nullref during display due to incorrect equality check --- osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs index 7825a50a0b..85db59b3ea 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs @@ -15,6 +15,8 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; +#nullable enable + namespace osu.Game.Online.Leaderboards { public class LeaderboardScoreTooltip : VisibilityContainer, ITooltip @@ -94,14 +96,14 @@ namespace osu.Game.Online.Leaderboards }; } - private ScoreInfo currentScore; + private ScoreInfo? displayedScore; public void SetContent(ScoreInfo score) { - if (currentScore.Equals(score)) + if (displayedScore?.Equals(score) == true) return; - currentScore = score; + displayedScore = score; timestampLabel.Text = $"Played on {score.Date.ToLocalTime():d MMMM yyyy HH:mm}"; From fdb52a8fd70105516a2b4fb32cb6b3b4657d98ae Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Feb 2022 16:20:40 +0900 Subject: [PATCH 847/996] Remove gap in tooltip display between statistics --- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 7779043ccd..b4aaebd4e4 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -186,7 +186,6 @@ namespace osu.Game.Online.Leaderboards Anchor = Anchor.BottomLeft, AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, - Spacing = new Vector2(10f, 0f), Margin = new MarginPadding { Left = edge_margin }, Children = statisticsLabels }, @@ -231,7 +230,6 @@ namespace osu.Game.Online.Leaderboards Origin = Anchor.BottomRight, AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, - Spacing = new Vector2(1), ChildrenEnumerable = Score.Mods.Select(mod => new ModIcon(mod) { Scale = new Vector2(0.375f) }) }, }, @@ -316,6 +314,7 @@ namespace osu.Game.Online.Leaderboards { AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, + Padding = new MarginPadding { Right = 10 }, Children = new Drawable[] { new Container From 3ca2c906842209fe36e6d67ee7ace35f2cfad747 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Feb 2022 16:35:05 +0900 Subject: [PATCH 848/996] Add test scores in `BeatmapLeaderboard` test scene with more mods --- .../SongSelect/TestSceneBeatmapLeaderboard.cs | 35 +++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 48230ff9e9..7292ec96ed 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -197,7 +197,22 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 1, MaxCombo = 244, TotalScore = 1707827, - //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + Mods = new Mod[] + { + new OsuModHidden(), + new OsuModHardRock(), + new OsuModFlashlight + { + FollowDelay = { Value = 200 } + }, + new OsuModDifficultyAdjust + { + CircleSize = { Value = 8 }, + ApproachRate = { Value = 7 }, + OverallDifficulty = { Value = 6 }, + DrainRate = { Value = 5 }, + } + }, Ruleset = new OsuRuleset().RulesetInfo, BeatmapInfo = beatmapInfo, User = new APIUser @@ -217,7 +232,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 1, MaxCombo = 244, TotalScore = 1707827, - //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, Ruleset = new OsuRuleset().RulesetInfo, User = new APIUser @@ -237,7 +252,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 1, MaxCombo = 244, TotalScore = 1707827, - //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, Ruleset = new OsuRuleset().RulesetInfo, @@ -258,7 +273,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 1, MaxCombo = 244, TotalScore = 1707827, - //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, Ruleset = new OsuRuleset().RulesetInfo, @@ -279,7 +294,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 1, MaxCombo = 244, TotalScore = 1707827, - //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, Ruleset = new OsuRuleset().RulesetInfo, @@ -300,7 +315,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 0.9826, MaxCombo = 244, TotalScore = 1707827, - //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, Ruleset = new OsuRuleset().RulesetInfo, @@ -321,7 +336,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 0.9654, MaxCombo = 244, TotalScore = 1707827, - //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, Ruleset = new OsuRuleset().RulesetInfo, @@ -342,7 +357,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 0.6025, MaxCombo = 244, TotalScore = 1707827, - //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, Ruleset = new OsuRuleset().RulesetInfo, @@ -363,7 +378,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 0.5140, MaxCombo = 244, TotalScore = 1707827, - //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, Ruleset = new OsuRuleset().RulesetInfo, @@ -384,7 +399,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 0.4222, MaxCombo = 244, TotalScore = 1707827, - //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, Ruleset = new OsuRuleset().RulesetInfo, From 8eace12fe3b5b585657cfec9ad733c129dd4d73a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Feb 2022 16:35:22 +0900 Subject: [PATCH 849/996] Synchronise (roughly) backgrounds of all custom tooltips --- .../Drawables/DifficultyIconTooltip.cs | 1 + .../Leaderboards/LeaderboardScoreTooltip.cs | 29 ++++++++++++------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs index ec4bcbd65f..aba01a1294 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs @@ -30,6 +30,7 @@ namespace osu.Game.Beatmaps.Drawables { background = new Box { + Alpha = 0.9f, RelativeSizeAxes = Axes.Both }, new FillFlowContainer diff --git a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs index 85db59b3ea..7b88c0d534 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs @@ -21,24 +21,31 @@ namespace osu.Game.Online.Leaderboards { public class LeaderboardScoreTooltip : VisibilityContainer, ITooltip { - private readonly OsuSpriteText timestampLabel; - private readonly FillFlowContainer topScoreStatistics; - private readonly FillFlowContainer bottomScoreStatistics; - private readonly FillFlowContainer modStatistics; + private OsuSpriteText timestampLabel = null!; + private FillFlowContainer topScoreStatistics = null!; + private FillFlowContainer bottomScoreStatistics = null!; + private FillFlowContainer modStatistics = null!; public LeaderboardScoreTooltip() { AutoSizeAxes = Axes.Both; + AutoSizeDuration = 200; + AutoSizeEasing = Easing.OutQuint; + Masking = true; CornerRadius = 5; + } + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { InternalChildren = new Drawable[] { new Box { RelativeSizeAxes = Axes.Both, - Alpha = 0.7f, - Colour = Colour4.Black, + Alpha = 0.9f, + Colour = colours.Gray3, }, new GridContainer { @@ -118,10 +125,10 @@ namespace osu.Game.Online.Leaderboards foreach (var result in score.GetStatisticsForDisplay()) { - (result.Result > HitResult.Perfect - ? bottomScoreStatistics - : topScoreStatistics - ).Add(new HitResultCell(result)); + if (result.Result > HitResult.Perfect) + bottomScoreStatistics.Add(new HitResultCell(result)); + else + topScoreStatistics.Add(new HitResultCell(result)); } } @@ -171,7 +178,7 @@ namespace osu.Game.Online.Leaderboards }, new OsuSpriteText { - Padding = new MarginPadding { Horizontal = 2f }, + Padding = new MarginPadding(2), Anchor = Anchor.Centre, Origin = Anchor.Centre, Font = OsuFont.Torus.With(size: 12, weight: FontWeight.SemiBold), From f87920cd83d060542ac77784fdefef09f9521348 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Feb 2022 16:43:26 +0900 Subject: [PATCH 850/996] Remove unnecessary `GridContainer` and list mods verticall to give more space --- .../Leaderboards/LeaderboardScoreTooltip.cs | 61 ++++++++----------- 1 file changed, 24 insertions(+), 37 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs index 7b88c0d534..8c349d56e0 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs @@ -47,55 +47,42 @@ namespace osu.Game.Online.Leaderboards Alpha = 0.9f, Colour = colours.Gray3, }, - new GridContainer + new FillFlowContainer { Margin = new MarginPadding(5f), + Spacing = new Vector2(10), AutoSizeAxes = Axes.Both, - RowDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.AutoSize), - }, - ColumnDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize), - }, - Content = new[] + Direction = FillDirection.Vertical, + Children = new Drawable[] { // Info row - new Drawable[] + timestampLabel = new OsuSpriteText { - timestampLabel = new OsuSpriteText - { - Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), - } + Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), }, // Mods row - new Drawable[] + modStatistics = new FillFlowContainer { - modStatistics = new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - } + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, }, - // Actual stats rows - new Drawable[] + new FillFlowContainer { - topScoreStatistics = new FillFlowContainer + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - } - }, - new Drawable[] - { - bottomScoreStatistics = new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, + // Actual stats rows + topScoreStatistics = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + }, + bottomScoreStatistics = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + }, } }, } From 0f83f77d2b3fc8de0d615361e53130f5958c94c6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Feb 2022 16:52:53 +0900 Subject: [PATCH 851/996] Add xmldoc for new `ResetFromReplayFrame` method --- osu.Game/Rulesets/Scoring/JudgementProcessor.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/Rulesets/Scoring/JudgementProcessor.cs b/osu.Game/Rulesets/Scoring/JudgementProcessor.cs index f38155240e..a643c31daa 100644 --- a/osu.Game/Rulesets/Scoring/JudgementProcessor.cs +++ b/osu.Game/Rulesets/Scoring/JudgementProcessor.cs @@ -108,6 +108,14 @@ namespace osu.Game.Rulesets.Scoring JudgedHits = 0; } + /// + /// Reset all statistics based on header information contained within a replay frame. + /// + /// + /// If the provided replay frame does not have any header information, this will be a noop. + /// + /// The ruleset to be used for retrieving statistics. + /// The replay frame to read header statistics from. public virtual void ResetFromReplayFrame(Ruleset ruleset, ReplayFrame frame) { if (frame.Header == null) From 15479ae046ebf96b22367565333622a573dd59ae Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Feb 2022 16:55:28 +0900 Subject: [PATCH 852/996] Add test coverage of no header doing nothing --- osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs index bcaa70d062..70ba868de6 100644 --- a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs +++ b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs @@ -60,6 +60,12 @@ namespace osu.Game.Tests.Gameplay Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(1_000_000)); Assert.That(scoreProcessor.JudgedHits, Is.EqualTo(1)); + // No header shouldn't cause any change + scoreProcessor.ResetFromReplayFrame(new OsuRuleset(), new OsuReplayFrame()); + + Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(1_000_000)); + Assert.That(scoreProcessor.JudgedHits, Is.EqualTo(1)); + // Reset with a miss instead. scoreProcessor.ResetFromReplayFrame(new OsuRuleset(), new OsuReplayFrame { From bfe6218ed51c6451f85046998e225de2bdcbe7e6 Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Tue, 1 Feb 2022 12:43:58 +0100 Subject: [PATCH 853/996] Change default orientation to SensorLandscape --- osu.Android/GameplayScreenRotationLocker.cs | 2 +- osu.Android/OsuGameActivity.cs | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Android/GameplayScreenRotationLocker.cs b/osu.Android/GameplayScreenRotationLocker.cs index 25bd659a5d..ab91b4e3b3 100644 --- a/osu.Android/GameplayScreenRotationLocker.cs +++ b/osu.Android/GameplayScreenRotationLocker.cs @@ -27,7 +27,7 @@ namespace osu.Android { gameActivity.RunOnUiThread(() => { - gameActivity.RequestedOrientation = userPlaying.NewValue ? ScreenOrientation.Locked : ScreenOrientation.FullUser; + gameActivity.RequestedOrientation = userPlaying.NewValue ? ScreenOrientation.Locked : OsuGameActivity.DEFAULT_ORIENTATION; }); } } diff --git a/osu.Android/OsuGameActivity.cs b/osu.Android/OsuGameActivity.cs index c9fb539d8a..e6679b61a6 100644 --- a/osu.Android/OsuGameActivity.cs +++ b/osu.Android/OsuGameActivity.cs @@ -17,7 +17,7 @@ using osu.Game.Database; namespace osu.Android { - [Activity(ConfigurationChanges = DEFAULT_CONFIG_CHANGES, Exported = true, LaunchMode = DEFAULT_LAUNCH_MODE, MainLauncher = true, ScreenOrientation = ScreenOrientation.FullUser)] + [Activity(ConfigurationChanges = DEFAULT_CONFIG_CHANGES, Exported = true, LaunchMode = DEFAULT_LAUNCH_MODE, MainLauncher = true, ScreenOrientation = DEFAULT_ORIENTATION)] [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osz", DataHost = "*", DataMimeType = "*/*")] [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osk", DataHost = "*", DataMimeType = "*/*")] [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osr", DataHost = "*", DataMimeType = "*/*")] @@ -39,6 +39,8 @@ namespace osu.Android [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryBrowsable, Intent.CategoryDefault }, DataSchemes = new[] { "osu", "osump" })] public class OsuGameActivity : AndroidGameActivity { + public const ScreenOrientation DEFAULT_ORIENTATION = ScreenOrientation.SensorLandscape; + private static readonly string[] osu_url_schemes = { "osu", "osump" }; private OsuGameAndroid game; From 41007169f722febe2217ff805cf95cdb6d81c3dd Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 1 Feb 2022 15:51:41 +0900 Subject: [PATCH 854/996] Give SpectatorState a user state --- .../Visual/Gameplay/TestSceneSpectator.cs | 2 +- .../Online/Spectator/SpectatingUserState.cs | 33 +++++++++++++++++++ osu.Game/Online/Spectator/SpectatorClient.cs | 10 +++++- osu.Game/Online/Spectator/SpectatorState.cs | 7 ++-- osu.Game/Rulesets/UI/ReplayRecorder.cs | 4 ++- osu.Game/Screens/Play/Player.cs | 2 +- 6 files changed, 52 insertions(+), 6 deletions(-) create mode 100644 osu.Game/Online/Spectator/SpectatingUserState.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index 917b3c89a8..86d6de6975 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -211,7 +211,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("send frames and finish play", () => { spectatorClient.HandleFrame(new OsuReplayFrame(1000, Vector2.Zero)); - spectatorClient.EndPlaying(); + spectatorClient.EndPlaying(new GameplayState(new TestBeatmap(new OsuRuleset().RulesetInfo), new OsuRuleset()) { HasPassed = true }); }); // We can't access API because we're an "online" test. diff --git a/osu.Game/Online/Spectator/SpectatingUserState.cs b/osu.Game/Online/Spectator/SpectatingUserState.cs new file mode 100644 index 0000000000..c7ba4ba248 --- /dev/null +++ b/osu.Game/Online/Spectator/SpectatingUserState.cs @@ -0,0 +1,33 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Online.Spectator +{ + public enum SpectatingUserState + { + /// + /// The spectated user has not yet played. + /// + Idle, + + /// + /// The spectated user is currently playing. + /// + Playing, + + /// + /// The spectated user has successfully completed gameplay. + /// + Completed, + + /// + /// The spectator user has failed during gameplay. + /// + Failed, + + /// + /// The spectated user has quit during gameplay. + /// + Quit + } +} diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index c3e70edcd3..f4161e1db9 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -138,6 +138,7 @@ namespace osu.Game.Online.Spectator currentState.BeatmapID = score.ScoreInfo.BeatmapInfo.OnlineID; currentState.RulesetID = score.ScoreInfo.RulesetID; currentState.Mods = score.ScoreInfo.Mods.Select(m => new APIMod(m)).ToArray(); + currentState.State = SpectatingUserState.Playing; currentBeatmap = state.Beatmap; currentScore = score; @@ -148,7 +149,7 @@ namespace osu.Game.Online.Spectator public void SendFrames(FrameDataBundle data) => lastSend = SendFramesInternal(data); - public void EndPlaying() + public void EndPlaying(GameplayState state) { // This method is most commonly called via Dispose(), which is can be asynchronous (via the AsyncDisposalQueue). // We probably need to find a better way to handle this... @@ -163,6 +164,13 @@ namespace osu.Game.Online.Spectator IsPlaying = false; currentBeatmap = null; + if (state.HasPassed) + currentState.State = SpectatingUserState.Completed; + else if (state.HasFailed) + currentState.State = SpectatingUserState.Failed; + else + currentState.State = SpectatingUserState.Quit; + EndPlayingInternal(currentState); }); } diff --git a/osu.Game/Online/Spectator/SpectatorState.cs b/osu.Game/Online/Spectator/SpectatorState.cs index ebb91e4dd2..fc62f16bba 100644 --- a/osu.Game/Online/Spectator/SpectatorState.cs +++ b/osu.Game/Online/Spectator/SpectatorState.cs @@ -24,14 +24,17 @@ namespace osu.Game.Online.Spectator [Key(2)] public IEnumerable Mods { get; set; } = Enumerable.Empty(); + [Key(3)] + public SpectatingUserState State { get; set; } + public bool Equals(SpectatorState other) { if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; - return BeatmapID == other.BeatmapID && Mods.SequenceEqual(other.Mods) && RulesetID == other.RulesetID; + return BeatmapID == other.BeatmapID && Mods.SequenceEqual(other.Mods) && RulesetID == other.RulesetID && State == other.State; } - public override string ToString() => $"Beatmap:{BeatmapID} Mods:{string.Join(',', Mods)} Ruleset:{RulesetID}"; + public override string ToString() => $"Beatmap:{BeatmapID} Mods:{string.Join(',', Mods)} Ruleset:{RulesetID} State:{State}"; } } diff --git a/osu.Game/Rulesets/UI/ReplayRecorder.cs b/osu.Game/Rulesets/UI/ReplayRecorder.cs index 976f95cef8..277040b2a6 100644 --- a/osu.Game/Rulesets/UI/ReplayRecorder.cs +++ b/osu.Game/Rulesets/UI/ReplayRecorder.cs @@ -55,7 +55,9 @@ namespace osu.Game.Rulesets.UI protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - spectatorClient?.EndPlaying(); + + if (spectatorClient != null && gameplayState != null) + spectatorClient.EndPlaying(gameplayState); } protected override void Update() diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index f6a2310826..1aa6cded48 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -1000,7 +1000,7 @@ namespace osu.Game.Screens.Play // EndPlaying() is typically called from ReplayRecorder.Dispose(). Disposal is currently asynchronous. // To resolve test failures, forcefully end playing synchronously when this screen exits. // Todo: Replace this with a more permanent solution once osu-framework has a synchronous cleanup method. - spectatorClient.EndPlaying(); + spectatorClient.EndPlaying(GameplayState); // GameplayClockContainer performs seeks / start / stop operations on the beatmap's track. // as we are no longer the current screen, we cannot guarantee the track is still usable. From f4210f7a302a3bad2130181800106cdcefeb2020 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 26 Jan 2022 22:21:07 +0900 Subject: [PATCH 855/996] Rework spectator components to use new user state --- osu.Game/Online/Spectator/SpectatorClient.cs | 8 +--- .../Dashboard/CurrentlyPlayingDisplay.cs | 42 ++++++++++++------- osu.Game/Screens/Spectate/SpectatorScreen.cs | 33 ++++++++------- 3 files changed, 45 insertions(+), 38 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index f4161e1db9..fb7526347b 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -93,12 +93,8 @@ namespace osu.Game.Online.Spectator { Schedule(() => { - // UserBeganPlaying() is called by the server regardless of whether the local user is watching the remote user, and is called a further time when the remote user is watched. - // This may be a temporary thing (see: https://github.com/ppy/osu-server-spectator/blob/2273778e02cfdb4a9c6a934f2a46a8459cb5d29c/osu.Server.Spectator/Hubs/SpectatorHub.cs#L28-L29). - // We don't want the user states to update unless the player is being watched, otherwise calling BindUserBeganPlaying() can lead to double invocations. if (watchingUsers.Contains(userId)) playingUserStates[userId] = state; - OnUserBeganPlaying?.Invoke(userId, state); }); @@ -109,8 +105,8 @@ namespace osu.Game.Online.Spectator { Schedule(() => { - playingUserStates.Remove(userId); - + if (watchingUsers.Contains(userId)) + playingUserStates[userId] = state; OnUserFinishedPlaying?.Invoke(userId, state); }); diff --git a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs index afbfb9f7ef..383f17d8d2 100644 --- a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs +++ b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs @@ -22,7 +22,7 @@ namespace osu.Game.Overlays.Dashboard { internal class CurrentlyPlayingDisplay : CompositeDrawable { - private readonly IBindableDictionary playingUserStates = new BindableDictionary(); + private readonly IBindableDictionary userStates = new BindableDictionary(); private FillFlowContainer userFlow; @@ -51,33 +51,32 @@ namespace osu.Game.Overlays.Dashboard { base.LoadComplete(); - playingUserStates.BindTo(spectatorClient.PlayingUserStates); - playingUserStates.BindCollectionChanged(onUsersChanged, true); + userStates.BindTo(spectatorClient.PlayingUserStates); + userStates.BindCollectionChanged(onUserStatesChanged, true); } - private void onUsersChanged(object sender, NotifyDictionaryChangedEventArgs e) => Schedule(() => + private void onUserStatesChanged(object sender, NotifyDictionaryChangedEventArgs e) => Schedule(() => { switch (e.Action) { case NotifyDictionaryChangedAction.Add: + case NotifyDictionaryChangedAction.Replace: Debug.Assert(e.NewItems != null); - foreach ((int userId, _) in e.NewItems) + foreach ((int userId, SpectatorState state) in e.NewItems) { + if (state.State != SpectatingUserState.Playing) + { + removePlayingUser(userId); + continue; + } + users.GetUserAsync(userId).ContinueWith(task => { var user = task.GetResultSafely(); - if (user == null) return; - - Schedule(() => - { - // user may no longer be playing. - if (!playingUserStates.ContainsKey(user.Id)) - return; - - userFlow.Add(createUserPanel(user)); - }); + if (user != null) + Schedule(() => addPlayingUser(user)); }); } @@ -87,9 +86,20 @@ namespace osu.Game.Overlays.Dashboard Debug.Assert(e.OldItems != null); foreach ((int userId, _) in e.OldItems) - userFlow.FirstOrDefault(card => card.User.Id == userId)?.Expire(); + removePlayingUser(userId); break; } + + void addPlayingUser(APIUser user) + { + // user may no longer be playing. + if (!userStates.TryGetValue(user.Id, out var state2) || state2.State != SpectatingUserState.Playing) + return; + + userFlow.Add(createUserPanel(user)); + } + + void removePlayingUser(int userId) => userFlow.FirstOrDefault(card => card.User.Id == userId)?.Expire(); }); private PlayingUserPanel createUserPanel(APIUser user) => diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs index 3cf9f79611..783ba494eb 100644 --- a/osu.Game/Screens/Spectate/SpectatorScreen.cs +++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs @@ -42,7 +42,7 @@ namespace osu.Game.Screens.Spectate [Resolved] private UserLookupCache userLookupCache { get; set; } - private readonly IBindableDictionary playingUserStates = new BindableDictionary(); + private readonly IBindableDictionary userStates = new BindableDictionary(); private readonly Dictionary userMap = new Dictionary(); private readonly Dictionary gameplayStates = new Dictionary(); @@ -77,8 +77,8 @@ namespace osu.Game.Screens.Spectate userMap[u.Id] = u; } - playingUserStates.BindTo(spectatorClient.PlayingUserStates); - playingUserStates.BindCollectionChanged(onPlayingUserStatesChanged, true); + userStates.BindTo(spectatorClient.PlayingUserStates); + userStates.BindCollectionChanged(onUserStatesChanged, true); realmSubscription = realm.RegisterForNotifications( realm => realm.All().Where(s => !s.DeletePending), beatmapsChanged); @@ -99,7 +99,7 @@ namespace osu.Game.Screens.Spectate { foreach ((int userId, _) in userMap) { - if (!playingUserStates.TryGetValue(userId, out var userState)) + if (!userStates.TryGetValue(userId, out var userState)) continue; if (beatmapSet.Beatmaps.Any(b => b.OnlineID == userState.BeatmapID)) @@ -107,40 +107,41 @@ namespace osu.Game.Screens.Spectate } } - private void onPlayingUserStatesChanged(object sender, NotifyDictionaryChangedEventArgs e) + private void onUserStatesChanged(object sender, NotifyDictionaryChangedEventArgs e) { switch (e.Action) { case NotifyDictionaryChangedAction.Add: foreach ((int userId, var state) in e.NewItems.AsNonNull()) - onUserStateAdded(userId, state); + onUserStateChanged(userId, state); break; case NotifyDictionaryChangedAction.Remove: - foreach ((int userId, var _) in e.OldItems.AsNonNull()) + foreach ((int userId, _) in e.OldItems.AsNonNull()) onUserStateRemoved(userId); break; case NotifyDictionaryChangedAction.Replace: - foreach ((int userId, var _) in e.OldItems.AsNonNull()) - onUserStateRemoved(userId); - foreach ((int userId, var state) in e.NewItems.AsNonNull()) - onUserStateAdded(userId, state); + onUserStateChanged(userId, state); break; } } - private void onUserStateAdded(int userId, SpectatorState state) + private void onUserStateChanged(int userId, SpectatorState newState) { - if (state.RulesetID == null || state.BeatmapID == null) + if (newState.RulesetID == null || newState.BeatmapID == null) return; if (!userMap.ContainsKey(userId)) return; - Schedule(() => OnUserStateChanged(userId, state)); - updateGameplayState(userId); + // Do nothing for failed/completed states. + if (newState.State == SpectatingUserState.Playing) + { + Schedule(() => OnUserStateChanged(userId, newState)); + updateGameplayState(userId); + } } private void onUserStateRemoved(int userId) @@ -162,7 +163,7 @@ namespace osu.Game.Screens.Spectate Debug.Assert(userMap.ContainsKey(userId)); var user = userMap[userId]; - var spectatorState = playingUserStates[userId]; + var spectatorState = userStates[userId]; var resolvedRuleset = rulesets.AvailableRulesets.FirstOrDefault(r => r.OnlineID == spectatorState.RulesetID)?.CreateInstance(); if (resolvedRuleset == null) From b75c08c9ab07232d44b1bde38f68f2054547b91c Mon Sep 17 00:00:00 2001 From: Spooghetti420 <83249561+Spooghetti420@users.noreply.github.com> Date: Tue, 1 Feb 2022 13:36:36 +0000 Subject: [PATCH 856/996] Improve beat length logic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs index 1abca970d5..6e942f672b 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs @@ -65,8 +65,8 @@ namespace osu.Game.Rulesets.Mania.Mods public static double GetNoteDurationInBeatLength(HoldNote holdNote, ManiaBeatmap beatmap) { - double bpmAtNoteTime = beatmap.ControlPointInfo.TimingPointAt(holdNote.StartTime).BPM; - return (60 * holdNote.Duration) / (1000 * bpmAtNoteTime); + double beatLength = beatmap.ControlPointInfo.TimingPointAt(holdNote.StartTime).BeatLength; + return holdNote.Duration / beatLength; } } } From 39524f3dd296d44cfb8187507a416e02a47b040c Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Tue, 1 Feb 2022 22:26:30 +0800 Subject: [PATCH 857/996] Split total pp into 2 lines --- .../Statistics/PerformanceStatisticTooltip.cs | 85 ++++++++++++++++--- 1 file changed, 72 insertions(+), 13 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs index 44e5c366bb..739fac9d63 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs @@ -72,16 +72,73 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics UpdateDisplay(performance); } - private Drawable createAttributeItem(PerformanceDisplayAttribute attribute, PerformanceDisplayAttribute perfectAttribute) + private Drawable createItemForTotal(PerformanceDisplayAttribute attribute, PerformanceDisplayAttribute perfectAttribute) { - bool isTotal = attribute.PropertyName == nameof(PerformanceAttributes.Total); - float fraction = (float)(attribute.Value / perfectAttribute.Value); - if (float.IsNaN(fraction)) - fraction = 0; - string text = fraction.ToLocalisableString("0%").ToString(); + return + new GridContainer + { + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Bottom = 10 }, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.Absolute, 250), + new Dimension(GridSizeMode.AutoSize) + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize) + }, + Content = new[] + { + new Drawable[] + { + new OsuSpriteText + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Font = OsuFont.GetFont(weight: FontWeight.Regular), + Text = attribute.DisplayName, + Colour = titleColor + }, + new OsuSpriteText + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Font = OsuFont.GetFont(weight: FontWeight.SemiBold), + Text = Math.Round(attribute.Value, MidpointRounding.AwayFromZero).ToLocalisableString(), + Colour = titleColor + } + }, + new Drawable[] + { + new OsuSpriteText + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Font = OsuFont.GetFont(weight: FontWeight.Regular), + Text = "Maximum", + Colour = OsuColour.Gray(0.7f) + }, + new OsuSpriteText + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Font = OsuFont.GetFont(weight: FontWeight.Regular), + Text = Math.Round(perfectAttribute.Value, MidpointRounding.AwayFromZero).ToLocalisableString(), + Colour = OsuColour.Gray(0.7f) + } + } + } + }; + } - if (isTotal) - text = (int)Math.Round(attribute.Value, MidpointRounding.AwayFromZero) + "/" + (int)Math.Round(perfectAttribute.Value, MidpointRounding.AwayFromZero); + private Drawable createItemForAttribute(PerformanceDisplayAttribute attribute, PerformanceDisplayAttribute perfectAttribute) + { + float percentage = (float)(attribute.Value / perfectAttribute.Value); + if (float.IsNaN(percentage)) + percentage = 0; + string text = percentage.ToLocalisableString("0%").ToString(); return new GridContainer { @@ -106,7 +163,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics Anchor = Anchor.CentreLeft, Font = OsuFont.GetFont(weight: FontWeight.Regular), Text = attribute.DisplayName, - Colour = isTotal ? titleColor : textColour + Colour = textColour }, new Bar { @@ -115,8 +172,8 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics Width = 130, Height = 5, BackgroundColour = Color4.White.Opacity(0.5f), - Colour = isTotal ? titleColor : textColour, - Length = fraction, + Colour = textColour, + Length = percentage, Margin = new MarginPadding { Left = 5, Right = 5 } }, new OsuSpriteText @@ -125,7 +182,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics Anchor = Anchor.CentreLeft, Font = OsuFont.GetFont(weight: FontWeight.SemiBold), Text = text, - Colour = isTotal ? titleColor : textColour + Colour = textColour } } } @@ -142,7 +199,9 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics foreach (PerformanceDisplayAttribute attr in displayAttributes) { - Content.Add(createAttributeItem(attr, perfectDisplayAttributes.First(a => a.PropertyName == attr.PropertyName))); + Content.Add(attr.PropertyName == nameof(PerformanceAttributes.Total) + ? createItemForTotal(attr, perfectDisplayAttributes.First(a => a.PropertyName == attr.PropertyName)) + : createItemForAttribute(attr, perfectDisplayAttributes.First(a => a.PropertyName == attr.PropertyName))); } } From b06128ffa5f4845bd0c9ae6dabe11120336b5cf4 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Tue, 1 Feb 2022 22:26:55 +0800 Subject: [PATCH 858/996] Rename "Final PP" to "Achieved PP" --- osu.Game/Rulesets/Difficulty/PerformanceAttributes.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Difficulty/PerformanceAttributes.cs b/osu.Game/Rulesets/Difficulty/PerformanceAttributes.cs index f210c669a6..e8c4c71913 100644 --- a/osu.Game/Rulesets/Difficulty/PerformanceAttributes.cs +++ b/osu.Game/Rulesets/Difficulty/PerformanceAttributes.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Difficulty /// public virtual IEnumerable GetAttributesForDisplay() { - yield return new PerformanceDisplayAttribute(nameof(Total), "Final PP", Total); + yield return new PerformanceDisplayAttribute(nameof(Total), "Achieved PP", Total); } } } From eee020f8e4a17db7a1af6be611cbc0b3c5554852 Mon Sep 17 00:00:00 2001 From: dekrain Date: Tue, 1 Feb 2022 20:26:52 +0100 Subject: [PATCH 859/996] Cleanup tooltip layout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- .../Leaderboards/LeaderboardScoreTooltip.cs | 42 ++++++------------- 1 file changed, 13 insertions(+), 29 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs index 8c349d56e0..a0eea94501 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs @@ -10,7 +10,6 @@ using osuTK; using osu.Game.Graphics.Sprites; using osu.Game.Graphics; using osu.Framework.Allocation; -using osu.Framework.Extensions.Color4Extensions; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; @@ -49,7 +48,7 @@ namespace osu.Game.Online.Leaderboards }, new FillFlowContainer { - Margin = new MarginPadding(5f), + Margin = new MarginPadding(5), Spacing = new Vector2(10), AutoSizeAxes = Axes.Both, Direction = FillDirection.Vertical, @@ -63,8 +62,10 @@ namespace osu.Game.Online.Leaderboards // Mods row modStatistics = new FillFlowContainer { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Full, + Spacing = new Vector2(5, 0), }, new FillFlowContainer { @@ -77,11 +78,13 @@ namespace osu.Game.Online.Leaderboards { AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, + Spacing = new Vector2(10, 0), }, bottomScoreStatistics = new FillFlowContainer { AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, + Spacing = new Vector2(10, 0), }, } }, @@ -133,7 +136,6 @@ namespace osu.Game.Online.Leaderboards public HitResultCell(HitResultDisplayStatistic stat) { AutoSizeAxes = Axes.Both; - Padding = new MarginPadding { Horizontal = 5f }; displayName = stat.DisplayName; result = stat.Result; @@ -148,35 +150,17 @@ namespace osu.Game.Online.Leaderboards Height = 12, AutoSizeAxes = Axes.X, Direction = FillDirection.Horizontal, - Spacing = new Vector2(2f, 0f), + Spacing = new Vector2(5f, 0f), Children = new Drawable[] { - new CircularContainer + new OsuSpriteText { - AutoSizeAxes = Axes.X, - RelativeSizeAxes = Axes.Y, - Masking = true, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4Extensions.FromHex("#222") - }, - new OsuSpriteText - { - Padding = new MarginPadding(2), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Font = OsuFont.Torus.With(size: 12, weight: FontWeight.SemiBold), - Text = displayName.ToUpperInvariant(), - Colour = colours.ForHitResult(result), - } - } + Font = OsuFont.Torus.With(size: 12, weight: FontWeight.SemiBold), + Text = displayName.ToUpperInvariant(), + Colour = colours.ForHitResult(result), }, new OsuSpriteText { - RelativeSizeAxes = Axes.Y, Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), Text = count.ToString(), }, @@ -192,7 +176,6 @@ namespace osu.Game.Online.Leaderboards public ModCell(Mod mod) { AutoSizeAxes = Axes.Both; - Padding = new MarginPadding { Horizontal = 5f }; this.mod = mod; } @@ -228,6 +211,7 @@ namespace osu.Game.Online.Leaderboards Text = mod.SettingDescription, Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, + Margin = new MarginPadding { Top = 1 }, }); } } From 3d7af805a3cd0905fef3bfc15544330d3bdcd912 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 1 Feb 2022 21:16:28 +0100 Subject: [PATCH 860/996] Fix `BeatmapMetadata` not using its user param correctly --- osu.Game/Beatmaps/BeatmapMetadata.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs index 1514d3af7a..f6666a6ea9 100644 --- a/osu.Game/Beatmaps/BeatmapMetadata.cs +++ b/osu.Game/Beatmaps/BeatmapMetadata.cs @@ -46,7 +46,7 @@ namespace osu.Game.Beatmaps public BeatmapMetadata(RealmUser? user = null) { - Author = new RealmUser(); + Author = user ?? new RealmUser(); } [UsedImplicitly] // Realm From a378e78ced3e0b5dfe6dafa3803060b1c9d118ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 1 Feb 2022 21:17:27 +0100 Subject: [PATCH 861/996] Fix `RealmLive` unnecessarily passing ID around Appears to have never been needed. When the `retrieveFromID` method was created in 81b5717ae793e334fdcf8efd72d20debfb49e9ca, it didn't use the `id` parameter for anything either. --- osu.Game/Database/RealmLive.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Database/RealmLive.cs b/osu.Game/Database/RealmLive.cs index 186e801425..ecfececaa4 100644 --- a/osu.Game/Database/RealmLive.cs +++ b/osu.Game/Database/RealmLive.cs @@ -63,7 +63,7 @@ namespace osu.Game.Database return; } - perform(retrieveFromID(r, ID)); + perform(retrieveFromID(r)); RealmLiveStatistics.USAGE_ASYNC.Value++; }); } @@ -85,7 +85,7 @@ namespace osu.Game.Database return realm.Run(r => { - var returnData = perform(retrieveFromID(r, ID)); + var returnData = perform(retrieveFromID(r)); RealmLiveStatistics.USAGE_ASYNC.Value++; if (returnData is RealmObjectBase realmObject && realmObject.IsManaged) @@ -139,11 +139,11 @@ namespace osu.Game.Database } dataIsFromUpdateThread = true; - data = retrieveFromID(realm.Realm, ID); + data = retrieveFromID(realm.Realm); RealmLiveStatistics.USAGE_UPDATE_REFETCH.Value++; } - private T retrieveFromID(Realm realm, Guid id) + private T retrieveFromID(Realm realm) { var found = realm.Find(ID); From 16e0cc6a2b393312f90da6320d6d9e4d00685e54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 1 Feb 2022 21:20:59 +0100 Subject: [PATCH 862/996] Remove `IIpcHost` param from `ScoreManager` No longer used since 3e3b9bc963583fa5f6ad3b822ad3e6e3818aa06c. --- osu.Game/OsuGameBase.cs | 2 +- osu.Game/Scoring/ScoreManager.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 8363c41437..1713e73905 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -247,7 +247,7 @@ namespace osu.Game var defaultBeatmap = new DummyWorkingBeatmap(Audio, Textures); // 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, Host, () => difficultyCache, LocalConfig)); + dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, realm, Scheduler, () => difficultyCache, LocalConfig)); dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, realm, RulesetStore, API, Audio, Resources, Host, defaultBeatmap, performOnlineLookups: true)); dependencies.Cache(BeatmapDownloader = new BeatmapModelDownloader(BeatmapManager, API)); diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 6f9cce2d3c..532c6b42a3 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -32,7 +32,7 @@ namespace osu.Game.Scoring private readonly ScoreModelManager scoreModelManager; public ScoreManager(RulesetStore rulesets, Func beatmaps, Storage storage, RealmAccess realm, Scheduler scheduler, - IIpcHost importHost = null, Func difficulties = null, OsuConfigManager configManager = null) + Func difficulties = null, OsuConfigManager configManager = null) { this.realm = realm; this.scheduler = scheduler; From c6a65ccfeddc3dd190d38b0629fbb9efbd64fab9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 1 Feb 2022 21:22:22 +0100 Subject: [PATCH 863/996] Remove unused parameter from `createContent()` No longer used since 513e470b525c4948d793555beb0df0928b3b9019. --- osu.Game/Screens/Edit/Timing/ControlPointTable.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index 1e6899e05f..0c12eff503 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -51,7 +51,7 @@ namespace osu.Game.Screens.Edit.Timing } Columns = createHeaders(); - Content = value.Select((g, i) => createContent(i, g)).ToArray().ToRectangular(); + Content = value.Select(createContent).ToArray().ToRectangular(); } } @@ -76,7 +76,7 @@ namespace osu.Game.Screens.Edit.Timing return columns.ToArray(); } - private Drawable[] createContent(int index, ControlPointGroup group) + private Drawable[] createContent(ControlPointGroup group) { return new Drawable[] { From 1fa2bf5d69f0c92755f9191acb4ae686b160fa9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 1 Feb 2022 21:27:14 +0100 Subject: [PATCH 864/996] Remove unused parameter from `createColourBars()` No longer used since b61aa660c63f0e126f32643ef17e7bb458d2a1ae. --- .../Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs index a8141c57da..7903e54960 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs @@ -10,7 +10,6 @@ using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; -using osu.Game.Graphics; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; using osuTK; @@ -49,7 +48,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load() { InternalChild = new FillFlowContainer { @@ -127,7 +126,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters } }; - createColourBars(colours); + createColourBars(); } protected override void LoadComplete() @@ -150,7 +149,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters iconLate.Rotation = -Rotation; } - private void createColourBars(OsuColour colours) + private void createColourBars() { var windows = HitWindows.GetAllAvailableWindows().ToArray(); From a94702b3aeaac4b1dfe1e0adeb71140650bcaabd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 1 Feb 2022 21:30:28 +0100 Subject: [PATCH 865/996] Remove unused parameters in `LegacyComboCounter` No longer used since 9bb8a43bcee61d11f26598fff2228af1f07a4d17. --- osu.Game/Screens/Play/HUD/LegacyComboCounter.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs index 567e4386c6..9510453ba5 100644 --- a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs @@ -109,7 +109,7 @@ namespace osu.Game.Screens.Play.HUD return; if (isRolling) - onDisplayedCountRolling(displayedCount, value); + onDisplayedCountRolling(value); else if (displayedCount + 1 == value) onDisplayedCountIncrement(value); else @@ -151,7 +151,7 @@ namespace osu.Game.Screens.Play.HUD if (prev + 1 == Current.Value) onCountIncrement(prev, Current.Value); else - onCountChange(prev, Current.Value); + onCountChange(Current.Value); } else { @@ -226,7 +226,7 @@ namespace osu.Game.Screens.Play.HUD transformRoll(currentValue, newValue); } - private void onCountChange(int currentValue, int newValue) + private void onCountChange(int newValue) { scheduledPopOutCurrentId++; @@ -236,7 +236,7 @@ namespace osu.Game.Screens.Play.HUD DisplayedCount = newValue; } - private void onDisplayedCountRolling(int currentValue, int newValue) + private void onDisplayedCountRolling(int newValue) { if (newValue == 0) displayedCountSpriteText.FadeOut(fade_out_duration); From 07d09b3520210f1233cb024e8419548f342c4001 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 1 Feb 2022 21:32:58 +0100 Subject: [PATCH 866/996] Remove unused parameter from `createGameplayComponents()` No longer used since 136843c8e450507ad0527622af851d648afb1545. --- osu.Game/Screens/Play/Player.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 1556e752ab..240620b686 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -251,7 +251,7 @@ namespace osu.Game.Screens.Play { // underlay and gameplay should have access to the skinning sources. createUnderlayComponents(), - createGameplayComponents(Beatmap.Value, playableBeatmap) + createGameplayComponents(Beatmap.Value) } }, FailOverlay = new FailOverlay @@ -364,7 +364,7 @@ namespace osu.Game.Screens.Play private Drawable createUnderlayComponents() => DimmableStoryboard = new DimmableStoryboard(Beatmap.Value.Storyboard) { RelativeSizeAxes = Axes.Both }; - private Drawable createGameplayComponents(IWorkingBeatmap working, IBeatmap playableBeatmap) => new ScalingContainer(ScalingMode.Gameplay) + private Drawable createGameplayComponents(IWorkingBeatmap working) => new ScalingContainer(ScalingMode.Gameplay) { Children = new Drawable[] { From 994fb966b6ba27baf7c178a7483ccf353db46fc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 1 Feb 2022 21:37:19 +0100 Subject: [PATCH 867/996] Remove `Host` ctor param from `SkinModelManager` No longer used since 29d074bdb8629ff480207498c0eeec61b010a530. --- osu.Game/Skinning/SkinManager.cs | 2 +- osu.Game/Skinning/SkinModelManager.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 06bd0abc9f..bad559d9fe 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -77,7 +77,7 @@ namespace osu.Game.Skinning userFiles = new StorageBackedResourceStore(storage.GetStorageForDirectory("files")); - skinModelManager = new SkinModelManager(storage, realm, host, this); + skinModelManager = new SkinModelManager(storage, realm, this); var defaultSkins = new[] { diff --git a/osu.Game/Skinning/SkinModelManager.cs b/osu.Game/Skinning/SkinModelManager.cs index 0af31100a9..33e49ce486 100644 --- a/osu.Game/Skinning/SkinModelManager.cs +++ b/osu.Game/Skinning/SkinModelManager.cs @@ -27,7 +27,7 @@ namespace osu.Game.Skinning private readonly IStorageResourceProvider skinResources; - public SkinModelManager(Storage storage, RealmAccess realm, GameHost host, IStorageResourceProvider skinResources) + public SkinModelManager(Storage storage, RealmAccess realm, IStorageResourceProvider skinResources) : base(storage, realm) { this.skinResources = skinResources; From 75101b11052c2f02471a8fed6129f9d0e4d5eb76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 1 Feb 2022 21:39:52 +0100 Subject: [PATCH 868/996] Remove unused ruleset ctor params from test beatmap model managers No longer used since 00e3af3366e80214c7f0c956d144000a78cd0b56. --- .../Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs | 4 ++-- osu.Game/Tests/Visual/EditorTestScene.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index f9161816e7..79cb424803 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -173,14 +173,14 @@ namespace osu.Game.Tests.Online protected override BeatmapModelManager CreateBeatmapModelManager(Storage storage, RealmAccess realm, RulesetStore rulesets, BeatmapOnlineLookupQueue onlineLookupQueue) { - return new TestBeatmapModelManager(this, storage, realm, rulesets, onlineLookupQueue); + return new TestBeatmapModelManager(this, storage, realm, onlineLookupQueue); } internal class TestBeatmapModelManager : BeatmapModelManager { private readonly TestBeatmapManager testBeatmapManager; - public TestBeatmapModelManager(TestBeatmapManager testBeatmapManager, Storage storage, RealmAccess databaseAccess, RulesetStore rulesetStore, BeatmapOnlineLookupQueue beatmapOnlineLookupQueue) + public TestBeatmapModelManager(TestBeatmapManager testBeatmapManager, Storage storage, RealmAccess databaseAccess, BeatmapOnlineLookupQueue beatmapOnlineLookupQueue) : base(databaseAccess, storage, beatmapOnlineLookupQueue) { this.testBeatmapManager = testBeatmapManager; diff --git a/osu.Game/Tests/Visual/EditorTestScene.cs b/osu.Game/Tests/Visual/EditorTestScene.cs index f7d62a8694..bcf169bb1e 100644 --- a/osu.Game/Tests/Visual/EditorTestScene.cs +++ b/osu.Game/Tests/Visual/EditorTestScene.cs @@ -126,7 +126,7 @@ namespace osu.Game.Tests.Visual protected override BeatmapModelManager CreateBeatmapModelManager(Storage storage, RealmAccess realm, RulesetStore rulesets, BeatmapOnlineLookupQueue onlineLookupQueue) { - return new TestBeatmapModelManager(storage, realm, rulesets, onlineLookupQueue); + return new TestBeatmapModelManager(storage, realm, onlineLookupQueue); } protected override WorkingBeatmapCache CreateWorkingBeatmapCache(AudioManager audioManager, IResourceStore resources, IResourceStore storage, WorkingBeatmap defaultBeatmap, GameHost host) @@ -150,7 +150,7 @@ namespace osu.Game.Tests.Visual internal class TestBeatmapModelManager : BeatmapModelManager { - public TestBeatmapModelManager(Storage storage, RealmAccess databaseAccess, RulesetStore rulesetStore, BeatmapOnlineLookupQueue beatmapOnlineLookupQueue) + public TestBeatmapModelManager(Storage storage, RealmAccess databaseAccess, BeatmapOnlineLookupQueue beatmapOnlineLookupQueue) : base(databaseAccess, storage, beatmapOnlineLookupQueue) { } From e042f29ee3a12e60eb6d06165db93af1c37faf8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 1 Feb 2022 21:41:18 +0100 Subject: [PATCH 869/996] Remove skin ctor param from `LegacyCatchComboCounter` No longer used since 004798d61d44d88114acfd7c2861378261eab107. --- .../Skinning/Legacy/CatchLegacySkinTransformer.cs | 2 +- .../Skinning/Legacy/LegacyCatchComboCounter.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs index faad95e386..b2a555f89d 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs @@ -81,7 +81,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy case CatchSkinComponents.CatchComboCounter: if (providesComboCounter) - return new LegacyCatchComboCounter(Skin); + return new LegacyCatchComboCounter(); return null; diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatchComboCounter.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatchComboCounter.cs index 33c3867f5a..b4d29988d9 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatchComboCounter.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatchComboCounter.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy private readonly LegacyRollingCounter explosion; - public LegacyCatchComboCounter(ISkin skin) + public LegacyCatchComboCounter() { AutoSizeAxes = Axes.Both; From e4028b8fc1b86df31a6882480797cf0094c0a027 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 1 Feb 2022 21:42:58 +0100 Subject: [PATCH 870/996] Remove index ctor param from `ColumnHitObjectArea` No longer used since 5692cecaa47e9e071fb6079d9f3847b8b1de0972. --- .../Skinning/TestSceneColumnHitObjectArea.cs | 4 ++-- osu.Game.Rulesets.Mania/UI/Column.cs | 2 +- osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneColumnHitObjectArea.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneColumnHitObjectArea.cs index 215f8fb1d5..8034341d15 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneColumnHitObjectArea.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneColumnHitObjectArea.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning { RelativeSizeAxes = Axes.Both, Width = 0.5f, - Child = new ColumnHitObjectArea(0, new HitObjectContainer()) + Child = new ColumnHitObjectArea(new HitObjectContainer()) { RelativeSizeAxes = Axes.Both } @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning { RelativeSizeAxes = Axes.Both, Width = 0.5f, - Child = new ColumnHitObjectArea(1, new HitObjectContainer()) + Child = new ColumnHitObjectArea(new HitObjectContainer()) { RelativeSizeAxes = Axes.Both } diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index dc3b86ac7f..a04f5ef98e 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -62,7 +62,7 @@ namespace osu.Game.Rulesets.Mania.UI sampleTriggerSource = new GameplaySampleTriggerSource(HitObjectContainer), // For input purposes, the background is added at the highest depth, but is then proxied back below all other elements background.CreateProxy(), - HitObjectArea = new ColumnHitObjectArea(Index, HitObjectContainer) { RelativeSizeAxes = Axes.Both }, + HitObjectArea = new ColumnHitObjectArea(HitObjectContainer) { RelativeSizeAxes = Axes.Both }, new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.KeyArea), _ => new DefaultKeyArea()) { RelativeSizeAxes = Axes.Both diff --git a/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs b/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs index f69d2aafdc..51c138f5e1 100644 --- a/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs +++ b/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Mania.UI.Components private readonly Drawable hitTarget; - public ColumnHitObjectArea(int columnIndex, HitObjectContainer hitObjectContainer) + public ColumnHitObjectArea(HitObjectContainer hitObjectContainer) : base(hitObjectContainer) { AddRangeInternal(new[] From 7cdf63c6543034a7517ec17949874d16bdd1b47f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 1 Feb 2022 21:44:59 +0100 Subject: [PATCH 871/996] Remove unused `FindProvider()` methods No longer needed since 39f99bf785676c443f37248668eb9bf45006032e. --- osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs | 1 - osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs | 2 -- osu.Game.Tests/NonVisual/Skinning/LegacySkinAnimationTest.cs | 1 - osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs | 4 ---- 4 files changed, 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs index 1f01ba601b..a36f07ff7b 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs @@ -118,7 +118,6 @@ namespace osu.Game.Rulesets.Osu.Tests public Drawable GetDrawableComponent(ISkinComponent component) => null; public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => null; public ISample GetSample(ISampleInfo sampleInfo) => null; - public ISkin FindProvider(Func lookupFunction) => null; public IBindable GetConfig(TLookup lookup) { diff --git a/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs b/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs index 34f70659e3..5553c67141 100644 --- a/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs +++ b/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs @@ -131,8 +131,6 @@ namespace osu.Game.Tests.Gameplay public ISample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException(); - public ISkin FindProvider(Func lookupFunction) => null; - public IBindable GetConfig(TLookup lookup) { switch (lookup) diff --git a/osu.Game.Tests/NonVisual/Skinning/LegacySkinAnimationTest.cs b/osu.Game.Tests/NonVisual/Skinning/LegacySkinAnimationTest.cs index 785f31386d..4209f188cc 100644 --- a/osu.Game.Tests/NonVisual/Skinning/LegacySkinAnimationTest.cs +++ b/osu.Game.Tests/NonVisual/Skinning/LegacySkinAnimationTest.cs @@ -61,7 +61,6 @@ namespace osu.Game.Tests.NonVisual.Skinning public Drawable GetDrawableComponent(ISkinComponent component) => throw new NotSupportedException(); public ISample GetSample(ISampleInfo sampleInfo) => throw new NotSupportedException(); public IBindable GetConfig(TLookup lookup) => throw new NotSupportedException(); - public ISkin FindProvider(Func lookupFunction) => null; } private class TestAnimationTimeReference : IAnimationTimeReference diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs index 3e8ba69e01..35130f3109 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs @@ -301,8 +301,6 @@ namespace osu.Game.Tests.Visual.Gameplay public ISample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException(); public IBindable GetConfig(TLookup lookup) => throw new NotImplementedException(); - - public ISkin FindProvider(Func lookupFunction) => throw new NotImplementedException(); } private class SecondarySource : ISkin @@ -314,8 +312,6 @@ namespace osu.Game.Tests.Visual.Gameplay public ISample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException(); public IBindable GetConfig(TLookup lookup) => throw new NotImplementedException(); - - public ISkin FindProvider(Func lookupFunction) => throw new NotImplementedException(); } [Cached(typeof(ISkinSource))] From b978010b481fd8e46a7b0448b813a3e547d375ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 1 Feb 2022 21:47:55 +0100 Subject: [PATCH 872/996] Remove unused `allowMissing` parameter in audio file check test No longer used since 7f95400f466beafe6fa94c002cfd6ed8dc3f4f94. --- .../Editing/Checks/CheckTooShortAudioFilesTest.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Editing/Checks/CheckTooShortAudioFilesTest.cs b/osu.Game.Tests/Editing/Checks/CheckTooShortAudioFilesTest.cs index 242fec2f68..53c85defae 100644 --- a/osu.Game.Tests/Editing/Checks/CheckTooShortAudioFilesTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckTooShortAudioFilesTest.cs @@ -52,7 +52,7 @@ namespace osu.Game.Tests.Editing.Checks beatmap.BeatmapInfo.BeatmapSet.Files.Add(CheckTestHelpers.CreateMockFile("jpg")); // Should fail to load, but not produce an error due to the extension not being expected to load. - Assert.IsEmpty(check.Run(getContext(null, allowMissing: true))); + Assert.IsEmpty(check.Run(getContext(null))); } [Test] @@ -91,7 +91,7 @@ namespace osu.Game.Tests.Editing.Checks { using (var resourceStream = TestResources.OpenResource("Samples/missing.mp3")) { - Assert.IsEmpty(check.Run(getContext(resourceStream, allowMissing: true))); + Assert.IsEmpty(check.Run(getContext(resourceStream))); } } @@ -107,7 +107,7 @@ namespace osu.Game.Tests.Editing.Checks } } - private BeatmapVerifierContext getContext(Stream resourceStream, bool allowMissing = false) + private BeatmapVerifierContext getContext(Stream resourceStream) { var mockWorkingBeatmap = new Mock(beatmap, null, null); mockWorkingBeatmap.Setup(w => w.GetStream(It.IsAny())).Returns(resourceStream); From 3674ed15ce7cc4e5b0e50303e04db4af6eba3164 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 1 Feb 2022 21:52:16 +0100 Subject: [PATCH 873/996] Remove unused game host parameter No longer used since eeccf836ec04d2e3aac1fec93c15b48aa6672352. --- .../Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index 79cb424803..94e61eaee0 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -47,7 +47,7 @@ namespace osu.Game.Tests.Online { Dependencies.Cache(rulesets = new RulesetStore(Realm)); Dependencies.CacheAs(beatmaps = new TestBeatmapManager(LocalStorage, Realm, rulesets, API, audio, Resources, host, Beatmap.Default)); - Dependencies.CacheAs(beatmapDownloader = new TestBeatmapModelDownloader(beatmaps, API, host)); + Dependencies.CacheAs(beatmapDownloader = new TestBeatmapModelDownloader(beatmaps, API)); } [SetUp] @@ -196,7 +196,7 @@ namespace osu.Game.Tests.Online internal class TestBeatmapModelDownloader : BeatmapModelDownloader { - public TestBeatmapModelDownloader(IModelImporter importer, IAPIProvider apiProvider, GameHost gameHost) + public TestBeatmapModelDownloader(IModelImporter importer, IAPIProvider apiProvider) : base(importer, apiProvider) { } From 35b76532903254b3ea434f7fccf9ef95d07be72c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Feb 2022 09:13:19 +0900 Subject: [PATCH 874/996] Revert mod flow changes and add visual test coverage showing an overflow case --- .../Visual/SongSelect/TestSceneBeatmapLeaderboard.cs | 12 +++++++----- .../Online/Leaderboards/LeaderboardScoreTooltip.cs | 5 ++--- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 7292ec96ed..667fd08084 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -203,14 +203,16 @@ namespace osu.Game.Tests.Visual.SongSelect new OsuModHardRock(), new OsuModFlashlight { - FollowDelay = { Value = 200 } + FollowDelay = { Value = 200 }, + SizeMultiplier = { Value = 5 }, }, new OsuModDifficultyAdjust { - CircleSize = { Value = 8 }, - ApproachRate = { Value = 7 }, - OverallDifficulty = { Value = 6 }, - DrainRate = { Value = 5 }, + CircleSize = { Value = 11 }, + ApproachRate = { Value = 10 }, + OverallDifficulty = { Value = 10 }, + DrainRate = { Value = 10 }, + ExtendedLimits = { Value = true } } }, Ruleset = new OsuRuleset().RulesetInfo, diff --git a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs index a0eea94501..c26e9e6802 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs @@ -62,9 +62,8 @@ namespace osu.Game.Online.Leaderboards // Mods row modStatistics = new FillFlowContainer { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Direction = FillDirection.Full, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, Spacing = new Vector2(5, 0), }, new FillFlowContainer From b4fd1ecba20a10858a1219beddabf8122517fa22 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Wed, 2 Feb 2022 11:02:01 +0800 Subject: [PATCH 875/996] Hide attribute if the maximum is 0 --- .../Statistics/PerformanceStatisticTooltip.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs index 739fac9d63..bd6eb057c6 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs @@ -137,8 +137,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics { float percentage = (float)(attribute.Value / perfectAttribute.Value); if (float.IsNaN(percentage)) - percentage = 0; - string text = percentage.ToLocalisableString("0%").ToString(); + return null; return new GridContainer { @@ -181,7 +180,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, Font = OsuFont.GetFont(weight: FontWeight.SemiBold), - Text = text, + Text = percentage.ToLocalisableString("0%"), Colour = textColour } } @@ -199,9 +198,12 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics foreach (PerformanceDisplayAttribute attr in displayAttributes) { - Content.Add(attr.PropertyName == nameof(PerformanceAttributes.Total) + var attributeItem = attr.PropertyName == nameof(PerformanceAttributes.Total) ? createItemForTotal(attr, perfectDisplayAttributes.First(a => a.PropertyName == attr.PropertyName)) - : createItemForAttribute(attr, perfectDisplayAttributes.First(a => a.PropertyName == attr.PropertyName))); + : createItemForAttribute(attr, perfectDisplayAttributes.First(a => a.PropertyName == attr.PropertyName)); + + if (attributeItem != null) + Content.Add(attributeItem); } } From d065e32ca1f8c77b361593016bedf0487540fcff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Feb 2022 13:23:49 +0900 Subject: [PATCH 876/996] Fix crash due to `MatchLeaderboardScore`s not having populated rulesets --- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 2 +- .../OnlinePlay/Match/Components/MatchLeaderboardScore.cs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index b4aaebd4e4..c2393a5de5 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -71,7 +71,7 @@ namespace osu.Game.Online.Leaderboards private Storage storage { get; set; } public ITooltip GetCustomTooltip() => new LeaderboardScoreTooltip(); - public ScoreInfo TooltipContent => Score; + public virtual ScoreInfo TooltipContent => Score; public LeaderboardScore(ScoreInfo score, int? rank, bool isOnlineScope = true) { diff --git a/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboardScore.cs b/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboardScore.cs index 799c44cc28..cf7e33fd63 100644 --- a/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboardScore.cs +++ b/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboardScore.cs @@ -14,6 +14,8 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components { private readonly APIUserScoreAggregate score; + public override ScoreInfo TooltipContent => null; // match aggregate scores can't show statistics that the custom tooltip displays. + public MatchLeaderboardScore(APIUserScoreAggregate score, int? rank, bool isOnlineScope = true) : base(score.CreateScoreInfo(), rank, isOnlineScope) { From ddc8094a75babd5fad94ea8b692b8e12a899c461 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Feb 2022 13:34:23 +0900 Subject: [PATCH 877/996] Update description --- osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs index 6e942f672b..a65938184c 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Mania.Mods public override double ScoreMultiplier => 1; - public override string Description => @"Turns all hold notes into normal notes. No coordination required."; + public override string Description => @"Replaces all hold notes with normal notes."; public override IconUsage? Icon => FontAwesome.Solid.DotCircle; From 0036d0e26db709f1661e3cff89364ccb7c6f94b5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Feb 2022 13:58:13 +0900 Subject: [PATCH 878/996] Move alternate mod to "conversion" category --- osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs | 2 +- osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs index f68f2b5bee..936bb290b4 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override string Description => @"Don't use the same key twice in a row!"; public override double ScoreMultiplier => 1.0; public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay) }; - public override ModType Type => ModType.DifficultyIncrease; + public override ModType Type => ModType.Conversion; public override IconUsage? Icon => FontAwesome.Solid.Keyboard; private bool introEnded; diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 7e8974b5ed..2faecbcda2 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -159,7 +159,6 @@ namespace osu.Game.Rulesets.Osu new MultiMod(new OsuModDoubleTime(), new OsuModNightcore()), new OsuModHidden(), new MultiMod(new OsuModFlashlight(), new OsuModBlinds()), - new OsuModAlternate(), }; case ModType.Conversion: @@ -170,6 +169,7 @@ namespace osu.Game.Rulesets.Osu new OsuModClassic(), new OsuModRandom(), new OsuModMirror(), + new OsuModAlternate(), }; case ModType.Automation: From fed63abd83715cf1f904a53038999b7625e41f4f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Feb 2022 14:02:48 +0900 Subject: [PATCH 879/996] Sanitise interceptor logic to now require two separate check paths --- osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs index 936bb290b4..9d389432e5 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs @@ -33,8 +33,6 @@ namespace osu.Game.Rulesets.Osu.Mods private OsuAction? lastActionPressed; private DrawableRuleset ruleset; - private bool shouldAlternate => !isBreakTime.Value && introEnded; - public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { ruleset = drawableRuleset; @@ -54,16 +52,22 @@ namespace osu.Game.Rulesets.Osu.Mods }; } - private bool onPressed(OsuAction key) + private bool checkCorrectAction(OsuAction action) { - if (lastActionPressed == key) + if (isBreakTime.Value) + return true; + + if (!introEnded) + return true; + + if (lastActionPressed != action) { - ruleset.Cursor.FlashColour(Colour4.Red, flash_duration, Easing.OutQuint); + // User alternated correctly + lastActionPressed = action; return true; } - lastActionPressed = key; - + ruleset.Cursor.FlashColour(Colour4.Red, flash_duration, Easing.OutQuint); return false; } @@ -83,7 +87,8 @@ namespace osu.Game.Rulesets.Osu.Mods } public bool OnPressed(KeyBindingPressEvent e) - => mod.shouldAlternate && mod.onPressed(e.Action); + // if the pressed action is incorrect, block it from reaching gameplay. + => !mod.checkCorrectAction(e.Action); public void OnReleased(KeyBindingReleaseEvent e) { From c7a192cc5fae75bca23b5a714d6ff2fde49b6bbf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Feb 2022 14:04:10 +0900 Subject: [PATCH 880/996] Only handle `LeftButton` and `RightButton` actions There are definitely going to be other actions used in the future, which would immediately cause this mod to fail. Limiting handling to left/right buttons only is the correct way forward. --- osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs index 9d389432e5..b9f25bd1cf 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs @@ -60,9 +60,20 @@ namespace osu.Game.Rulesets.Osu.Mods if (!introEnded) return true; + switch (action) + { + case OsuAction.LeftButton: + case OsuAction.RightButton: + break; + + // Any action which is not left or right button should be ignored. + default: + return true; + } + if (lastActionPressed != action) { - // User alternated correctly + // User alternated correctly. lastActionPressed = action; return true; } From c5c4c85006ffd4a34e05ba33d6e81bb9acf8acc8 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Wed, 2 Feb 2022 13:29:18 +0800 Subject: [PATCH 881/996] Lazily create content of StatisticItem --- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 22 +++++++++------- osu.Game.Rulesets.Osu/OsuRuleset.cs | 25 +++++++++++-------- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 22 +++++++++------- .../Ranking/Statistics/StatisticContainer.cs | 2 +- .../Ranking/Statistics/StatisticItem.cs | 12 +++++++-- .../Ranking/Statistics/StatisticsPanel.cs | 10 +++++--- 6 files changed, 59 insertions(+), 34 deletions(-) diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 2098c7f5d8..8ac8001457 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -370,21 +370,25 @@ namespace osu.Game.Rulesets.Mania { Columns = new[] { - new StatisticItem("Timing Distribution", new HitEventTimingDistributionGraph(score.HitEvents) - { - RelativeSizeAxes = Axes.X, - Height = 250 - }), + new StatisticItem("Timing Distribution", + true, + () => new HitEventTimingDistributionGraph(score.HitEvents) + { + RelativeSizeAxes = Axes.X, + Height = 250 + }), } }, new StatisticRow { Columns = new[] { - new StatisticItem(string.Empty, new SimpleStatisticTable(3, new SimpleStatisticItem[] - { - new UnstableRate(score.HitEvents) - })) + new StatisticItem(string.Empty, + true, + () => new SimpleStatisticTable(3, new SimpleStatisticItem[] + { + new UnstableRate(score.HitEvents) + })) } } }; diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 18e4bb259c..b6f417b7fe 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -278,7 +278,8 @@ namespace osu.Game.Rulesets.Osu Columns = new[] { new StatisticItem("Timing Distribution", - new HitEventTimingDistributionGraph(timedHitEvents) + true, + () => new HitEventTimingDistributionGraph(timedHitEvents) { RelativeSizeAxes = Axes.X, Height = 250 @@ -289,21 +290,25 @@ namespace osu.Game.Rulesets.Osu { Columns = new[] { - new StatisticItem("Accuracy Heatmap", new AccuracyHeatmap(score, playableBeatmap) - { - RelativeSizeAxes = Axes.X, - Height = 250 - }), + new StatisticItem("Accuracy Heatmap", + true, + () => new AccuracyHeatmap(score, playableBeatmap) + { + RelativeSizeAxes = Axes.X, + Height = 250 + }), } }, new StatisticRow { Columns = new[] { - new StatisticItem(string.Empty, new SimpleStatisticTable(3, new SimpleStatisticItem[] - { - new UnstableRate(timedHitEvents) - })) + new StatisticItem(string.Empty, + true, + () => new SimpleStatisticTable(3, new SimpleStatisticItem[] + { + new UnstableRate(timedHitEvents) + })) } } }; diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index ca860f24c3..2a64b8dddd 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -213,21 +213,25 @@ namespace osu.Game.Rulesets.Taiko { Columns = new[] { - new StatisticItem("Timing Distribution", new HitEventTimingDistributionGraph(timedHitEvents) - { - RelativeSizeAxes = Axes.X, - Height = 250 - }), + new StatisticItem("Timing Distribution", + true, + () => new HitEventTimingDistributionGraph(timedHitEvents) + { + RelativeSizeAxes = Axes.X, + Height = 250 + }), } }, new StatisticRow { Columns = new[] { - new StatisticItem(string.Empty, new SimpleStatisticTable(3, new SimpleStatisticItem[] - { - new UnstableRate(timedHitEvents) - })) + new StatisticItem(string.Empty, + true, + () => new SimpleStatisticTable(3, new SimpleStatisticItem[] + { + new UnstableRate(timedHitEvents) + })) } } }; diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs b/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs index 485d24d024..f3bd2c6fc1 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs @@ -43,7 +43,7 @@ namespace osu.Game.Screens.Ranking.Statistics RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Margin = new MarginPadding { Top = 15 }, - Child = item.Content + Child = item.Content() } }, }, diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs b/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs index 4903983759..5e6ddf445b 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using JetBrains.Annotations; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -20,22 +21,29 @@ namespace osu.Game.Screens.Ranking.Statistics /// /// The content to be displayed. /// - public readonly Drawable Content; + public readonly Func Content; /// /// The of this row. This can be thought of as the column dimension of an encompassing . /// public readonly Dimension Dimension; + /// + /// Whether this item requires hit events. If true, the will not be created if no hit events are available. + /// + public readonly bool RequiresHitEvents; + /// /// Creates a new , to be displayed inside a in the results screen. /// /// The name of the item. Can be to hide the item header. + /// Whether this item requires hit events. If true, the will not be created if no hit events are available. /// The content to be displayed. /// The of this item. This can be thought of as the column dimension of an encompassing . - public StatisticItem([NotNull] string name, [NotNull] Drawable content, [CanBeNull] Dimension dimension = null) + public StatisticItem([NotNull] string name, bool requiresHitEvents, [NotNull] Func content, [CanBeNull] Dimension dimension = null) { Name = name; + RequiresHitEvents = requiresHitEvents; Content = content; Dimension = dimension; } diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index 567a2307dd..14cdfe2f03 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -118,6 +118,10 @@ namespace osu.Game.Screens.Ranking.Statistics foreach (var row in newScore.Ruleset.CreateInstance().CreateStatisticsForScore(newScore, playableBeatmap)) { + var columnsToDisplay = newScore.HitEvents.Count == 0 + ? row.Columns?.Where(c => !c.RequiresHitEvents).ToArray() + : row.Columns; + rows.Add(new GridContainer { Anchor = Anchor.TopCentre, @@ -126,14 +130,14 @@ namespace osu.Game.Screens.Ranking.Statistics AutoSizeAxes = Axes.Y, Content = new[] { - row.Columns?.Select(c => new StatisticContainer(c) + columnsToDisplay?.Select(c => new StatisticContainer(c) { Anchor = Anchor.Centre, Origin = Anchor.Centre, }).Cast().ToArray() }, - ColumnDimensions = Enumerable.Range(0, row.Columns?.Length ?? 0) - .Select(i => row.Columns[i].Dimension ?? new Dimension()).ToArray(), + ColumnDimensions = Enumerable.Range(0, columnsToDisplay?.Length ?? 0) + .Select(i => columnsToDisplay?[i].Dimension ?? new Dimension()).ToArray(), RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) } }); } From a2affefb0aa8ca794ff329a1a7e65e3502450f35 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Feb 2022 14:33:17 +0900 Subject: [PATCH 882/996] Avoid checking gameplay clock time in `Update` method --- osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs index b9f25bd1cf..46b97dd23b 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs @@ -16,7 +16,7 @@ using osu.Game.Screens.Play; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModAlternate : Mod, IApplicableToDrawableRuleset, IApplicableToPlayer, IUpdatableByPlayfield + public class OsuModAlternate : Mod, IApplicableToDrawableRuleset, IApplicableToPlayer { public override string Name => @"Alternate"; public override string Acronym => @"AL"; @@ -26,20 +26,23 @@ namespace osu.Game.Rulesets.Osu.Mods public override ModType Type => ModType.Conversion; public override IconUsage? Icon => FontAwesome.Solid.Keyboard; - private bool introEnded; - private double earliestStartTime; + private double firstObjectValidJudgementTime; private IBindable isBreakTime; private const double flash_duration = 1000; private OsuAction? lastActionPressed; private DrawableRuleset ruleset; + private IFrameStableClock gameplayClock; + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { ruleset = drawableRuleset; drawableRuleset.KeyBindingInputManager.Add(new InputInterceptor(this)); var firstHitObject = ruleset.Objects.FirstOrDefault(); - earliestStartTime = (firstHitObject?.StartTime ?? 0) - (firstHitObject?.HitWindows.WindowFor(HitResult.Meh) ?? 0); + firstObjectValidJudgementTime = (firstHitObject?.StartTime ?? 0) - (firstHitObject?.HitWindows.WindowFor(HitResult.Meh) ?? 0); + + gameplayClock = drawableRuleset.FrameStableClock; } public void ApplyToPlayer(Player player) @@ -57,7 +60,7 @@ namespace osu.Game.Rulesets.Osu.Mods if (isBreakTime.Value) return true; - if (!introEnded) + if (gameplayClock.CurrentTime < firstObjectValidJudgementTime) return true; switch (action) @@ -82,12 +85,6 @@ namespace osu.Game.Rulesets.Osu.Mods return false; } - public void Update(Playfield playfield) - { - if (!introEnded) - introEnded = playfield.Clock.CurrentTime > earliestStartTime; - } - private class InputInterceptor : Component, IKeyBindingHandler { private readonly OsuModAlternate mod; From 3ba5d88914d09e1725ee3abaec9fd44699e3973c Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Wed, 2 Feb 2022 13:41:51 +0800 Subject: [PATCH 883/996] Update statistics item display logic --- .../Ranking/Statistics/StatisticsPanel.cs | 131 +++++++++--------- 1 file changed, 69 insertions(+), 62 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index 14cdfe2f03..9d89aa213c 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -74,85 +74,92 @@ namespace osu.Game.Screens.Ranking.Statistics if (newScore == null) return; - if (newScore.HitEvents.Count == 0) + spinner.Show(); + + var localCancellationSource = loadCancellation = new CancellationTokenSource(); + IBeatmap playableBeatmap = null; + + // Todo: The placement of this is temporary. Eventually we'll both generate the playable beatmap _and_ run through it in a background task to generate the hit events. + Task.Run(() => { - content.Add(new FillFlowContainer + playableBeatmap = beatmapManager.GetWorkingBeatmap(newScore.BeatmapInfo).GetPlayableBeatmap(newScore.Ruleset, newScore.Mods); + }, loadCancellation.Token).ContinueWith(t => Schedule(() => + { + var rows = new FillFlowContainer { - RelativeSizeAxes = Axes.Both, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, Direction = FillDirection.Vertical, - Children = new Drawable[] + Spacing = new Vector2(30, 15), + Alpha = 0 + }; + + bool panelIsEmpty = true; + bool hitEventsAvailable = newScore.HitEvents.Count != 0; + + foreach (var row in newScore.Ruleset.CreateInstance().CreateStatisticsForScore(newScore, playableBeatmap)) + { + var columnsToDisplay = hitEventsAvailable + ? row.Columns + : row.Columns?.Where(c => !c.RequiresHitEvents).ToArray(); + + if (columnsToDisplay?.Any() ?? false) + panelIsEmpty = false; + + rows.Add(new GridContainer { - new MessagePlaceholder("Extended statistics are only available after watching a replay!"), - new ReplayDownloadButton(newScore) + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Content = new[] { - Scale = new Vector2(1.5f), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, + columnsToDisplay?.Select(c => new StatisticContainer(c) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }).Cast().ToArray() }, - } - }); - } - else - { - spinner.Show(); + ColumnDimensions = Enumerable.Range(0, columnsToDisplay?.Length ?? 0) + .Select(i => columnsToDisplay?[i].Dimension ?? new Dimension()).ToArray(), + RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) } + }); + } - var localCancellationSource = loadCancellation = new CancellationTokenSource(); - IBeatmap playableBeatmap = null; - - // Todo: The placement of this is temporary. Eventually we'll both generate the playable beatmap _and_ run through it in a background task to generate the hit events. - Task.Run(() => + if (!hitEventsAvailable) { - playableBeatmap = beatmapManager.GetWorkingBeatmap(newScore.BeatmapInfo).GetPlayableBeatmap(newScore.Ruleset, newScore.Mods); - }, loadCancellation.Token).ContinueWith(t => Schedule(() => - { - var rows = new FillFlowContainer + rows.Add(new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, Direction = FillDirection.Vertical, - Spacing = new Vector2(30, 15), - Alpha = 0 - }; - - foreach (var row in newScore.Ruleset.CreateInstance().CreateStatisticsForScore(newScore, playableBeatmap)) - { - var columnsToDisplay = newScore.HitEvents.Count == 0 - ? row.Columns?.Where(c => !c.RequiresHitEvents).ToArray() - : row.Columns; - - rows.Add(new GridContainer + Children = new Drawable[] { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Content = new[] + new MessagePlaceholder(panelIsEmpty + ? "Extended statistics are only available after watching a replay!" + : "More statistics available after watching a replay!"), + new ReplayDownloadButton(newScore) { - columnsToDisplay?.Select(c => new StatisticContainer(c) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }).Cast().ToArray() + Scale = new Vector2(1.5f), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, }, - ColumnDimensions = Enumerable.Range(0, columnsToDisplay?.Length ?? 0) - .Select(i => columnsToDisplay?[i].Dimension ?? new Dimension()).ToArray(), - RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) } - }); - } + } + }); + } - LoadComponentAsync(rows, d => - { - if (!Score.Value.Equals(newScore)) - return; + LoadComponentAsync(rows, d => + { + if (!Score.Value.Equals(newScore)) + return; - spinner.Hide(); - content.Add(d); - d.FadeIn(250, Easing.OutQuint); - }, localCancellationSource.Token); - }), localCancellationSource.Token); - } + spinner.Hide(); + content.Add(d); + d.FadeIn(250, Easing.OutQuint); + }, localCancellationSource.Token); + }), localCancellationSource.Token); } protected override bool OnClick(ClickEvent e) From 6e60e68b809be7e1d93f3a80371f1ba551d45c43 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Wed, 2 Feb 2022 14:44:06 +0900 Subject: [PATCH 884/996] Change from click to mousedown+mouseup and only play when cursor is visible --- osu.Game/Graphics/Cursor/MenuCursor.cs | 29 +++++++++++++++----------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/osu.Game/Graphics/Cursor/MenuCursor.cs b/osu.Game/Graphics/Cursor/MenuCursor.cs index a89d8dac71..0cc751ea21 100644 --- a/osu.Game/Graphics/Cursor/MenuCursor.cs +++ b/osu.Game/Graphics/Cursor/MenuCursor.cs @@ -76,18 +76,6 @@ namespace osu.Game.Graphics.Cursor return base.OnMouseMove(e); } - protected override bool OnClick(ClickEvent e) - { - var channel = tapSample.GetChannel(); - - // scale to [-0.75, 0.75] so that the sample isn't fully panned left or right (sounds weird) - channel.Balance.Value = ((activeCursor.X / DrawWidth) * 2 - 1) * 0.75; - channel.Frequency.Value = 0.99 + RNG.NextDouble(0.02); - channel.Play(); - - return base.OnClick(e); - } - protected override bool OnMouseDown(MouseDownEvent e) { if (State.Value == Visibility.Visible) @@ -105,6 +93,8 @@ namespace osu.Game.Graphics.Cursor dragRotationState = DragRotationState.DragStarted; positionMouseDown = e.MousePosition; } + + playTapSample(); } return base.OnMouseDown(e); @@ -122,6 +112,9 @@ namespace osu.Game.Graphics.Cursor activeCursor.RotateTo(0, 600 * (1 + Math.Abs(activeCursor.Rotation / 720)), Easing.OutElasticHalf); dragRotationState = DragRotationState.NotDragging; } + + if (State.Value == Visibility.Visible) + playTapSample(0.8); } base.OnMouseUp(e); @@ -139,6 +132,18 @@ namespace osu.Game.Graphics.Cursor activeCursor.ScaleTo(0.6f, 250, Easing.In); } + private void playTapSample(double baseFrequency = 1f) + { + const float random_range = 0.02f; + SampleChannel channel = tapSample.GetChannel(); + + // Scale to [-0.75, 0.75] so that the sample isn't fully panned left or right (sounds weird) + channel.Balance.Value = ((activeCursor.X / DrawWidth) * 2 - 1) * 0.75; + channel.Frequency.Value = baseFrequency - (random_range / 2f) + RNG.NextDouble(random_range); + + channel.Play(); + } + public class Cursor : Container { private Container cursorContainer; From 0c5da9370a239b957f6d7a2fce929c0b7cff323c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Feb 2022 14:51:55 +0900 Subject: [PATCH 885/996] Fix rulesets potentially being marked `Available` even when methods are missing Came up when running the game after the recent breaking changes (https://github.com/ppy/osu/pull/16722), where two template rulesets I had loaded were erroring on startup but still being marked as available, allowing them to crash the game on attempting to initiate relpay logic. These cases are already handled for first-time ruleset loading via the `GetTypes()` enumeration in `RulesetStore.addRuleset`, but when consistency checking already present rulesets the only runtime validation being done was `ruleset.CreateInstance()`, which does not handle missing types or methods. --- osu.Game/Rulesets/RulesetStore.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index d017d54ed9..dd25005006 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -149,6 +149,10 @@ namespace osu.Game.Rulesets var instanceInfo = (Activator.CreateInstance(resolvedType) as Ruleset)?.RulesetInfo ?? throw new RulesetLoadException(@"Instantiation failure"); + // If a ruleset isn't up-to-date with the API, it could cause a crash at an arbitrary point of execution. + // To eagerly handle cases of missing implementations, enumerate all types here and mark as non-available on throw. + resolvedType.Assembly.GetTypes(); + r.Name = instanceInfo.Name; r.ShortName = instanceInfo.ShortName; r.InstantiationInfo = instanceInfo.InstantiationInfo; From e7d72f1823a5a97db761b0712851128942668366 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Feb 2022 15:10:56 +0900 Subject: [PATCH 886/996] Revert recent changes --- osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs | 80 +++++++++++++++---- 1 file changed, 66 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs index a383b533fd..cfe3312415 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs @@ -11,8 +11,8 @@ using osu.Game.Rulesets.Osu.UI; using osuTK; using System.Linq; using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Configuration; using osu.Framework.Bindables; +using osu.Game.Configuration; namespace osu.Game.Rulesets.Osu.Mods { @@ -34,10 +34,16 @@ namespace osu.Game.Rulesets.Osu.Mods MaxValue = 1.0f, }; - private double? lastUpdate; + private const float spin_radius = 30; + + private Vector2? prevCursorPos; + private OsuInputManager inputManager; public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { + // Grab the input manager for future use + inputManager = (OsuInputManager)drawableRuleset.KeyBindingInputManager; + // Hide judgment displays and follow points drawableRuleset.Playfield.DisplayJudgements.Value = false; (drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide(); @@ -45,29 +51,75 @@ namespace osu.Game.Rulesets.Osu.Mods public void Update(Playfield playfield) { + var cursorPos = playfield.Cursor.ActiveCursor.DrawPosition; double currentTime = playfield.Clock.CurrentTime; - if (currentTime - (lastUpdate ?? double.MinValue) < 100) - return; - - Vector2 cursorPos = playfield.Cursor.ActiveCursor.DrawPosition; - + // Move all currently alive object to new destination foreach (var drawable in playfield.HitObjectContainer.AliveObjects.OfType()) { var h = drawable.HitObject; - if (currentTime < h.StartTime && (drawable is DrawableHitCircle || drawable is DrawableSlider)) + switch (drawable) { - double timeMoving = currentTime - (h.StartTime - h.TimePreempt); - float percentDoneMoving = (float)(timeMoving / h.TimePreempt); - float percentDistLeft = Math.Clamp(AssistStrength.Value - percentDoneMoving + 0.1f, 0, 1); + case DrawableHitCircle circle: - Vector2 targetPos = drawable.Position + percentDistLeft * (cursorPos - drawable.Position); - drawable.MoveTo(targetPos, h.StartTime - currentTime); + // 10ms earlier on the note to reduce chance of missing when clicking early / cursor moves fast + circle.MoveTo(cursorPos, Math.Max(0, h.StartTime - currentTime - 10)); + // FIXME: some circles cause flash at original(?) position when clicked too early + + break; + + case DrawableSlider slider: + + // Move slider to cursor + if (currentTime < h.StartTime) + { + slider.MoveTo(cursorPos, Math.Max(0, h.StartTime - currentTime - 10)); + } + // Move slider so that sliderball stays on the cursor + else + { + slider.HeadCircle.Hide(); // hide flash, triangles, ... so they don't move with slider + slider.MoveTo(cursorPos - slider.Ball.DrawPosition); + // FIXME: some sliders re-appearing at their original position for a single frame when they're done + } + + break; + + case DrawableSpinner spinner: + + // Move spinner _next_ to cursor + if (currentTime < h.StartTime) + { + spinner.MoveTo(cursorPos + new Vector2(0, -spin_radius), Math.Max(0, h.StartTime - currentTime - 10)); + } + else + { + // Move spinner visually + Vector2 delta = spin_radius * (spinner.Position - prevCursorPos ?? cursorPos).Normalized(); + const float angle = 3 * MathF.PI / 180; // radians per update, arbitrary value + + // Rotation matrix + var targetPos = new Vector2( + delta.X * MathF.Cos(angle) - delta.Y * MathF.Sin(angle) + cursorPos.X, + delta.X * MathF.Sin(angle) + delta.Y * MathF.Cos(angle) + cursorPos.Y + ); + + spinner.MoveTo(targetPos); + + // Move spinner logically + if (inputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false) + { + // Arbitrary value, might lead to some inconsistencies depending on clock rate, replay, ... + spinner.RotationTracker.AddRotation(2 * MathF.PI); + } + } + + break; } } - lastUpdate = currentTime; + prevCursorPos = cursorPos; } } } From 104256a054150963b6ffd5a4b68c14fedcfff834 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Feb 2022 15:06:04 +0900 Subject: [PATCH 887/996] Add test coverage --- .../Mods/TestSceneOsuModAimAssist.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAimAssist.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAimAssist.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAimAssist.cs new file mode 100644 index 0000000000..8fa7e0cd09 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAimAssist.cs @@ -0,0 +1,23 @@ +// 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 TestSceneOsuModAimAssist : OsuModTestScene + { + [Test] + public void TestAimAssist() + { + var mod = new OsuModAimAssist(); + + CreateModTest(new ModTestData + { + Autoplay = false, + Mod = mod, + }); + } + } +} From 334ed2c9c488ec63e786d9197f756aa5bdaeb3b0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Feb 2022 15:36:09 +0900 Subject: [PATCH 888/996] Fix sliders moving before they are actually hit --- osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs index cfe3312415..a3c5638ea7 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs @@ -72,7 +72,7 @@ namespace osu.Game.Rulesets.Osu.Mods case DrawableSlider slider: // Move slider to cursor - if (currentTime < h.StartTime) + if (!slider.HeadCircle.Result.HasResult) { slider.MoveTo(cursorPos, Math.Max(0, h.StartTime - currentTime - 10)); } From f07502ac5fadab49871be06c51bb29d95a7cb32a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Feb 2022 16:15:06 +0900 Subject: [PATCH 889/996] Use simple damp easing rather than transforms --- osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs | 69 ++++++++++--------- 1 file changed, 37 insertions(+), 32 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs index a3c5638ea7..db83ee09af 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs @@ -2,17 +2,18 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Sprites; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Rulesets.UI; -using osu.Game.Rulesets.Osu.UI; -using osuTK; using System.Linq; -using osu.Game.Rulesets.Osu.Objects; 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 { @@ -26,17 +27,18 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay) }; + private IFrameStableClock gameplayClock; + [SettingSource("Assist strength", "Change the distance notes should travel towards you.", 0)] - public BindableFloat AssistStrength { get; } = new BindableFloat(0.3f) + public BindableFloat AssistStrength { get; } = new BindableFloat(0.5f) { Precision = 0.05f, - MinValue = 0.0f, + MinValue = 0.1f, MaxValue = 1.0f, }; - private const float spin_radius = 30; + private const float spin_radius = 50; - private Vector2? prevCursorPos; private OsuInputManager inputManager; public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) @@ -44,6 +46,8 @@ namespace osu.Game.Rulesets.Osu.Mods // Grab the input manager for future use inputManager = (OsuInputManager)drawableRuleset.KeyBindingInputManager; + gameplayClock = drawableRuleset.FrameStableClock; + // Hide judgment displays and follow points drawableRuleset.Playfield.DisplayJudgements.Value = false; (drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide(); @@ -62,10 +66,7 @@ namespace osu.Game.Rulesets.Osu.Mods switch (drawable) { case DrawableHitCircle circle: - - // 10ms earlier on the note to reduce chance of missing when clicking early / cursor moves fast - circle.MoveTo(cursorPos, Math.Max(0, h.StartTime - currentTime - 10)); - // FIXME: some circles cause flash at original(?) position when clicked too early + easeTo(circle, cursorPos); break; @@ -74,13 +75,13 @@ namespace osu.Game.Rulesets.Osu.Mods // Move slider to cursor if (!slider.HeadCircle.Result.HasResult) { - slider.MoveTo(cursorPos, Math.Max(0, h.StartTime - currentTime - 10)); + easeTo(slider, cursorPos); } // Move slider so that sliderball stays on the cursor else { slider.HeadCircle.Hide(); // hide flash, triangles, ... so they don't move with slider - slider.MoveTo(cursorPos - slider.Ball.DrawPosition); + easeTo(slider, cursorPos - slider.Ball.DrawPosition); // FIXME: some sliders re-appearing at their original position for a single frame when they're done } @@ -91,35 +92,39 @@ namespace osu.Game.Rulesets.Osu.Mods // Move spinner _next_ to cursor if (currentTime < h.StartTime) { - spinner.MoveTo(cursorPos + new Vector2(0, -spin_radius), Math.Max(0, h.StartTime - currentTime - 10)); + easeTo(spinner, cursorPos + new Vector2(0, -spin_radius)); } else { // Move spinner visually - Vector2 delta = spin_radius * (spinner.Position - prevCursorPos ?? cursorPos).Normalized(); - const float angle = 3 * MathF.PI / 180; // radians per update, arbitrary value - - // Rotation matrix - var targetPos = new Vector2( - delta.X * MathF.Cos(angle) - delta.Y * MathF.Sin(angle) + cursorPos.X, - delta.X * MathF.Sin(angle) + delta.Y * MathF.Cos(angle) + cursorPos.Y - ); - - spinner.MoveTo(targetPos); + Vector2 delta = new Vector2(spin_radius); + float angle = (float)gameplayClock.CurrentTime * 10; // Move spinner logically if (inputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false) { - // Arbitrary value, might lead to some inconsistencies depending on clock rate, replay, ... - spinner.RotationTracker.AddRotation(2 * MathF.PI); + var targetPos = new Vector2( + delta.X * MathF.Cos(angle) - delta.Y * MathF.Sin(angle) + cursorPos.X, + delta.X * MathF.Sin(angle) + delta.Y * MathF.Cos(angle) + cursorPos.Y + ); + + easeTo(spinner, targetPos); } } break; } } + } - prevCursorPos = cursorPos; + private void easeTo(DrawableHitObject hitObject, Vector2 destination) + { + double dampLength = Interpolation.Lerp(500, 50, AssistStrength.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 987aa5a21c043360d71f0155b52d70e77ddd3a5e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Feb 2022 16:15:14 +0900 Subject: [PATCH 890/996] Add testing of different strengths --- .../Mods/TestSceneOsuModAimAssist.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAimAssist.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAimAssist.cs index 8fa7e0cd09..b8310bc4e7 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAimAssist.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAimAssist.cs @@ -8,15 +8,19 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { public class TestSceneOsuModAimAssist : OsuModTestScene { - [Test] - public void TestAimAssist() + [TestCase(0.1f)] + [TestCase(0.5f)] + [TestCase(1)] + public void TestAimAssist(float strength) { - var mod = new OsuModAimAssist(); - CreateModTest(new ModTestData { + Mod = new OsuModAimAssist + { + AssistStrength = { Value = strength }, + }, + PassCondition = () => true, Autoplay = false, - Mod = mod, }); } } From 2e46404fe5e10f5279730ad61f90be5a805830b8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Feb 2022 16:15:49 +0900 Subject: [PATCH 891/996] Remove spinner support for now --- osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs | 30 ------------------- 1 file changed, 30 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs index db83ee09af..e3a68bcbcc 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs @@ -37,8 +37,6 @@ namespace osu.Game.Rulesets.Osu.Mods MaxValue = 1.0f, }; - private const float spin_radius = 50; - private OsuInputManager inputManager; public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) @@ -82,34 +80,6 @@ namespace osu.Game.Rulesets.Osu.Mods { slider.HeadCircle.Hide(); // hide flash, triangles, ... so they don't move with slider easeTo(slider, cursorPos - slider.Ball.DrawPosition); - // FIXME: some sliders re-appearing at their original position for a single frame when they're done - } - - break; - - case DrawableSpinner spinner: - - // Move spinner _next_ to cursor - if (currentTime < h.StartTime) - { - easeTo(spinner, cursorPos + new Vector2(0, -spin_radius)); - } - else - { - // Move spinner visually - Vector2 delta = new Vector2(spin_radius); - float angle = (float)gameplayClock.CurrentTime * 10; - - // Move spinner logically - if (inputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false) - { - var targetPos = new Vector2( - delta.X * MathF.Cos(angle) - delta.Y * MathF.Sin(angle) + cursorPos.X, - delta.X * MathF.Sin(angle) + delta.Y * MathF.Cos(angle) + cursorPos.Y - ); - - easeTo(spinner, targetPos); - } } break; From 6e41a6e704512ea2a355ae23d5793435ce464ef7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Feb 2022 16:26:10 +0900 Subject: [PATCH 892/996] Tidy up code into a presentable state --- osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs | 31 +++++-------------- 1 file changed, 7 insertions(+), 24 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs index e3a68bcbcc..ed4b139e00 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs @@ -2,7 +2,6 @@ // 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; @@ -23,30 +22,26 @@ namespace osu.Game.Rulesets.Osu.Mods public override string Acronym => "AA"; public override IconUsage? Icon => FontAwesome.Solid.MousePointer; public override ModType Type => ModType.Fun; - public override string Description => "No need to chase the circle, the circle chases you"; + public override string Description => "No need to chase the circle – the circle chases you!"; public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay) }; private IFrameStableClock gameplayClock; - [SettingSource("Assist strength", "Change the distance notes should travel towards you.", 0)] + [SettingSource("Assist strength", "How much this mod will assist you.", 0)] public BindableFloat AssistStrength { get; } = new BindableFloat(0.5f) { Precision = 0.05f, - MinValue = 0.1f, + MinValue = 0.05f, MaxValue = 1.0f, }; - private OsuInputManager inputManager; - public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { - // Grab the input manager for future use - inputManager = (OsuInputManager)drawableRuleset.KeyBindingInputManager; - gameplayClock = drawableRuleset.FrameStableClock; - // Hide judgment displays and follow points + // 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(); } @@ -54,33 +49,21 @@ namespace osu.Game.Rulesets.Osu.Mods public void Update(Playfield playfield) { var cursorPos = playfield.Cursor.ActiveCursor.DrawPosition; - double currentTime = playfield.Clock.CurrentTime; - // Move all currently alive object to new destination - foreach (var drawable in playfield.HitObjectContainer.AliveObjects.OfType()) + foreach (var drawable in playfield.HitObjectContainer.AliveObjects) { - var h = drawable.HitObject; - switch (drawable) { case DrawableHitCircle circle: easeTo(circle, cursorPos); - break; case DrawableSlider slider: - // Move slider to cursor if (!slider.HeadCircle.Result.HasResult) - { easeTo(slider, cursorPos); - } - // Move slider so that sliderball stays on the cursor else - { - slider.HeadCircle.Hide(); // hide flash, triangles, ... so they don't move with slider easeTo(slider, cursorPos - slider.Ball.DrawPosition); - } break; } @@ -89,7 +72,7 @@ namespace osu.Game.Rulesets.Osu.Mods private void easeTo(DrawableHitObject hitObject, Vector2 destination) { - double dampLength = Interpolation.Lerp(500, 50, AssistStrength.Value); + double dampLength = Interpolation.Lerp(3000, 40, AssistStrength.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); From 4758de226beda4f592aecef6f2ec2d8d0c390f03 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Feb 2022 16:27:59 +0900 Subject: [PATCH 893/996] Update resources --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index f85a96f819..963b61a2d3 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -51,7 +51,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index aa6fb93aa0..cc68393eae 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -37,7 +37,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index fbb4688588..846617c2f7 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + From 5e3d124eef996801dcb315cc48ed7499dc4aa0ef Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Wed, 2 Feb 2022 17:20:22 +0800 Subject: [PATCH 894/996] Add scrolling to the extended statistics panel --- .../Ranking/Statistics/StatisticsPanel.cs | 78 +++++++++++++------ 1 file changed, 56 insertions(+), 22 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index 9d89aa213c..1c96e640b4 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; using osu.Game.Beatmaps; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Online.Placeholders; using osu.Game.Scoring; @@ -85,15 +86,21 @@ namespace osu.Game.Screens.Ranking.Statistics playableBeatmap = beatmapManager.GetWorkingBeatmap(newScore.BeatmapInfo).GetPlayableBeatmap(newScore.Ruleset, newScore.Mods); }, loadCancellation.Token).ContinueWith(t => Schedule(() => { - var rows = new FillFlowContainer + FillFlowContainer rows; + Container container = new OsuScrollContainer(Direction.Vertical) { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre, - Direction = FillDirection.Vertical, - Spacing = new Vector2(30, 15), - Alpha = 0 + Alpha = 0, + Children = new[] + { + rows = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y + } + } }; bool panelIsEmpty = true; @@ -107,6 +114,8 @@ namespace osu.Game.Screens.Ranking.Statistics if (columnsToDisplay?.Any() ?? false) panelIsEmpty = false; + else + continue; rows.Add(new GridContainer { @@ -114,6 +123,7 @@ namespace osu.Game.Screens.Ranking.Statistics Origin = Anchor.TopCentre, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, + Margin = new MarginPadding { Bottom = 15 }, Content = new[] { columnsToDisplay?.Select(c => new StatisticContainer(c) @@ -130,27 +140,51 @@ namespace osu.Game.Screens.Ranking.Statistics if (!hitEventsAvailable) { - rows.Add(new FillFlowContainer + if (panelIsEmpty) { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Children = new Drawable[] + // Replace the scroll container with fill flow container to get the message centered. + container = new FillFlowContainer { - new MessagePlaceholder(panelIsEmpty - ? "Extended statistics are only available after watching a replay!" - : "More statistics available after watching a replay!"), - new ReplayDownloadButton(newScore) + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Direction = FillDirection.Vertical, + Children = new Drawable[] { - Scale = new Vector2(1.5f), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - } - }); + new MessagePlaceholder("Extended statistics are only available after watching a replay!"), + new ReplayDownloadButton(newScore) + { + Scale = new Vector2(1.5f), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + } + }; + } + else + { + rows.Add(new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Children = new Drawable[] + { + new MessagePlaceholder("More statistics available after watching a replay!"), + new ReplayDownloadButton(newScore) + { + Scale = new Vector2(1.5f), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + } + }); + } } - LoadComponentAsync(rows, d => + LoadComponentAsync(container, d => { if (!Score.Value.Equals(newScore)) return; From 6a482827fea64cbc0869700489e4feb9389f86d6 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Wed, 2 Feb 2022 17:23:03 +0800 Subject: [PATCH 895/996] Fix weird line breaking --- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 6 ++---- osu.Game.Rulesets.Osu/OsuRuleset.cs | 9 +++------ osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 6 ++---- 3 files changed, 7 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 8ac8001457..0ea1cd5fc7 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -370,8 +370,7 @@ namespace osu.Game.Rulesets.Mania { Columns = new[] { - new StatisticItem("Timing Distribution", - true, + new StatisticItem("Timing Distribution", true, () => new HitEventTimingDistributionGraph(score.HitEvents) { RelativeSizeAxes = Axes.X, @@ -383,8 +382,7 @@ namespace osu.Game.Rulesets.Mania { Columns = new[] { - new StatisticItem(string.Empty, - true, + new StatisticItem(string.Empty, true, () => new SimpleStatisticTable(3, new SimpleStatisticItem[] { new UnstableRate(score.HitEvents) diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index b6f417b7fe..2bf47100c1 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -277,8 +277,7 @@ namespace osu.Game.Rulesets.Osu { Columns = new[] { - new StatisticItem("Timing Distribution", - true, + new StatisticItem("Timing Distribution", true, () => new HitEventTimingDistributionGraph(timedHitEvents) { RelativeSizeAxes = Axes.X, @@ -290,8 +289,7 @@ namespace osu.Game.Rulesets.Osu { Columns = new[] { - new StatisticItem("Accuracy Heatmap", - true, + new StatisticItem("Accuracy Heatmap", true, () => new AccuracyHeatmap(score, playableBeatmap) { RelativeSizeAxes = Axes.X, @@ -303,8 +301,7 @@ namespace osu.Game.Rulesets.Osu { Columns = new[] { - new StatisticItem(string.Empty, - true, + new StatisticItem(string.Empty, true, () => new SimpleStatisticTable(3, new SimpleStatisticItem[] { new UnstableRate(timedHitEvents) diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 2a64b8dddd..fe4116b4a6 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -213,8 +213,7 @@ namespace osu.Game.Rulesets.Taiko { Columns = new[] { - new StatisticItem("Timing Distribution", - true, + new StatisticItem("Timing Distribution", true, () => new HitEventTimingDistributionGraph(timedHitEvents) { RelativeSizeAxes = Axes.X, @@ -226,8 +225,7 @@ namespace osu.Game.Rulesets.Taiko { Columns = new[] { - new StatisticItem(string.Empty, - true, + new StatisticItem(string.Empty, true, () => new SimpleStatisticTable(3, new SimpleStatisticItem[] { new UnstableRate(timedHitEvents) From 90e30bc9e8b3e7058aebfa83336383d52d0e0efb Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Wed, 2 Feb 2022 17:26:17 +0800 Subject: [PATCH 896/996] Remove useless null checks --- osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index 1c96e640b4..8b69f83055 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -110,9 +110,9 @@ namespace osu.Game.Screens.Ranking.Statistics { var columnsToDisplay = hitEventsAvailable ? row.Columns - : row.Columns?.Where(c => !c.RequiresHitEvents).ToArray(); + : row.Columns.Where(c => !c.RequiresHitEvents).ToArray(); - if (columnsToDisplay?.Any() ?? false) + if (columnsToDisplay.Any()) panelIsEmpty = false; else continue; @@ -132,8 +132,8 @@ namespace osu.Game.Screens.Ranking.Statistics Origin = Anchor.Centre, }).Cast().ToArray() }, - ColumnDimensions = Enumerable.Range(0, columnsToDisplay?.Length ?? 0) - .Select(i => columnsToDisplay?[i].Dimension ?? new Dimension()).ToArray(), + ColumnDimensions = Enumerable.Range(0, columnsToDisplay.Length) + .Select(i => columnsToDisplay[i].Dimension ?? new Dimension()).ToArray(), RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) } }); } From 042574660c10d6a28c29a81d6f45f5b8547f956a Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Wed, 2 Feb 2022 17:29:03 +0800 Subject: [PATCH 897/996] Rename "Content" to "CreateContent" --- .../Ranking/Statistics/StatisticContainer.cs | 2 +- .../Screens/Ranking/Statistics/StatisticItem.cs | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs b/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs index f3bd2c6fc1..79f813ef64 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs @@ -43,7 +43,7 @@ namespace osu.Game.Screens.Ranking.Statistics RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Margin = new MarginPadding { Top = 15 }, - Child = item.Content() + Child = item.CreateContent() } }, }, diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs b/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs index 5e6ddf445b..cb5ba4b9fe 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs @@ -19,9 +19,9 @@ namespace osu.Game.Screens.Ranking.Statistics public readonly string Name; /// - /// The content to be displayed. + /// A function returning the content to be displayed. /// - public readonly Func Content; + public readonly Func CreateContent; /// /// The of this row. This can be thought of as the column dimension of an encompassing . @@ -29,7 +29,7 @@ namespace osu.Game.Screens.Ranking.Statistics public readonly Dimension Dimension; /// - /// Whether this item requires hit events. If true, the will not be created if no hit events are available. + /// Whether this item requires hit events. If true, will not be called if no hit events are available. /// public readonly bool RequiresHitEvents; @@ -37,14 +37,14 @@ namespace osu.Game.Screens.Ranking.Statistics /// Creates a new , to be displayed inside a in the results screen. /// /// The name of the item. Can be to hide the item header. - /// Whether this item requires hit events. If true, the will not be created if no hit events are available. - /// The content to be displayed. + /// Whether this item requires hit events. If true, will not be called if no hit events are available. + /// A function returning the content to be displayed. /// The of this item. This can be thought of as the column dimension of an encompassing . - public StatisticItem([NotNull] string name, bool requiresHitEvents, [NotNull] Func content, [CanBeNull] Dimension dimension = null) + public StatisticItem([NotNull] string name, bool requiresHitEvents, [NotNull] Func createContent, [CanBeNull] Dimension dimension = null) { Name = name; RequiresHitEvents = requiresHitEvents; - Content = content; + CreateContent = createContent; Dimension = dimension; } } From 36bfef4f54f4f116ec7f84747db2c4c93a883d0d Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Wed, 2 Feb 2022 17:32:16 +0800 Subject: [PATCH 898/996] Dispose container before replacing --- osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index 8b69f83055..2327f60f81 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -143,6 +143,7 @@ namespace osu.Game.Screens.Ranking.Statistics if (panelIsEmpty) { // Replace the scroll container with fill flow container to get the message centered. + container.Dispose(); container = new FillFlowContainer { RelativeSizeAxes = Axes.Both, From b5fb3b7dae02bdbaeec37e535d447bb09fcfbaeb Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 2 Feb 2022 18:42:20 +0900 Subject: [PATCH 899/996] Fix crash when selecting swap mod as freemod --- .../TestSceneMultiplayerMatchSubScreen.cs | 25 +++++++++++++++++++ .../Participants/ParticipantPanel.cs | 8 ++++-- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index 9d14d80d07..869fb17317 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -15,8 +15,12 @@ using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Taiko; +using osu.Game.Rulesets.Taiko.Mods; +using osu.Game.Rulesets.UI; using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.OnlinePlay.Multiplayer.Match; +using osu.Game.Screens.OnlinePlay.Multiplayer.Participants; using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Resources; @@ -77,6 +81,27 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for join", () => RoomJoined); } + [Test] + public void TestTaikoOnlyMod() + { + AddStep("add playlist item", () => + { + SelectedRoom.Value.Playlist.Add(new PlaylistItem + { + Beatmap = { Value = new TestBeatmap(new TaikoRuleset().RulesetInfo).BeatmapInfo }, + Ruleset = { Value = new TaikoRuleset().RulesetInfo }, + AllowedMods = { new TaikoModSwap() } + }); + }); + + ClickButtonWhenEnabled(); + + AddUntilStep("wait for join", () => RoomJoined); + + AddStep("select swap mod", () => Client.ChangeUserMods(API.LocalUser.Value.OnlineID, new[] { new TaikoModSwap() })); + AddUntilStep("participant panel has mod", () => this.ChildrenOfType().Any(p => p.ChildrenOfType().Any(m => m.Mod is TaikoModSwap))); + } + [Test] public void TestSettingValidity() { diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs index 8fbaebadfe..96a665f33d 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; @@ -18,6 +19,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Online; using osu.Game.Online.API; using osu.Game.Online.Multiplayer; +using osu.Game.Online.Rooms; using osu.Game.Rulesets; using osu.Game.Screens.Play.HUD; using osu.Game.Users; @@ -184,8 +186,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants const double fade_time = 50; - // Todo: Should use the room's selected item to determine ruleset. - var ruleset = rulesets.GetRuleset(0)?.CreateInstance(); + var currentItem = Playlist.GetCurrentItem(); + Debug.Assert(currentItem != null); + + var ruleset = rulesets.GetRuleset(currentItem.RulesetID)?.CreateInstance(); int? currentModeRank = ruleset != null ? User.User?.RulesetsStatistics?.GetValueOrDefault(ruleset.ShortName)?.GlobalRank : null; userRankText.Text = currentModeRank != null ? $"#{currentModeRank.Value:N0}" : string.Empty; From b0023b9809d15cdb7f19d5a2f6be9c0d047bf9fc Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Wed, 2 Feb 2022 19:00:46 +0800 Subject: [PATCH 900/996] Also dispose `rows` --- osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index 2327f60f81..62f9eb8506 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -143,6 +143,7 @@ namespace osu.Game.Screens.Ranking.Statistics if (panelIsEmpty) { // Replace the scroll container with fill flow container to get the message centered. + rows.Dispose(); container.Dispose(); container = new FillFlowContainer { From 1e19c7046a71de056cc975deaa045eb2eff46c8b Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Wed, 2 Feb 2022 19:02:29 +0800 Subject: [PATCH 901/996] Use spacing instead of bottom margin --- osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index 62f9eb8506..68414c4d49 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -98,7 +98,8 @@ namespace osu.Game.Screens.Ranking.Statistics rows = new FillFlowContainer { RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(30, 15) } } }; @@ -123,7 +124,6 @@ namespace osu.Game.Screens.Ranking.Statistics Origin = Anchor.TopCentre, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Margin = new MarginPadding { Bottom = 15 }, Content = new[] { columnsToDisplay?.Select(c => new StatisticContainer(c) From 3c2a6fe2086a1eb560a4aefe1805a8d402b1d153 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Wed, 2 Feb 2022 19:07:14 +0800 Subject: [PATCH 902/996] Don't prompt for a replay if no item requires hit events --- osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index 68414c4d49..08be1de652 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -105,6 +105,7 @@ namespace osu.Game.Screens.Ranking.Statistics }; bool panelIsEmpty = true; + bool panelIsComplete = true; bool hitEventsAvailable = newScore.HitEvents.Count != 0; foreach (var row in newScore.Ruleset.CreateInstance().CreateStatisticsForScore(newScore, playableBeatmap)) @@ -113,6 +114,9 @@ namespace osu.Game.Screens.Ranking.Statistics ? row.Columns : row.Columns.Where(c => !c.RequiresHitEvents).ToArray(); + if (columnsToDisplay.Length < row.Columns.Length) + panelIsComplete = false; + if (columnsToDisplay.Any()) panelIsEmpty = false; else @@ -163,7 +167,7 @@ namespace osu.Game.Screens.Ranking.Statistics } }; } - else + else if (!panelIsComplete) { rows.Add(new FillFlowContainer { From 19eb9ad8a7e86b384ca65648b95e35e37c2ce068 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Feb 2022 23:02:38 +0900 Subject: [PATCH 903/996] Reorder `StatisticsItem` constructor to make a touch more sense --- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 20 ++++++------ osu.Game.Rulesets.Osu/OsuRuleset.cs | 31 +++++++++---------- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 20 ++++++------ .../Ranking/Statistics/StatisticItem.cs | 4 +-- 4 files changed, 34 insertions(+), 41 deletions(-) diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 0ea1cd5fc7..ffb26b224f 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -370,23 +370,21 @@ namespace osu.Game.Rulesets.Mania { Columns = new[] { - new StatisticItem("Timing Distribution", true, - () => new HitEventTimingDistributionGraph(score.HitEvents) - { - RelativeSizeAxes = Axes.X, - Height = 250 - }), + new StatisticItem("Timing Distribution", () => new HitEventTimingDistributionGraph(score.HitEvents) + { + RelativeSizeAxes = Axes.X, + Height = 250 + }, true), } }, new StatisticRow { Columns = new[] { - new StatisticItem(string.Empty, true, - () => new SimpleStatisticTable(3, new SimpleStatisticItem[] - { - new UnstableRate(score.HitEvents) - })) + new StatisticItem(string.Empty, () => new SimpleStatisticTable(3, new SimpleStatisticItem[] + { + new UnstableRate(score.HitEvents) + }), true) } } }; diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 2bf47100c1..1122a869b7 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -277,35 +277,32 @@ namespace osu.Game.Rulesets.Osu { Columns = new[] { - new StatisticItem("Timing Distribution", true, - () => new HitEventTimingDistributionGraph(timedHitEvents) - { - RelativeSizeAxes = Axes.X, - Height = 250 - }), + new StatisticItem("Timing Distribution", () => new HitEventTimingDistributionGraph(timedHitEvents) + { + RelativeSizeAxes = Axes.X, + Height = 250 + }, true), } }, new StatisticRow { Columns = new[] { - new StatisticItem("Accuracy Heatmap", true, - () => new AccuracyHeatmap(score, playableBeatmap) - { - RelativeSizeAxes = Axes.X, - Height = 250 - }), + new StatisticItem("Accuracy Heatmap", () => new AccuracyHeatmap(score, playableBeatmap) + { + RelativeSizeAxes = Axes.X, + Height = 250 + }, true), } }, new StatisticRow { Columns = new[] { - new StatisticItem(string.Empty, true, - () => new SimpleStatisticTable(3, new SimpleStatisticItem[] - { - new UnstableRate(timedHitEvents) - })) + new StatisticItem(string.Empty, () => new SimpleStatisticTable(3, new SimpleStatisticItem[] + { + new UnstableRate(timedHitEvents) + }), true) } } }; diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index fe4116b4a6..21c99c0d2f 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -213,23 +213,21 @@ namespace osu.Game.Rulesets.Taiko { Columns = new[] { - new StatisticItem("Timing Distribution", true, - () => new HitEventTimingDistributionGraph(timedHitEvents) - { - RelativeSizeAxes = Axes.X, - Height = 250 - }), + new StatisticItem("Timing Distribution", () => new HitEventTimingDistributionGraph(timedHitEvents) + { + RelativeSizeAxes = Axes.X, + Height = 250 + }, true), } }, new StatisticRow { Columns = new[] { - new StatisticItem(string.Empty, true, - () => new SimpleStatisticTable(3, new SimpleStatisticItem[] - { - new UnstableRate(timedHitEvents) - })) + new StatisticItem(string.Empty, () => new SimpleStatisticTable(3, new SimpleStatisticItem[] + { + new UnstableRate(timedHitEvents) + }), true) } } }; diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs b/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs index cb5ba4b9fe..3cfc38e263 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs @@ -37,10 +37,10 @@ namespace osu.Game.Screens.Ranking.Statistics /// Creates a new , to be displayed inside a in the results screen. /// /// The name of the item. Can be to hide the item header. - /// Whether this item requires hit events. If true, will not be called if no hit events are available. /// A function returning the content to be displayed. + /// Whether this item requires hit events. If true, will not be called if no hit events are available. /// The of this item. This can be thought of as the column dimension of an encompassing . - public StatisticItem([NotNull] string name, bool requiresHitEvents, [NotNull] Func createContent, [CanBeNull] Dimension dimension = null) + public StatisticItem([NotNull] string name, [NotNull] Func createContent, bool requiresHitEvents = false, [CanBeNull] Dimension dimension = null) { Name = name; RequiresHitEvents = requiresHitEvents; From 9d1d13c7152592d31a212fc04b04aac1fac171b1 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 2 Feb 2022 23:00:43 +0900 Subject: [PATCH 904/996] Fix up TestSpectatorClient implementation Rather than using a list which is supposed to be updated "client"-side, now uses the "server"-side list. --- .../Visual/Spectator/TestSpectatorClient.cs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs index 7848a825f4..6862cda88c 100644 --- a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs +++ b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs @@ -39,11 +39,7 @@ namespace osu.Game.Tests.Visual.Spectator public TestSpectatorClient() { - OnNewFrames += (i, bundle) => - { - if (PlayingUserStates.ContainsKey(i)) - lastReceivedUserFrames[i] = bundle.Frames[^1]; - }; + OnNewFrames += (i, bundle) => lastReceivedUserFrames[i] = bundle.Frames[^1]; } /// @@ -62,16 +58,20 @@ namespace osu.Game.Tests.Visual.Spectator /// Ends play for an arbitrary user. /// /// The user to end play for. - public void EndPlay(int userId) + /// The spectator state to end play with. + public void EndPlay(int userId, SpectatingUserState state = SpectatingUserState.Quit) { - if (!PlayingUserStates.ContainsKey(userId)) + if (!userBeatmapDictionary.ContainsKey(userId)) return; ((ISpectatorClient)this).UserFinishedPlaying(userId, new SpectatorState { BeatmapID = userBeatmapDictionary[userId], RulesetID = 0, + State = state }); + + userBeatmapDictionary.Remove(userId); } public new void Schedule(Action action) => base.Schedule(action); @@ -130,7 +130,7 @@ namespace osu.Game.Tests.Visual.Spectator protected override Task WatchUserInternal(int userId) { // When newly watching a user, the server sends the playing state immediately. - if (PlayingUserStates.ContainsKey(userId)) + if (userBeatmapDictionary.ContainsKey(userId)) sendPlayingState(userId); return Task.CompletedTask; @@ -144,6 +144,7 @@ namespace osu.Game.Tests.Visual.Spectator { BeatmapID = userBeatmapDictionary[userId], RulesetID = 0, + State = SpectatingUserState.Playing }); } } From 589f5e7a31f53275f7cd001adbe4deedb1e18473 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 2 Feb 2022 23:09:38 +0900 Subject: [PATCH 905/996] Update test which has now been resolved --- osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index d28e5203e3..9f8470446c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -155,11 +155,12 @@ namespace osu.Game.Tests.Visual.Gameplay waitForPlayer(); checkPaused(true); + sendFrames(); - finish(); + finish(SpectatingUserState.Failed); - checkPaused(false); - // TODO: should replay until running out of frames then fail + checkPaused(false); // Should continue playing until out of frames + checkPaused(true); } [Test] @@ -246,7 +247,7 @@ namespace osu.Game.Tests.Visual.Gameplay private void start(int? beatmapId = null) => AddStep("start play", () => spectatorClient.StartPlay(streamingUser.Id, beatmapId ?? importedBeatmapId)); - private void finish() => AddStep("end play", () => spectatorClient.EndPlay(streamingUser.Id)); + private void finish(SpectatingUserState state = SpectatingUserState.Quit) => AddStep("end play", () => spectatorClient.EndPlay(streamingUser.Id, state)); private void checkPaused(bool state) => AddUntilStep($"game is {(state ? "paused" : "playing")}", () => player.ChildrenOfType().First().IsPaused.Value == state); From fcbba3d9481ca396acd9885593065ea782362475 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 2 Feb 2022 23:11:29 +0900 Subject: [PATCH 906/996] Rename PlayingUserStates -> WatchingUserStates --- .../Visual/Gameplay/TestSceneSpectatorHost.cs | 4 ++-- .../TestSceneMultiplayerGameplayLeaderboard.cs | 2 +- osu.Game/Online/Spectator/SpectatorClient.cs | 12 ++++++------ .../Overlays/Dashboard/CurrentlyPlayingDisplay.cs | 2 +- osu.Game/Screens/Spectate/SpectatorScreen.cs | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorHost.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorHost.cs index 409cec4cf6..6d6b0bf89e 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorHost.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorHost.cs @@ -37,8 +37,8 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestClientSendsCorrectRuleset() { - AddUntilStep("spectator client sending frames", () => spectatorClient.PlayingUserStates.ContainsKey(dummy_user_id)); - AddAssert("spectator client sent correct ruleset", () => spectatorClient.PlayingUserStates[dummy_user_id].RulesetID == Ruleset.Value.OnlineID); + AddUntilStep("spectator client sending frames", () => spectatorClient.WatchingUserStates.ContainsKey(dummy_user_id)); + AddAssert("spectator client sent correct ruleset", () => spectatorClient.WatchingUserStates[dummy_user_id].RulesetID == Ruleset.Value.OnlineID); } public override void TearDownSteps() diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs index 8b71dfac21..55450b36e2 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs @@ -115,7 +115,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public void RandomlyUpdateState() { - foreach ((int userId, _) in PlayingUserStates) + foreach ((int userId, _) in WatchingUserStates) { if (RNG.NextBool()) continue; diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index 156d544960..3646c51d94 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -37,8 +37,8 @@ namespace osu.Game.Online.Spectator private readonly List watchingUsers = new List(); - public IBindableDictionary PlayingUserStates => playingUserStates; - private readonly BindableDictionary playingUserStates = new BindableDictionary(); + public IBindableDictionary WatchingUserStates => watchingUserStates; + private readonly BindableDictionary watchingUserStates = new BindableDictionary(); private IBeatmap? currentBeatmap; private Score? currentScore; @@ -85,7 +85,7 @@ namespace osu.Game.Online.Spectator BeginPlayingInternal(currentState); } else - playingUserStates.Clear(); + watchingUserStates.Clear(); }), true); } @@ -94,7 +94,7 @@ namespace osu.Game.Online.Spectator Schedule(() => { if (watchingUsers.Contains(userId)) - playingUserStates[userId] = state; + watchingUserStates[userId] = state; OnUserBeganPlaying?.Invoke(userId, state); }); @@ -106,7 +106,7 @@ namespace osu.Game.Online.Spectator Schedule(() => { if (watchingUsers.Contains(userId)) - playingUserStates[userId] = state; + watchingUserStates[userId] = state; OnUserFinishedPlaying?.Invoke(userId, state); }); @@ -193,7 +193,7 @@ namespace osu.Game.Online.Spectator Schedule(() => { watchingUsers.Remove(userId); - playingUserStates.Remove(userId); + watchingUserStates.Remove(userId); StopWatchingUserInternal(userId); }); } diff --git a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs index 383f17d8d2..975402a443 100644 --- a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs +++ b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs @@ -51,7 +51,7 @@ namespace osu.Game.Overlays.Dashboard { base.LoadComplete(); - userStates.BindTo(spectatorClient.PlayingUserStates); + userStates.BindTo(spectatorClient.WatchingUserStates); userStates.BindCollectionChanged(onUserStatesChanged, true); } diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs index 783ba494eb..0b2a7cecf6 100644 --- a/osu.Game/Screens/Spectate/SpectatorScreen.cs +++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs @@ -77,7 +77,7 @@ namespace osu.Game.Screens.Spectate userMap[u.Id] = u; } - userStates.BindTo(spectatorClient.PlayingUserStates); + userStates.BindTo(spectatorClient.WatchingUserStates); userStates.BindCollectionChanged(onUserStatesChanged, true); realmSubscription = realm.RegisterForNotifications( From 81a22dbd29543b3ff5387669bd6ce1fc048a4811 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 2 Feb 2022 23:19:43 +0900 Subject: [PATCH 907/996] Add back playing users list --- osu.Game/Online/Spectator/SpectatorClient.cs | 28 +++++++++-- .../Dashboard/CurrentlyPlayingDisplay.cs | 48 ++++++++----------- 2 files changed, 45 insertions(+), 31 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index 3646c51d94..9e168411b0 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -35,16 +35,28 @@ namespace osu.Game.Online.Spectator /// public abstract IBindable IsConnected { get; } + /// + /// The states of all users currently being watched. + /// + public IBindableDictionary WatchingUserStates => watchingUserStates; + + /// + /// A global list of all players currently playing. + /// + public IBindableList PlayingUsers => playingUsers; + + /// + /// All users currently being watched. + /// private readonly List watchingUsers = new List(); - public IBindableDictionary WatchingUserStates => watchingUserStates; private readonly BindableDictionary watchingUserStates = new BindableDictionary(); + private readonly BindableList playingUsers = new BindableList(); + private readonly SpectatorState currentState = new SpectatorState(); private IBeatmap? currentBeatmap; private Score? currentScore; - private readonly SpectatorState currentState = new SpectatorState(); - /// /// Whether the local user is playing. /// @@ -85,7 +97,10 @@ namespace osu.Game.Online.Spectator BeginPlayingInternal(currentState); } else + { watchingUserStates.Clear(); + playingUsers.Clear(); + } }), true); } @@ -93,8 +108,12 @@ namespace osu.Game.Online.Spectator { Schedule(() => { + if (!playingUsers.Contains(userId)) + playingUsers.Add(userId); + if (watchingUsers.Contains(userId)) watchingUserStates[userId] = state; + OnUserBeganPlaying?.Invoke(userId, state); }); @@ -105,8 +124,11 @@ namespace osu.Game.Online.Spectator { Schedule(() => { + playingUsers.Remove(userId); + if (watchingUsers.Contains(userId)) watchingUserStates[userId] = state; + OnUserFinishedPlaying?.Invoke(userId, state); }); diff --git a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs index 975402a443..02ef28f825 100644 --- a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs +++ b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.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.Collections.Specialized; using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; @@ -22,7 +23,7 @@ namespace osu.Game.Overlays.Dashboard { internal class CurrentlyPlayingDisplay : CompositeDrawable { - private readonly IBindableDictionary userStates = new BindableDictionary(); + private readonly IBindableList playingUsers = new BindableList(); private FillFlowContainer userFlow; @@ -51,55 +52,46 @@ namespace osu.Game.Overlays.Dashboard { base.LoadComplete(); - userStates.BindTo(spectatorClient.WatchingUserStates); - userStates.BindCollectionChanged(onUserStatesChanged, true); + playingUsers.BindTo(spectatorClient.PlayingUsers); + playingUsers.BindCollectionChanged(onPlayingUsersChanged, true); } - private void onUserStatesChanged(object sender, NotifyDictionaryChangedEventArgs e) => Schedule(() => + private void onPlayingUsersChanged(object sender, NotifyCollectionChangedEventArgs e) => Schedule(() => { switch (e.Action) { - case NotifyDictionaryChangedAction.Add: - case NotifyDictionaryChangedAction.Replace: + case NotifyCollectionChangedAction.Add: Debug.Assert(e.NewItems != null); - foreach ((int userId, SpectatorState state) in e.NewItems) + foreach (int userId in e.NewItems) { - if (state.State != SpectatingUserState.Playing) - { - removePlayingUser(userId); - continue; - } - users.GetUserAsync(userId).ContinueWith(task => { var user = task.GetResultSafely(); if (user != null) - Schedule(() => addPlayingUser(user)); + { + Schedule(() => + { + // user may no longer be playing. + if (!playingUsers.Contains(user.Id)) + return; + + userFlow.Add(createUserPanel(user)); + }); + } }); } break; - case NotifyDictionaryChangedAction.Remove: + case NotifyCollectionChangedAction.Remove: Debug.Assert(e.OldItems != null); - foreach ((int userId, _) in e.OldItems) - removePlayingUser(userId); + foreach (int userId in e.OldItems) + userFlow.FirstOrDefault(card => card.User.Id == userId)?.Expire(); break; } - - void addPlayingUser(APIUser user) - { - // user may no longer be playing. - if (!userStates.TryGetValue(user.Id, out var state2) || state2.State != SpectatingUserState.Playing) - return; - - userFlow.Add(createUserPanel(user)); - } - - void removePlayingUser(int userId) => userFlow.FirstOrDefault(card => card.User.Id == userId)?.Expire(); }); private PlayingUserPanel createUserPanel(APIUser user) => From 074a69163558c6ee6c2b177e97ed53126ab8b805 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 2 Feb 2022 17:16:35 +0300 Subject: [PATCH 908/996] Set keyboard step to `0.1` for difficulty adjust sliders --- osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs index 45873a321a..c8e7284f5d 100644 --- a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs +++ b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs @@ -105,6 +105,7 @@ namespace osu.Game.Rulesets.Mods { ShowsDefaultIndicator = false, Current = currentNumber, + KeyboardStep = 0.1f, } }; From 74637444070838cc55bd0d7e04f9580f13552737 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 2 Feb 2022 19:17:33 +0100 Subject: [PATCH 909/996] Fix osu! autoplay-like mods not declaring incompatibility with `AimAssist` --- osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModCinema.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 106edfb623..2668013321 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.Append(typeof(OsuModAutopilot)).Append(typeof(OsuModSpunOut)).ToArray(); + public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAimAssist), typeof(OsuModAutopilot), typeof(OsuModSpunOut) }).ToArray(); public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => new Score { diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs b/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs index f478790134..ff31cfcd18 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Osu.Mods { public class OsuModCinema : ModCinema { - public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).Append(typeof(OsuModSpunOut)).ToArray(); + public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAimAssist), typeof(OsuModAutopilot), typeof(OsuModSpunOut) }).ToArray(); public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => new Score { From dca1bddabb789867e34562e20bba19d02e22fab1 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 2 Feb 2022 21:25:50 +0300 Subject: [PATCH 910/996] Lock supported interface orientation to landscape for iPhone --- osu.Game.Rulesets.Catch.Tests.iOS/Info.plist | 5 +++++ osu.Game.Rulesets.Mania.Tests.iOS/Info.plist | 5 +++++ osu.Game.Rulesets.Osu.Tests.iOS/Info.plist | 5 +++++ osu.Game.Rulesets.Taiko.Tests.iOS/Info.plist | 5 +++++ osu.Game.Tests.iOS/Info.plist | 5 +++++ osu.iOS/Info.plist | 5 +++++ 6 files changed, 30 insertions(+) diff --git a/osu.Game.Rulesets.Catch.Tests.iOS/Info.plist b/osu.Game.Rulesets.Catch.Tests.iOS/Info.plist index 3ba1886d98..88317b6393 100644 --- a/osu.Game.Rulesets.Catch.Tests.iOS/Info.plist +++ b/osu.Game.Rulesets.Catch.Tests.iOS/Info.plist @@ -24,6 +24,11 @@ armv7 UISupportedInterfaceOrientations + + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown diff --git a/osu.Game.Rulesets.Mania.Tests.iOS/Info.plist b/osu.Game.Rulesets.Mania.Tests.iOS/Info.plist index 09ed2dd007..8d05c2bb73 100644 --- a/osu.Game.Rulesets.Mania.Tests.iOS/Info.plist +++ b/osu.Game.Rulesets.Mania.Tests.iOS/Info.plist @@ -24,6 +24,11 @@ armv7 UISupportedInterfaceOrientations + + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown diff --git a/osu.Game.Rulesets.Osu.Tests.iOS/Info.plist b/osu.Game.Rulesets.Osu.Tests.iOS/Info.plist index dd032ef1c1..63ad313a70 100644 --- a/osu.Game.Rulesets.Osu.Tests.iOS/Info.plist +++ b/osu.Game.Rulesets.Osu.Tests.iOS/Info.plist @@ -24,6 +24,11 @@ armv7 UISupportedInterfaceOrientations + + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown diff --git a/osu.Game.Rulesets.Taiko.Tests.iOS/Info.plist b/osu.Game.Rulesets.Taiko.Tests.iOS/Info.plist index ac658cd14e..3c76622ee3 100644 --- a/osu.Game.Rulesets.Taiko.Tests.iOS/Info.plist +++ b/osu.Game.Rulesets.Taiko.Tests.iOS/Info.plist @@ -24,6 +24,11 @@ armv7 UISupportedInterfaceOrientations + + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown diff --git a/osu.Game.Tests.iOS/Info.plist b/osu.Game.Tests.iOS/Info.plist index 1a89345bc5..835e958bc6 100644 --- a/osu.Game.Tests.iOS/Info.plist +++ b/osu.Game.Tests.iOS/Info.plist @@ -24,6 +24,11 @@ armv7 UISupportedInterfaceOrientations + + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown diff --git a/osu.iOS/Info.plist b/osu.iOS/Info.plist index 2592f909ce..1156c0954c 100644 --- a/osu.iOS/Info.plist +++ b/osu.iOS/Info.plist @@ -40,6 +40,11 @@ NSMicrophoneUsageDescription We don't really use the microphone. UISupportedInterfaceOrientations + + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown From 4aa4df69f258bd4027c8c90abba8adaa6a3a7b28 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 2 Feb 2022 22:17:23 +0300 Subject: [PATCH 911/996] Reorder iOS landscape orientations to prioritise "Landscape Right" "Landscape Right" is often the proper default for landscape-only applications. Matches up with all other landscape-only iOS games I have locally. --- osu.Game.Rulesets.Catch.Tests.iOS/Info.plist | 4 ++-- osu.Game.Rulesets.Mania.Tests.iOS/Info.plist | 4 ++-- osu.Game.Rulesets.Osu.Tests.iOS/Info.plist | 4 ++-- osu.Game.Rulesets.Taiko.Tests.iOS/Info.plist | 4 ++-- osu.Game.Tests.iOS/Info.plist | 4 ++-- osu.iOS/Info.plist | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests.iOS/Info.plist b/osu.Game.Rulesets.Catch.Tests.iOS/Info.plist index 88317b6393..33ddac6dfb 100644 --- a/osu.Game.Rulesets.Catch.Tests.iOS/Info.plist +++ b/osu.Game.Rulesets.Catch.Tests.iOS/Info.plist @@ -25,15 +25,15 @@ UISupportedInterfaceOrientations - UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + UIInterfaceOrientationLandscapeLeft UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + UIInterfaceOrientationLandscapeLeft XSAppIconAssets Assets.xcassets/AppIcon.appiconset diff --git a/osu.Game.Rulesets.Mania.Tests.iOS/Info.plist b/osu.Game.Rulesets.Mania.Tests.iOS/Info.plist index 8d05c2bb73..78349334b4 100644 --- a/osu.Game.Rulesets.Mania.Tests.iOS/Info.plist +++ b/osu.Game.Rulesets.Mania.Tests.iOS/Info.plist @@ -25,15 +25,15 @@ UISupportedInterfaceOrientations - UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + UIInterfaceOrientationLandscapeLeft UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + UIInterfaceOrientationLandscapeLeft XSAppIconAssets Assets.xcassets/AppIcon.appiconset diff --git a/osu.Game.Rulesets.Osu.Tests.iOS/Info.plist b/osu.Game.Rulesets.Osu.Tests.iOS/Info.plist index 63ad313a70..b9f371c049 100644 --- a/osu.Game.Rulesets.Osu.Tests.iOS/Info.plist +++ b/osu.Game.Rulesets.Osu.Tests.iOS/Info.plist @@ -25,15 +25,15 @@ UISupportedInterfaceOrientations - UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + UIInterfaceOrientationLandscapeLeft UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + UIInterfaceOrientationLandscapeLeft XSAppIconAssets Assets.xcassets/AppIcon.appiconset diff --git a/osu.Game.Rulesets.Taiko.Tests.iOS/Info.plist b/osu.Game.Rulesets.Taiko.Tests.iOS/Info.plist index 3c76622ee3..65c47d2115 100644 --- a/osu.Game.Rulesets.Taiko.Tests.iOS/Info.plist +++ b/osu.Game.Rulesets.Taiko.Tests.iOS/Info.plist @@ -25,15 +25,15 @@ UISupportedInterfaceOrientations - UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + UIInterfaceOrientationLandscapeLeft UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + UIInterfaceOrientationLandscapeLeft XSAppIconAssets Assets.xcassets/AppIcon.appiconset diff --git a/osu.Game.Tests.iOS/Info.plist b/osu.Game.Tests.iOS/Info.plist index 835e958bc6..ed0c2e4dbf 100644 --- a/osu.Game.Tests.iOS/Info.plist +++ b/osu.Game.Tests.iOS/Info.plist @@ -25,15 +25,15 @@ UISupportedInterfaceOrientations - UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + UIInterfaceOrientationLandscapeLeft UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + UIInterfaceOrientationLandscapeLeft XSAppIconAssets Assets.xcassets/AppIcon.appiconset diff --git a/osu.iOS/Info.plist b/osu.iOS/Info.plist index 1156c0954c..02968b87a7 100644 --- a/osu.iOS/Info.plist +++ b/osu.iOS/Info.plist @@ -41,15 +41,15 @@ We don't really use the microphone. UISupportedInterfaceOrientations - UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + UIInterfaceOrientationLandscapeLeft UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + UIInterfaceOrientationLandscapeLeft XSAppIconAssets Assets.xcassets/AppIcon.appiconset From 82f9ad63f514f1220c44bb087d58a154a1e1f2c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 2 Feb 2022 20:41:25 +0100 Subject: [PATCH 912/996] Fix flashlight size multiplier printing with too many decimal digits --- osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs | 2 +- osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs | 2 +- osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs | 2 +- osu.Game/Rulesets/Mods/ModFlashlight.cs | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs index 2d92c925d7..d576ea3df8 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Catch.Mods public override double ScoreMultiplier => 1.12; [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")] - public override BindableNumber SizeMultiplier { get; } = new BindableNumber + public override BindableFloat SizeMultiplier { get; } = new BindableFloat { MinValue = 0.5f, MaxValue = 1.5f, diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs index 1ee4ea12e3..8ef5bfd94c 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Mania.Mods public override Type[] IncompatibleMods => new[] { typeof(ModHidden) }; [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")] - public override BindableNumber SizeMultiplier { get; } = new BindableNumber + public override BindableFloat SizeMultiplier { get; } = new BindableFloat { MinValue = 0.5f, MaxValue = 3f, diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs index b4eff57c55..38c84be295 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.Mods }; [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")] - public override BindableNumber SizeMultiplier { get; } = new BindableNumber + public override BindableFloat SizeMultiplier { get; } = new BindableFloat { MinValue = 0.5f, MaxValue = 2f, diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs index fb07c687bb..beec785fe8 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Taiko.Mods public override double ScoreMultiplier => 1.12; [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")] - public override BindableNumber SizeMultiplier { get; } = new BindableNumber + public override BindableFloat SizeMultiplier { get; } = new BindableFloat { MinValue = 0.5f, MaxValue = 1.5f, diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index e6487c6b29..b449f3f64d 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Mods public override string Description => "Restricted view area."; [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")] - public abstract BindableNumber SizeMultiplier { get; } + public abstract BindableFloat SizeMultiplier { get; } [SettingSource("Change size based on combo", "Decrease the flashlight size as combo increases.")] public abstract BindableBool ComboBasedSize { get; } From e2fcdc394b25cbeb0af8a74c4e68bd79f722f8c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 23 Jan 2022 16:29:00 +0100 Subject: [PATCH 913/996] Extract method for difficulty switch menu creation --- osu.Game/Screens/Edit/Editor.cs | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 2fead84deb..71a19c4425 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -806,6 +806,15 @@ namespace osu.Game.Screens.Edit fileMenuItems.Add(new EditorMenuItemSpacer()); + fileMenuItems.Add(createDifficultySwitchMenu()); + + fileMenuItems.Add(new EditorMenuItemSpacer()); + fileMenuItems.Add(new EditorMenuItem("Exit", MenuItemType.Standard, this.Exit)); + return fileMenuItems; + } + + private EditorMenuItem createDifficultySwitchMenu() + { var beatmapSet = playableBeatmap.BeatmapInfo.BeatmapSet; Debug.Assert(beatmapSet != null); @@ -818,20 +827,13 @@ namespace osu.Game.Screens.Edit difficultyItems.Add(new EditorMenuItemSpacer()); foreach (var beatmap in rulesetBeatmaps.OrderBy(b => b.StarRating)) - difficultyItems.Add(createDifficultyMenuItem(beatmap)); + { + bool isCurrentDifficulty = playableBeatmap.BeatmapInfo.Equals(beatmap); + difficultyItems.Add(new DifficultyMenuItem(beatmap, isCurrentDifficulty, SwitchToDifficulty)); + } } - fileMenuItems.Add(new EditorMenuItem("Change difficulty") { Items = difficultyItems }); - - fileMenuItems.Add(new EditorMenuItemSpacer()); - fileMenuItems.Add(new EditorMenuItem("Exit", MenuItemType.Standard, this.Exit)); - return fileMenuItems; - } - - private DifficultyMenuItem createDifficultyMenuItem(BeatmapInfo beatmapInfo) - { - bool isCurrentDifficulty = playableBeatmap.BeatmapInfo.Equals(beatmapInfo); - return new DifficultyMenuItem(beatmapInfo, isCurrentDifficulty, SwitchToDifficulty); + return new EditorMenuItem("Change difficulty") { Items = difficultyItems }; } protected void SwitchToDifficulty(BeatmapInfo nextBeatmap) => loader?.ScheduleDifficultySwitch(nextBeatmap, GetState(nextBeatmap)); From 3386f038ba0857f47934c438d7d8a7024e06285c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 23 Jan 2022 16:34:02 +0100 Subject: [PATCH 914/996] Add new difficulty creation menu --- osu.Game/Screens/Edit/Editor.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 71a19c4425..61c5fd2ca4 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -77,6 +77,9 @@ namespace osu.Game.Screens.Edit [Resolved] private BeatmapManager beatmapManager { get; set; } + [Resolved] + private RulesetStore rulesets { get; set; } + [Resolved] private Storage storage { get; set; } @@ -806,6 +809,7 @@ namespace osu.Game.Screens.Edit fileMenuItems.Add(new EditorMenuItemSpacer()); + fileMenuItems.Add(createDifficultyCreationMenu()); fileMenuItems.Add(createDifficultySwitchMenu()); fileMenuItems.Add(new EditorMenuItemSpacer()); @@ -813,6 +817,16 @@ namespace osu.Game.Screens.Edit return fileMenuItems; } + private EditorMenuItem createDifficultyCreationMenu() + { + var rulesetItems = new List(); + + foreach (var ruleset in rulesets.AvailableRulesets.OrderBy(ruleset => ruleset.OnlineID)) + rulesetItems.Add(new EditorMenuItem(ruleset.Name)); + + return new EditorMenuItem("Create new difficulty") { Items = rulesetItems }; + } + private EditorMenuItem createDifficultySwitchMenu() { var beatmapSet = playableBeatmap.BeatmapInfo.BeatmapSet; From b613aedeb81ea46bcb664b9b76feac179252778f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 23 Jan 2022 16:36:18 +0100 Subject: [PATCH 915/996] Fix menu item width changing when hovered --- osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs b/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs index 4267b82bb7..4ecc543ffd 100644 --- a/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs +++ b/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs @@ -117,6 +117,7 @@ namespace osu.Game.Graphics.UserInterface { NormalText = new OsuSpriteText { + AlwaysPresent = true, // ensures that the menu item does not change width when switching between normal and bold text. Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Font = OsuFont.GetFont(size: text_size), @@ -124,7 +125,7 @@ namespace osu.Game.Graphics.UserInterface }, BoldText = new OsuSpriteText { - AlwaysPresent = true, + AlwaysPresent = true, // ensures that the menu item does not change width when switching between normal and bold text. Alpha = 0, Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, From dc96c4888bfa5fa04ddd4228b9bf218d206a71c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 23 Jan 2022 17:49:17 +0100 Subject: [PATCH 916/996] Add support for creating new blank difficulties --- osu.Game/Beatmaps/BeatmapManager.cs | 32 +++++++++++++++++++++- osu.Game/Beatmaps/BeatmapMetadata.cs | 16 ++++++++++- osu.Game/Beatmaps/BeatmapModelManager.cs | 23 ++++++++++++++-- osu.Game/Database/RealmObjectExtensions.cs | 11 +++++++- osu.Game/Models/RealmUser.cs | 6 +++- osu.Game/Screens/Edit/Editor.cs | 7 +++-- osu.Game/Screens/Edit/EditorLoader.cs | 11 ++++++-- 7 files changed, 95 insertions(+), 11 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index e4fdb3d471..38ba244f28 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -73,7 +73,9 @@ namespace osu.Game.Beatmaps new BeatmapModelManager(realm, storage, onlineLookupQueue); /// - /// Create a new . + /// Create a new beatmap set, backed by a model, + /// with a single difficulty which is backed by a model + /// and represented by the returned usable . /// public WorkingBeatmap CreateNew(RulesetInfo ruleset, APIUser user) { @@ -105,6 +107,34 @@ namespace osu.Game.Beatmaps return imported.PerformRead(s => GetWorkingBeatmap(s.Beatmaps.First())); } + /// + /// Add a new difficulty to the beatmap set represented by the provided . + /// The new difficulty will be backed by a model + /// and represented by the returned . + /// + public WorkingBeatmap CreateNewBlankDifficulty(BeatmapSetInfo beatmapSetInfo, RulesetInfo rulesetInfo) + { + // fetch one of the existing difficulties to copy timing points and metadata from, + // so that the user doesn't have to fill all of that out again. + // this silently assumes that all difficulties have the same timing points and metadata, + // but cases where this isn't true seem rather rare / pathological. + var referenceBeatmap = GetWorkingBeatmap(beatmapSetInfo.Beatmaps.First()); + + var newBeatmap = new Beatmap + { + BeatmapInfo = new BeatmapInfo(rulesetInfo, new BeatmapDifficulty(), referenceBeatmap.Metadata.DeepClone()) + }; + + foreach (var timingPoint in referenceBeatmap.Beatmap.ControlPointInfo.TimingPoints) + newBeatmap.ControlPointInfo.Add(timingPoint.Time, timingPoint.DeepClone()); + + var createdBeatmapInfo = beatmapModelManager.AddDifficultyToBeatmapSet(beatmapSetInfo, newBeatmap); + return GetWorkingBeatmap(createdBeatmapInfo); + } + + // TODO: add back support for making a copy of another difficulty + // (likely via a separate `CopyDifficulty()` method). + /// /// Delete a beatmap difficulty. /// diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs index f6666a6ea9..3a24c4808f 100644 --- a/osu.Game/Beatmaps/BeatmapMetadata.cs +++ b/osu.Game/Beatmaps/BeatmapMetadata.cs @@ -7,6 +7,7 @@ using Newtonsoft.Json; using osu.Framework.Testing; using osu.Game.Models; using osu.Game.Users; +using osu.Game.Utils; using Realms; #nullable enable @@ -16,7 +17,7 @@ namespace osu.Game.Beatmaps [ExcludeFromDynamicCompile] [Serializable] [MapTo("BeatmapMetadata")] - public class BeatmapMetadata : RealmObject, IBeatmapMetadataInfo + public class BeatmapMetadata : RealmObject, IBeatmapMetadataInfo, IDeepCloneable { public string Title { get; set; } = string.Empty; @@ -57,5 +58,18 @@ namespace osu.Game.Beatmaps IUser IBeatmapMetadataInfo.Author => Author; public override string ToString() => this.GetDisplayTitle(); + + public BeatmapMetadata DeepClone() => new BeatmapMetadata(Author.DeepClone()) + { + Title = Title, + TitleUnicode = TitleUnicode, + Artist = Artist, + ArtistUnicode = ArtistUnicode, + Source = Source, + Tags = Tags, + PreviewTime = PreviewTime, + AudioFile = AudioFile, + BackgroundFile = BackgroundFile + }; } } diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs index e8104f2ecb..2ab5ac1db9 100644 --- a/osu.Game/Beatmaps/BeatmapModelManager.cs +++ b/osu.Game/Beatmaps/BeatmapModelManager.cs @@ -49,7 +49,6 @@ namespace osu.Game.Beatmaps public virtual void Save(BeatmapInfo beatmapInfo, IBeatmap beatmapContent, ISkin? beatmapSkin = null) { var setInfo = beatmapInfo.BeatmapSet; - Debug.Assert(setInfo != null); // Difficulty settings must be copied first due to the clone in `Beatmap<>.BeatmapInfo_Set`. @@ -85,6 +84,24 @@ namespace osu.Game.Beatmaps WorkingBeatmapCache?.Invalidate(beatmapInfo); } + /// + /// Add a new difficulty to the beatmap set represented by the provided . + /// + public BeatmapInfo AddDifficultyToBeatmapSet(BeatmapSetInfo beatmapSetInfo, Beatmap beatmap) + { + return Realm.Run(realm => + { + var beatmapInfo = beatmap.BeatmapInfo; + + beatmapSetInfo.Beatmaps.Add(beatmapInfo); + beatmapInfo.BeatmapSet = beatmapSetInfo; + + Save(beatmapInfo, beatmap); + + return beatmapInfo.Detach(); + }); + } + private static string getFilename(BeatmapInfo beatmapInfo) { var metadata = beatmapInfo.Metadata; @@ -103,9 +120,9 @@ namespace osu.Game.Beatmaps public void Update(BeatmapSetInfo item) { - Realm.Write(realm => + Realm.Write(r => { - var existing = realm.Find(item.ID); + var existing = r.Find(item.ID); item.CopyChangesToRealm(existing); }); } diff --git a/osu.Game/Database/RealmObjectExtensions.cs b/osu.Game/Database/RealmObjectExtensions.cs index 7a0ca2c85a..f89bbbe19d 100644 --- a/osu.Game/Database/RealmObjectExtensions.cs +++ b/osu.Game/Database/RealmObjectExtensions.cs @@ -58,7 +58,16 @@ namespace osu.Game.Database if (existing != null) copyChangesToRealm(beatmap, existing); else - d.Beatmaps.Add(beatmap); + { + var newBeatmap = new BeatmapInfo + { + ID = beatmap.ID, + BeatmapSet = d, + Ruleset = d.Realm.Find(beatmap.Ruleset.ShortName) + }; + d.Beatmaps.Add(newBeatmap); + copyChangesToRealm(beatmap, newBeatmap); + } } }); diff --git a/osu.Game/Models/RealmUser.cs b/osu.Game/Models/RealmUser.cs index 5fccff597c..18c849cf0a 100644 --- a/osu.Game/Models/RealmUser.cs +++ b/osu.Game/Models/RealmUser.cs @@ -2,12 +2,14 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Game.Database; using osu.Game.Users; +using osu.Game.Utils; using Realms; namespace osu.Game.Models { - public class RealmUser : EmbeddedObject, IUser, IEquatable + public class RealmUser : EmbeddedObject, IUser, IEquatable, IDeepCloneable { public int OnlineID { get; set; } = 1; @@ -22,5 +24,7 @@ namespace osu.Game.Models return OnlineID == other.OnlineID && Username == other.Username; } + + public RealmUser DeepClone() => (RealmUser)this.Detach().MemberwiseClone(); } } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 61c5fd2ca4..df8e326c5b 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -822,11 +822,14 @@ namespace osu.Game.Screens.Edit var rulesetItems = new List(); foreach (var ruleset in rulesets.AvailableRulesets.OrderBy(ruleset => ruleset.OnlineID)) - rulesetItems.Add(new EditorMenuItem(ruleset.Name)); + rulesetItems.Add(new EditorMenuItem(ruleset.Name, MenuItemType.Standard, () => createNewDifficulty(ruleset))); return new EditorMenuItem("Create new difficulty") { Items = rulesetItems }; } + private void createNewDifficulty(RulesetInfo rulesetInfo) + => loader?.ScheduleSwitchToNewDifficulty(editorBeatmap.BeatmapInfo.BeatmapSet, rulesetInfo, GetState()); + private EditorMenuItem createDifficultySwitchMenu() { var beatmapSet = playableBeatmap.BeatmapInfo.BeatmapSet; @@ -850,7 +853,7 @@ namespace osu.Game.Screens.Edit return new EditorMenuItem("Change difficulty") { Items = difficultyItems }; } - protected void SwitchToDifficulty(BeatmapInfo nextBeatmap) => loader?.ScheduleDifficultySwitch(nextBeatmap, GetState(nextBeatmap)); + protected void SwitchToDifficulty(BeatmapInfo nextBeatmap) => loader?.ScheduleSwitchToExistingDifficulty(nextBeatmap, GetState(nextBeatmap)); private void cancelExit() { diff --git a/osu.Game/Screens/Edit/EditorLoader.cs b/osu.Game/Screens/Edit/EditorLoader.cs index 15d70e28b6..731bc75b52 100644 --- a/osu.Game/Screens/Edit/EditorLoader.cs +++ b/osu.Game/Screens/Edit/EditorLoader.cs @@ -10,6 +10,7 @@ using osu.Framework.Screens; using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Screens.Menu; using osu.Game.Screens.Play; @@ -78,7 +79,13 @@ namespace osu.Game.Screens.Edit } } - public void ScheduleDifficultySwitch(BeatmapInfo nextBeatmap, EditorState editorState) + public void ScheduleSwitchToNewDifficulty(BeatmapSetInfo beatmapSetInfo, RulesetInfo rulesetInfo, EditorState editorState) + => scheduleDifficultySwitch(() => beatmapManager.CreateNewBlankDifficulty(beatmapSetInfo, rulesetInfo), editorState); + + public void ScheduleSwitchToExistingDifficulty(BeatmapInfo beatmapInfo, EditorState editorState) + => scheduleDifficultySwitch(() => beatmapManager.GetWorkingBeatmap(beatmapInfo), editorState); + + private void scheduleDifficultySwitch(Func nextBeatmap, EditorState editorState) { scheduledDifficultySwitch?.Cancel(); ValidForResume = true; @@ -87,7 +94,7 @@ namespace osu.Game.Screens.Edit scheduledDifficultySwitch = Schedule(() => { - Beatmap.Value = beatmapManager.GetWorkingBeatmap(nextBeatmap); + Beatmap.Value = nextBeatmap.Invoke(); state = editorState; // This screen is a weird exception to the rule that nothing after song select changes the global beatmap. From 0d51c015addfcd10fdcc24f6ef37446ef7c1fdb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 23 Jan 2022 18:34:33 +0100 Subject: [PATCH 917/996] Add basic test coverage for new difficulty creation --- .../Editing/TestSceneEditorBeatmapCreation.cs | 40 +++++++++++++++++++ osu.Game/Screens/Edit/Editor.cs | 4 +- osu.Game/Tests/Visual/EditorTestScene.cs | 2 + 3 files changed, 44 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index e3fb44534b..9ceff86426 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -90,5 +90,45 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("track length changed", () => Beatmap.Value.Track.Length > 60000); } + + [Test] + public void TestCreateNewDifficulty() + { + string firstDifficultyName = Guid.NewGuid().ToString(); + string secondDifficultyName = Guid.NewGuid().ToString(); + + AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = firstDifficultyName); + AddStep("save beatmap", () => Editor.Save()); + AddAssert("new beatmap persisted", () => + { + var beatmap = beatmapManager.QueryBeatmap(b => b.DifficultyName == firstDifficultyName); + var set = beatmapManager.QueryBeatmapSet(s => s.ID == EditorBeatmap.BeatmapInfo.BeatmapSet.ID); + + return beatmap != null + && beatmap.DifficultyName == firstDifficultyName + && set != null + && set.PerformRead(s => s.Beatmaps.Single().ID == beatmap.ID); + }); + + AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo)); + AddUntilStep("wait for created", () => + { + string difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; + return difficultyName != null && difficultyName != firstDifficultyName; + }); + + AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = secondDifficultyName); + AddStep("save beatmap", () => Editor.Save()); + AddAssert("new beatmap persisted", () => + { + var beatmap = beatmapManager.QueryBeatmap(b => b.DifficultyName == secondDifficultyName); + var set = beatmapManager.QueryBeatmapSet(s => s.ID == EditorBeatmap.BeatmapInfo.BeatmapSet.ID); + + return beatmap != null + && beatmap.DifficultyName == secondDifficultyName + && set != null + && set.PerformRead(s => s.Beatmaps.Count == 2 && s.Beatmaps.Any(b => b.DifficultyName == secondDifficultyName)); + }); + } } } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index df8e326c5b..3d7677ce19 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -822,12 +822,12 @@ namespace osu.Game.Screens.Edit var rulesetItems = new List(); foreach (var ruleset in rulesets.AvailableRulesets.OrderBy(ruleset => ruleset.OnlineID)) - rulesetItems.Add(new EditorMenuItem(ruleset.Name, MenuItemType.Standard, () => createNewDifficulty(ruleset))); + rulesetItems.Add(new EditorMenuItem(ruleset.Name, MenuItemType.Standard, () => CreateNewDifficulty(ruleset))); return new EditorMenuItem("Create new difficulty") { Items = rulesetItems }; } - private void createNewDifficulty(RulesetInfo rulesetInfo) + protected void CreateNewDifficulty(RulesetInfo rulesetInfo) => loader?.ScheduleSwitchToNewDifficulty(editorBeatmap.BeatmapInfo.BeatmapSet, rulesetInfo, GetState()); private EditorMenuItem createDifficultySwitchMenu() diff --git a/osu.Game/Tests/Visual/EditorTestScene.cs b/osu.Game/Tests/Visual/EditorTestScene.cs index bcf169bb1e..52d61ed422 100644 --- a/osu.Game/Tests/Visual/EditorTestScene.cs +++ b/osu.Game/Tests/Visual/EditorTestScene.cs @@ -107,6 +107,8 @@ namespace osu.Game.Tests.Visual public new void SwitchToDifficulty(BeatmapInfo beatmapInfo) => base.SwitchToDifficulty(beatmapInfo); + public new void CreateNewDifficulty(RulesetInfo rulesetInfo) => base.CreateNewDifficulty(rulesetInfo); + public new bool HasUnsavedChanges => base.HasUnsavedChanges; public TestEditor(EditorLoader loader = null) From 54bb6ad40c992cb55de194ed12aac4686fea6ee6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 23 Jan 2022 18:56:19 +0100 Subject: [PATCH 918/996] Fix working beatmaps not seeing new difficulties after add --- osu.Game/Beatmaps/BeatmapManager.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 38ba244f28..d9b0ec3170 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -129,6 +129,8 @@ namespace osu.Game.Beatmaps newBeatmap.ControlPointInfo.Add(timingPoint.Time, timingPoint.DeepClone()); var createdBeatmapInfo = beatmapModelManager.AddDifficultyToBeatmapSet(beatmapSetInfo, newBeatmap); + + workingBeatmapCache.Invalidate(createdBeatmapInfo.BeatmapSet); return GetWorkingBeatmap(createdBeatmapInfo); } From 87e2e83288ec72a569207d0854dbba49bf4880b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 23 Jan 2022 19:25:59 +0100 Subject: [PATCH 919/996] Add test coverage for difficulty name clash cases --- .../Editing/TestSceneEditorBeatmapCreation.cs | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index 9ceff86426..31348c1e17 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -130,5 +130,59 @@ namespace osu.Game.Tests.Visual.Editing && set.PerformRead(s => s.Beatmaps.Count == 2 && s.Beatmaps.Any(b => b.DifficultyName == secondDifficultyName)); }); } + + [Test] + public void TestCreateNewBeatmapFailsWithBlankNamedDifficulties() + { + Guid setId = Guid.Empty; + + AddStep("retrieve set ID", () => setId = EditorBeatmap.BeatmapInfo.BeatmapSet!.ID); + AddStep("save beatmap", () => Editor.Save()); + AddAssert("new beatmap persisted", () => + { + var set = beatmapManager.QueryBeatmapSet(s => s.ID == setId); + return set != null && set.PerformRead(s => s.Beatmaps.Count == 1 && s.Files.Count == 1); + }); + + AddStep("try to create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo)); + AddAssert("beatmap set unchanged", () => + { + var set = beatmapManager.QueryBeatmapSet(s => s.ID == setId); + return set != null && set.PerformRead(s => s.Beatmaps.Count == 1 && s.Files.Count == 1); + }); + } + + [Test] + public void TestCreateNewBeatmapFailsWithSameNamedDifficulties() + { + Guid setId = Guid.Empty; + const string duplicate_difficulty_name = "duplicate"; + + AddStep("retrieve set ID", () => setId = EditorBeatmap.BeatmapInfo.BeatmapSet!.ID); + AddStep("set difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = duplicate_difficulty_name); + AddStep("save beatmap", () => Editor.Save()); + AddAssert("new beatmap persisted", () => + { + var set = beatmapManager.QueryBeatmapSet(s => s.ID == setId); + return set != null && set.PerformRead(s => s.Beatmaps.Count == 1 && s.Files.Count == 1); + }); + + AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo)); + AddUntilStep("wait for created", () => + { + string difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; + return difficultyName != null && difficultyName != duplicate_difficulty_name; + }); + + AddStep("set difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = duplicate_difficulty_name); + AddStep("try to save beatmap", () => Editor.Save()); + AddAssert("beatmap set not corrupted", () => + { + var set = beatmapManager.QueryBeatmapSet(s => s.ID == setId); + // the difficulty was already created at the point of the switch. + // what we want to check is that both difficulties do not use the same file. + return set != null && set.PerformRead(s => s.Beatmaps.Count == 2 && s.Files.Count == 2); + }); + } } } From 4f1aac9345f8e4b292d079b630c2a00e62a264ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 23 Jan 2022 19:42:07 +0100 Subject: [PATCH 920/996] Add safeties preventing creating multiple difficulties with same name --- osu.Game/Beatmaps/BeatmapModelManager.cs | 6 ++++++ osu.Game/Screens/Edit/Editor.cs | 16 ++++++++++++---- osu.Game/Screens/Edit/EditorLoader.cs | 16 +++++++++++++++- 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs index 2ab5ac1db9..327e10e643 100644 --- a/osu.Game/Beatmaps/BeatmapModelManager.cs +++ b/osu.Game/Beatmaps/BeatmapModelManager.cs @@ -71,6 +71,12 @@ namespace osu.Game.Beatmaps // AddFile generally handles updating/replacing files, but this is a case where the filename may have also changed so let's delete for simplicity. var existingFileInfo = setInfo.Files.SingleOrDefault(f => string.Equals(f.Filename, beatmapInfo.Path, StringComparison.OrdinalIgnoreCase)); + string targetFilename = getFilename(beatmapInfo); + + // ensure that two difficulties from the set don't point at the same beatmap file. + if (setInfo.Beatmaps.Any(b => string.Equals(b.Path, targetFilename, StringComparison.OrdinalIgnoreCase))) + throw new InvalidOperationException($"{setInfo.GetDisplayString()} already has a difficulty with the name of '{beatmapInfo.DifficultyName}'."); + if (existingFileInfo != null) DeleteFile(setInfo, existingFileInfo); diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 3d7677ce19..64b23ccda8 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -386,12 +386,20 @@ namespace osu.Game.Screens.Edit return; } + try + { + // save the loaded beatmap's data stream. + beatmapManager.Save(editorBeatmap.BeatmapInfo, editorBeatmap.PlayableBeatmap, editorBeatmap.BeatmapSkin); + } + catch (Exception ex) + { + // can fail e.g. due to duplicated difficulty names. + Logger.Error(ex, ex.Message); + return; + } + // no longer new after first user-triggered save. isNewBeatmap = false; - - // save the loaded beatmap's data stream. - beatmapManager.Save(editorBeatmap.BeatmapInfo, editorBeatmap.PlayableBeatmap, editorBeatmap.BeatmapSkin); - updateLastSavedHash(); } diff --git a/osu.Game/Screens/Edit/EditorLoader.cs b/osu.Game/Screens/Edit/EditorLoader.cs index 731bc75b52..de47411fdc 100644 --- a/osu.Game/Screens/Edit/EditorLoader.cs +++ b/osu.Game/Screens/Edit/EditorLoader.cs @@ -6,6 +6,7 @@ using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Logging; using osu.Framework.Screens; using osu.Framework.Threading; using osu.Game.Beatmaps; @@ -80,7 +81,20 @@ namespace osu.Game.Screens.Edit } public void ScheduleSwitchToNewDifficulty(BeatmapSetInfo beatmapSetInfo, RulesetInfo rulesetInfo, EditorState editorState) - => scheduleDifficultySwitch(() => beatmapManager.CreateNewBlankDifficulty(beatmapSetInfo, rulesetInfo), editorState); + => scheduleDifficultySwitch(() => + { + try + { + return beatmapManager.CreateNewBlankDifficulty(beatmapSetInfo, rulesetInfo); + } + catch (Exception ex) + { + // if the beatmap creation fails (e.g. due to duplicated difficulty names), + // bring the user back to the previous beatmap as a best-effort. + Logger.Error(ex, ex.Message); + return Beatmap.Value; + } + }, editorState); public void ScheduleSwitchToExistingDifficulty(BeatmapInfo beatmapInfo, EditorState editorState) => scheduleDifficultySwitch(() => beatmapManager.GetWorkingBeatmap(beatmapInfo), editorState); From afc48d86df1be79ca37d02cb3fdee604d7f7ecaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 23 Jan 2022 19:50:02 +0100 Subject: [PATCH 921/996] Add failing test coverage for save after safeties addition --- .../Editing/TestSceneEditorBeatmapCreation.cs | 1 + osu.Game/Screens/Edit/Editor.cs | 13 +++++++++---- osu.Game/Tests/Visual/EditorTestScene.cs | 2 +- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index 31348c1e17..a14c9aded3 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -109,6 +109,7 @@ namespace osu.Game.Tests.Visual.Editing && set != null && set.PerformRead(s => s.Beatmaps.Single().ID == beatmap.ID); }); + AddAssert("can save again", () => Editor.Save()); AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo)); AddUntilStep("wait for created", () => diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 64b23ccda8..78c5c862f7 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -378,12 +378,16 @@ namespace osu.Game.Screens.Edit Clipboard.Content.Value = state.ClipboardContent; }); - protected void Save() + /// + /// Saves the currently edited beatmap. + /// + /// Whether the save was successful. + protected bool Save() { if (!canSave) { notifications?.Post(new SimpleErrorNotification { Text = "Saving is not supported for this ruleset yet, sorry!" }); - return; + return false; } try @@ -395,12 +399,13 @@ namespace osu.Game.Screens.Edit { // can fail e.g. due to duplicated difficulty names. Logger.Error(ex, ex.Message); - return; + return false; } // no longer new after first user-triggered save. isNewBeatmap = false; updateLastSavedHash(); + return true; } protected override void Update() @@ -809,7 +814,7 @@ namespace osu.Game.Screens.Edit { var fileMenuItems = new List { - new EditorMenuItem("Save", MenuItemType.Standard, Save) + new EditorMenuItem("Save", MenuItemType.Standard, () => Save()) }; if (RuntimeInfo.IsDesktop) diff --git a/osu.Game/Tests/Visual/EditorTestScene.cs b/osu.Game/Tests/Visual/EditorTestScene.cs index 52d61ed422..da13c6862f 100644 --- a/osu.Game/Tests/Visual/EditorTestScene.cs +++ b/osu.Game/Tests/Visual/EditorTestScene.cs @@ -97,7 +97,7 @@ namespace osu.Game.Tests.Visual public new void Redo() => base.Redo(); - public new void Save() => base.Save(); + public new bool Save() => base.Save(); public new void Cut() => base.Cut(); From 47429fb0c68b6df09b73056a8a7a5b8d3fe2e4ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 23 Jan 2022 19:54:57 +0100 Subject: [PATCH 922/996] Fix same-name safety firing wrongly --- osu.Game/Beatmaps/BeatmapModelManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs index 327e10e643..d4df4b7870 100644 --- a/osu.Game/Beatmaps/BeatmapModelManager.cs +++ b/osu.Game/Beatmaps/BeatmapModelManager.cs @@ -74,7 +74,7 @@ namespace osu.Game.Beatmaps string targetFilename = getFilename(beatmapInfo); // ensure that two difficulties from the set don't point at the same beatmap file. - if (setInfo.Beatmaps.Any(b => string.Equals(b.Path, targetFilename, StringComparison.OrdinalIgnoreCase))) + if (setInfo.Beatmaps.Any(b => b.ID != beatmapInfo.ID && string.Equals(b.Path, targetFilename, StringComparison.OrdinalIgnoreCase))) throw new InvalidOperationException($"{setInfo.GetDisplayString()} already has a difficulty with the name of '{beatmapInfo.DifficultyName}'."); if (existingFileInfo != null) From a8ffc4fc2a08b27af6e295ee97cfee3d208d4bc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 23 Jan 2022 19:58:21 +0100 Subject: [PATCH 923/996] Add editor override to respect `IsolateSavingFromDatabase` --- osu.Game/Beatmaps/BeatmapManager.cs | 2 +- osu.Game/Tests/Visual/EditorTestScene.cs | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index d9b0ec3170..9c9b995955 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -112,7 +112,7 @@ namespace osu.Game.Beatmaps /// The new difficulty will be backed by a model /// and represented by the returned . /// - public WorkingBeatmap CreateNewBlankDifficulty(BeatmapSetInfo beatmapSetInfo, RulesetInfo rulesetInfo) + public virtual WorkingBeatmap CreateNewBlankDifficulty(BeatmapSetInfo beatmapSetInfo, RulesetInfo rulesetInfo) { // fetch one of the existing difficulties to copy timing points and metadata from, // so that the user doesn't have to fill all of that out again. diff --git a/osu.Game/Tests/Visual/EditorTestScene.cs b/osu.Game/Tests/Visual/EditorTestScene.cs index da13c6862f..331bf04644 100644 --- a/osu.Game/Tests/Visual/EditorTestScene.cs +++ b/osu.Game/Tests/Visual/EditorTestScene.cs @@ -136,6 +136,12 @@ namespace osu.Game.Tests.Visual return new TestWorkingBeatmapCache(this, audioManager, resources, storage, defaultBeatmap, host); } + public override WorkingBeatmap CreateNewBlankDifficulty(BeatmapSetInfo beatmapSetInfo, RulesetInfo rulesetInfo) + { + // don't actually care about properly creating a difficulty for this context. + return TestBeatmap; + } + private class TestWorkingBeatmapCache : WorkingBeatmapCache { private readonly TestBeatmapManager testBeatmapManager; From 62537eb4aa5f458fd959846a8bf318b16e2acef0 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 3 Feb 2022 12:44:33 +0900 Subject: [PATCH 924/996] Fix spectator not completing --- osu.Game/Screens/Spectate/SpectatorScreen.cs | 21 +++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs index 0b2a7cecf6..460352ee07 100644 --- a/osu.Game/Screens/Spectate/SpectatorScreen.cs +++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs @@ -112,6 +112,7 @@ namespace osu.Game.Screens.Spectate switch (e.Action) { case NotifyDictionaryChangedAction.Add: + case NotifyDictionaryChangedAction.Replace: foreach ((int userId, var state) in e.NewItems.AsNonNull()) onUserStateChanged(userId, state); break; @@ -120,11 +121,6 @@ namespace osu.Game.Screens.Spectate foreach ((int userId, _) in e.OldItems.AsNonNull()) onUserStateRemoved(userId); break; - - case NotifyDictionaryChangedAction.Replace: - foreach ((int userId, var state) in e.NewItems.AsNonNull()) - onUserStateChanged(userId, state); - break; } } @@ -136,11 +132,18 @@ namespace osu.Game.Screens.Spectate if (!userMap.ContainsKey(userId)) return; - // Do nothing for failed/completed states. - if (newState.State == SpectatingUserState.Playing) + switch (newState.State) { - Schedule(() => OnUserStateChanged(userId, newState)); - updateGameplayState(userId); + case SpectatingUserState.Completed: + // Make sure that gameplay completes to the end. + if (gameplayStates.TryGetValue(userId, out var gameplayState)) + gameplayState.Score.Replay.HasReceivedAllFrames = true; + break; + + case SpectatingUserState.Playing: + Schedule(() => OnUserStateChanged(userId, newState)); + updateGameplayState(userId); + break; } } From aff36d4e163c96c9dd35ea280a1b630806b63d10 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 3 Feb 2022 11:52:37 +0800 Subject: [PATCH 925/996] Refactor `populateStatistics` to avoid disposing --- .../Ranking/Statistics/StatisticsPanel.cs | 128 +++++++++--------- 1 file changed, 63 insertions(+), 65 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index 08be1de652..cbb4e5089d 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -86,88 +86,86 @@ namespace osu.Game.Screens.Ranking.Statistics playableBeatmap = beatmapManager.GetWorkingBeatmap(newScore.BeatmapInfo).GetPlayableBeatmap(newScore.Ruleset, newScore.Mods); }, loadCancellation.Token).ContinueWith(t => Schedule(() => { - FillFlowContainer rows; - Container container = new OsuScrollContainer(Direction.Vertical) - { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Alpha = 0, - Children = new[] - { - rows = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Spacing = new Vector2(30, 15) - } - } - }; - - bool panelIsEmpty = true; - bool panelIsComplete = true; bool hitEventsAvailable = newScore.HitEvents.Count != 0; + Container container; - foreach (var row in newScore.Ruleset.CreateInstance().CreateStatisticsForScore(newScore, playableBeatmap)) + var statisticRows = newScore.Ruleset.CreateInstance().CreateStatisticsForScore(newScore, playableBeatmap); + + if (!hitEventsAvailable && statisticRows.SelectMany(r => r.Columns).All(c => c.RequiresHitEvents)) { - var columnsToDisplay = hitEventsAvailable - ? row.Columns - : row.Columns.Where(c => !c.RequiresHitEvents).ToArray(); - - if (columnsToDisplay.Length < row.Columns.Length) - panelIsComplete = false; - - if (columnsToDisplay.Any()) - panelIsEmpty = false; - else - continue; - - rows.Add(new GridContainer + container = new FillFlowContainer { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Content = new[] + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Direction = FillDirection.Vertical, + Children = new Drawable[] { - columnsToDisplay?.Select(c => new StatisticContainer(c) + new MessagePlaceholder("Extended statistics are only available after watching a replay!"), + new ReplayDownloadButton(newScore) { + Scale = new Vector2(1.5f), Anchor = Anchor.Centre, Origin = Anchor.Centre, - }).Cast().ToArray() - }, - ColumnDimensions = Enumerable.Range(0, columnsToDisplay.Length) - .Select(i => columnsToDisplay[i].Dimension ?? new Dimension()).ToArray(), - RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) } - }); + }, + } + }; } - - if (!hitEventsAvailable) + else { - if (panelIsEmpty) + FillFlowContainer rows; + container = new OsuScrollContainer(Direction.Vertical) { - // Replace the scroll container with fill flow container to get the message centered. - rows.Dispose(); - container.Dispose(); - container = new FillFlowContainer + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Alpha = 0, + Children = new[] { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Direction = FillDirection.Vertical, - Children = new Drawable[] + rows = new FillFlowContainer { - new MessagePlaceholder("Extended statistics are only available after watching a replay!"), - new ReplayDownloadButton(newScore) + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(30, 15) + } + } + }; + + bool panelIsComplete = true; + + foreach (var row in statisticRows) + { + var columnsToDisplay = hitEventsAvailable + ? row.Columns + : row.Columns.Where(c => !c.RequiresHitEvents).ToArray(); + + if (columnsToDisplay.Length < row.Columns.Length) + panelIsComplete = false; + + if (columnsToDisplay.Length == 0) + continue; + + rows.Add(new GridContainer + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Content = new[] + { + columnsToDisplay?.Select(c => new StatisticContainer(c) { - Scale = new Vector2(1.5f), Anchor = Anchor.Centre, Origin = Anchor.Centre, - }, - } - }; + }).Cast().ToArray() + }, + ColumnDimensions = Enumerable.Range(0, columnsToDisplay.Length) + .Select(i => columnsToDisplay[i].Dimension ?? new Dimension()).ToArray(), + RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) } + }); } - else if (!panelIsComplete) + + if (!hitEventsAvailable && !panelIsComplete) { rows.Add(new FillFlowContainer { From d9a43b4c4ce50d58ff4d12393c9b4f4c66bc602d Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 3 Feb 2022 13:16:54 +0900 Subject: [PATCH 926/996] Fix API requests not completing when offline --- osu.Game/Online/API/APIAccess.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index 8d91548149..1d2abe0c7f 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -399,7 +399,10 @@ namespace osu.Game.Online.API lock (queue) { if (state.Value == APIState.Offline) + { + request.Fail(new WebException("Disconnected from server")); return; + } queue.Enqueue(request); } From 62fa915193ba65a386d647ba63b0e5d422d18c64 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Feb 2022 13:58:55 +0900 Subject: [PATCH 927/996] Standardise exception messages for local-user-logged-out flows --- osu.Game/Online/API/APIAccess.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index 1d2abe0c7f..a1b8e5bee7 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -400,7 +400,7 @@ namespace osu.Game.Online.API { if (state.Value == APIState.Offline) { - request.Fail(new WebException("Disconnected from server")); + failFromAPIOffline(request); return; } @@ -419,7 +419,7 @@ namespace osu.Game.Online.API if (failOldRequests) { foreach (var req in oldQueueRequests) - req.Fail(new WebException(@"Disconnected from server")); + failFromAPIOffline(req); } } } @@ -440,6 +440,13 @@ namespace osu.Game.Online.API flushQueue(); } + private void failFromAPIOffline(APIRequest req) + { + Debug.Assert(state.Value == APIState.Offline); + + req.Fail(new WebException(@"User not logged in")); + } + private static APIUser createGuestUser() => new GuestUser(); protected override void Dispose(bool isDisposing) From a69c7a9de6f80b3b95b628abc1b5711c6338743e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Feb 2022 14:09:27 +0900 Subject: [PATCH 928/996] Split exceptions back out to give better messaging --- osu.Game/Online/API/APIAccess.cs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index a1b8e5bee7..c5302a393c 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -400,7 +400,7 @@ namespace osu.Game.Online.API { if (state.Value == APIState.Offline) { - failFromAPIOffline(request); + request.Fail(new WebException(@"User not logged in")); return; } @@ -419,7 +419,7 @@ namespace osu.Game.Online.API if (failOldRequests) { foreach (var req in oldQueueRequests) - failFromAPIOffline(req); + req.Fail(new WebException($@"Request failed from flush operation (state {state.Value})")); } } } @@ -440,13 +440,6 @@ namespace osu.Game.Online.API flushQueue(); } - private void failFromAPIOffline(APIRequest req) - { - Debug.Assert(state.Value == APIState.Offline); - - req.Fail(new WebException(@"User not logged in")); - } - private static APIUser createGuestUser() => new GuestUser(); protected override void Dispose(bool isDisposing) From c8ce00b26a5c9ca1049274cd7eee7e0c2f54e075 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Feb 2022 14:50:40 +0900 Subject: [PATCH 929/996] Trigger a re-layout of HUD components when scoring mode is changed This is a simple way of fixing the layout of scoring elements overlapping due to different score display width requirements of different scoring modes. It will only resolve the case where a user hasn't customsied the layout of the default skins, but as this is a very simple / low effort implementation for the most common scenario, I think it makes sense. Closes https://github.com/ppy/osu/issues/16067. --- osu.Game/Screens/Play/HUDOverlay.cs | 30 +++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index fdb5d418f3..628452fbc8 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -17,6 +17,7 @@ using osu.Game.Input.Bindings; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Screens.Play.HUD; using osu.Game.Skinning; @@ -83,10 +84,7 @@ namespace osu.Game.Screens.Play Children = new Drawable[] { CreateFailingLayer(), - mainComponents = new SkinnableTargetContainer(SkinnableTarget.MainHUDComponents) - { - RelativeSizeAxes = Axes.Both, - }, + mainComponents = new MainComponentsContainer(), topRightElements = new FillFlowContainer { Anchor = Anchor.TopRight, @@ -325,5 +323,29 @@ namespace osu.Game.Screens.Play break; } } + + private class MainComponentsContainer : SkinnableTargetContainer + { + private Bindable scoringMode; + + [Resolved] + private OsuConfigManager config { get; set; } + + public MainComponentsContainer() + : base(SkinnableTarget.MainHUDComponents) + { + RelativeSizeAxes = Axes.Both; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + // When the scoring mode changes, relative positions of elements may change (see DefaultSkin.GetDrawableComponent). + // This is a best effort implementation for cases where users haven't customised layouts. + scoringMode = config.GetBindable(OsuSetting.ScoreDisplayMode); + scoringMode.BindValueChanged(val => Reload()); + } + } } } From 6355ac66634834124adfb30be16a3a3d138ada9c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Feb 2022 15:10:06 +0900 Subject: [PATCH 930/996] Wait for `DialogOverlay` load in more tests Apparently the previous fix was not enough as this can still be seen failing (https://github.com/ppy/osu/runs/5046718623?check_suite_focus=true). This change is copying from what other tests use seemingly reliably, such as `TestScenePerformFromScreen`) --- osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index ed484e03f6..e31377b96e 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -128,6 +128,7 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("choose clear all scores", () => InputManager.Key(Key.Number4)); + AddUntilStep("wait for dialog display", () => Game.Dependencies.Get().IsLoaded); AddUntilStep("wait for dialog", () => Game.Dependencies.Get().CurrentDialog != null); AddStep("confirm deletion", () => InputManager.Key(Key.Number1)); AddUntilStep("wait for dialog dismissed", () => Game.Dependencies.Get().CurrentDialog == null); @@ -172,6 +173,7 @@ namespace osu.Game.Tests.Visual.Navigation InputManager.Click(MouseButton.Left); }); + AddUntilStep("wait for dialog display", () => Game.Dependencies.Get().IsLoaded); AddUntilStep("wait for dialog", () => Game.Dependencies.Get().CurrentDialog != null); AddStep("confirm deletion", () => InputManager.Key(Key.Number1)); AddUntilStep("wait for dialog dismissed", () => Game.Dependencies.Get().CurrentDialog == null); From 41aa4b8cca63be85c7a8434618c0dc3189f04f87 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Feb 2022 16:04:05 +0900 Subject: [PATCH 931/996] Fix `TestSelectingFilteredRuleset` failing under visual tests due to using local database --- .../SongSelect/TestSceneBeatmapCarousel.cs | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index a23bc620ec..4e46901e08 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -601,7 +601,7 @@ namespace osu.Game.Tests.Visual.SongSelect { BeatmapSetInfo testMixed = null; - createCarousel(); + createCarousel(new List()); AddStep("add mixed ruleset beatmapset", () => { @@ -765,22 +765,22 @@ namespace osu.Game.Tests.Visual.SongSelect { bool changed = false; - createCarousel(c => + if (beatmapSets == null) + { + beatmapSets = new List(); + + for (int i = 1; i <= (count ?? set_count); i++) + { + beatmapSets.Add(randomDifficulties + ? TestResources.CreateTestBeatmapSetInfo() + : TestResources.CreateTestBeatmapSetInfo(3)); + } + } + + createCarousel(beatmapSets, c => { carouselAdjust?.Invoke(c); - if (beatmapSets == null) - { - beatmapSets = new List(); - - for (int i = 1; i <= (count ?? set_count); i++) - { - beatmapSets.Add(randomDifficulties - ? TestResources.CreateTestBeatmapSetInfo() - : TestResources.CreateTestBeatmapSetInfo(3)); - } - } - carousel.Filter(initialCriteria?.Invoke() ?? new FilterCriteria()); carousel.BeatmapSetsChanged = () => changed = true; carousel.BeatmapSets = beatmapSets; @@ -789,7 +789,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddUntilStep("Wait for load", () => changed); } - private void createCarousel(Action carouselAdjust = null, Container target = null) + private void createCarousel(List beatmapSets, Action carouselAdjust = null, Container target = null) { AddStep("Create carousel", () => { @@ -803,6 +803,8 @@ namespace osu.Game.Tests.Visual.SongSelect carouselAdjust?.Invoke(carousel); + carousel.BeatmapSets = beatmapSets; + (target ?? this).Child = carousel; }); } From e65996efc31b5c17d7386baa1548d204889849ed Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Feb 2022 17:14:38 +0900 Subject: [PATCH 932/996] Rename variable to match purpose better --- osu.Game/Screens/Select/SongSelect.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 44af63a554..ee807762bf 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -110,7 +110,7 @@ namespace osu.Game.Screens.Select private double audioFeedbackLastPlaybackTime; - private bool randomClicked; + private bool randomSelectionPending; [Resolved] private MusicController music { get; set; } @@ -321,12 +321,12 @@ namespace osu.Game.Screens.Select { NextRandom = () => { - randomClicked = true; + randomSelectionPending = true; Carousel.SelectNextRandom(); }, PreviousRandom = () => { - randomClicked = true; + randomSelectionPending = true; Carousel.SelectPreviousRandom(); } }, null), @@ -498,7 +498,7 @@ namespace osu.Game.Screens.Select { if (beatmap != null && beatmapInfoPrevious != null && Time.Current - audioFeedbackLastPlaybackTime >= 50) { - if (randomClicked) + if (randomSelectionPending) sampleRandomBeatmap.Play(); else if (beatmap.BeatmapSet?.ID == beatmapInfoPrevious.BeatmapSet?.ID) sampleChangeDifficulty.Play(); @@ -508,7 +508,7 @@ namespace osu.Game.Screens.Select audioFeedbackLastPlaybackTime = Time.Current; } - randomClicked = false; + randomSelectionPending = false; beatmapInfoPrevious = beatmap; } From a27d0572ed3d5c8b4c5f082015de2f6c33b86005 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 3 Feb 2022 17:00:40 +0800 Subject: [PATCH 933/996] Add test cases for manual testing --- .../Ranking/TestSceneStatisticsPanel.cs | 155 ++++++++++++++++++ 1 file changed, 155 insertions(+) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs index f64b7b2b65..35281a85eb 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs @@ -6,10 +6,18 @@ using System.Collections.Generic; using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Mods; using osu.Game.Scoring; using osu.Game.Screens.Ranking.Statistics; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI; using osu.Game.Tests.Resources; using osuTK; @@ -41,6 +49,24 @@ namespace osu.Game.Tests.Visual.Ranking loadPanel(TestResources.CreateTestScoreInfo()); } + [Test] + public void TestScoreInRulesetWhereAllStatsRequireHitEvents() + { + loadPanel(TestResources.CreateTestScoreInfo(new TestRulesetAllStatsRequireHitEvents().RulesetInfo)); + } + + [Test] + public void TestScoreInRulesetWhereNoStatsRequireHitEvents() + { + loadPanel(TestResources.CreateTestScoreInfo(new TestRulesetNoStatsRequireHitEvents().RulesetInfo)); + } + + [Test] + public void TestScoreInMixedRuleset() + { + loadPanel(TestResources.CreateTestScoreInfo(new TestRulesetMixed().RulesetInfo)); + } + [Test] public void TestNullScore() { @@ -75,5 +101,134 @@ namespace osu.Game.Tests.Visual.Ranking return hitEvents; } + + private class TestRuleset : Ruleset + { + public override IEnumerable GetModsFor(ModType type) + { + throw new NotImplementedException(); + } + + public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) + { + throw new NotImplementedException(); + } + + public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) + { + throw new NotImplementedException(); + } + + public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) + { + throw new NotImplementedException(); + } + + public override string Description => string.Empty; + + public override string ShortName => string.Empty; + + protected static Drawable CreatePlaceholderStatistic(string message) => new Container + { + RelativeSizeAxes = Axes.X, + Masking = true, + CornerRadius = 20, + Height = 250, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.Gray(0.5f), + Alpha = 0.5f + }, + new OsuSpriteText + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Text = message, + Margin = new MarginPadding { Left = 20 } + } + } + }; + } + + private class TestRulesetAllStatsRequireHitEvents : TestRuleset + { + public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) + { + return new[] + { + new StatisticRow + { + Columns = new[] + { + new StatisticItem("Statistic Requiring Hit Events 1", + () => CreatePlaceholderStatistic("Placeholder statistic. Requires hit events"), true) + } + }, + new StatisticRow + { + Columns = new[] + { + new StatisticItem("Statistic Requiring Hit Events 2", + () => CreatePlaceholderStatistic("Placeholder statistic. Requires hit events"), true) + } + } + }; + } + } + + private class TestRulesetNoStatsRequireHitEvents : TestRuleset + { + public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) + { + return new[] + { + new StatisticRow + { + Columns = new[] + { + new StatisticItem("Statistic Not Requiring Hit Events 1", + () => CreatePlaceholderStatistic("Placeholder statistic. Does not require hit events")) + } + }, + new StatisticRow + { + Columns = new[] + { + new StatisticItem("Statistic Not Requiring Hit Events 2", + () => CreatePlaceholderStatistic("Placeholder statistic. Does not require hit events")) + } + } + }; + } + } + + private class TestRulesetMixed : TestRuleset + { + public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) + { + return new[] + { + new StatisticRow + { + Columns = new[] + { + new StatisticItem("Statistic Requiring Hit Events", + () => CreatePlaceholderStatistic("Placeholder statistic. Requires hit events"), true) + } + }, + new StatisticRow + { + Columns = new[] + { + new StatisticItem("Statistic Not Requiring Hit Events", + () => CreatePlaceholderStatistic("Placeholder statistic. Does not require hit events")) + } + } + }; + } + } } } From 6d6327d3da550900a717b166a8220f09ab1dde16 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Feb 2022 18:40:10 +0900 Subject: [PATCH 934/996] Fix test beatmap loading potentially performing selection before carousel itself is loaded --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 0f5c708ab8..f17daa8697 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -108,7 +108,7 @@ namespace osu.Game.Screens.Select set { loadedTestBeatmaps = true; - loadBeatmapSets(value); + Schedule(() => loadBeatmapSets(value)); } } From 6974c2d255f400be9401479df9e782c79a2c04b8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Feb 2022 19:00:03 +0900 Subject: [PATCH 935/996] Remove weird `panelIsComplete` flag and replace LINQ with simple `foreach` --- .../Ranking/Statistics/StatisticsPanel.cs | 46 +++++++++++-------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index cbb4e5089d..898bd69b2c 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.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.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -131,41 +132,48 @@ namespace osu.Game.Screens.Ranking.Statistics } }; - bool panelIsComplete = true; + bool anyRequiredHitEvents = false; foreach (var row in statisticRows) { - var columnsToDisplay = hitEventsAvailable - ? row.Columns - : row.Columns.Where(c => !c.RequiresHitEvents).ToArray(); + var columns = row.Columns; - if (columnsToDisplay.Length < row.Columns.Length) - panelIsComplete = false; - - if (columnsToDisplay.Length == 0) + if (columns.Length == 0) continue; + var columnContent = new List(); + var dimensions = new List(); + + foreach (var col in columns) + { + if (!hitEventsAvailable && col.RequiresHitEvents) + { + anyRequiredHitEvents = true; + continue; + } + + columnContent.Add(new StatisticContainer(col) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }); + + dimensions.Add(col.Dimension ?? new Dimension()); + } + rows.Add(new GridContainer { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Content = new[] - { - columnsToDisplay?.Select(c => new StatisticContainer(c) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }).Cast().ToArray() - }, - ColumnDimensions = Enumerable.Range(0, columnsToDisplay.Length) - .Select(i => columnsToDisplay[i].Dimension ?? new Dimension()).ToArray(), + Content = new[] { columnContent.ToArray() }, + ColumnDimensions = dimensions.ToArray(), RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) } }); } - if (!hitEventsAvailable && !panelIsComplete) + if (anyRequiredHitEvents) { rows.Add(new FillFlowContainer { From 47d577ec9c822edb762c91067fb7c17ea97a766a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Feb 2022 19:17:56 +0900 Subject: [PATCH 936/996] Add back constructor for ruleset compatibility --- osu.Game/Screens/Ranking/Statistics/StatisticItem.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs b/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs index 3cfc38e263..b43fbbdeee 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs @@ -33,6 +33,12 @@ namespace osu.Game.Screens.Ranking.Statistics /// public readonly bool RequiresHitEvents; + [Obsolete("Use constructor which takes creation function instead.")] // Can be removed 20220803. + public StatisticItem([NotNull] string name, [NotNull] Drawable content, [CanBeNull] Dimension dimension = null) + : this(name, () => content, true, dimension) + { + } + /// /// Creates a new , to be displayed inside a in the results screen. /// From ad47649d1c5044c64c4a4f8d50ff739031019221 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Feb 2022 19:38:53 +0900 Subject: [PATCH 937/996] Make `BeatmapModelManager.Save` non-virtual --- osu.Game/Beatmaps/BeatmapModelManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs index d4df4b7870..a9da221e08 100644 --- a/osu.Game/Beatmaps/BeatmapModelManager.cs +++ b/osu.Game/Beatmaps/BeatmapModelManager.cs @@ -46,7 +46,7 @@ namespace osu.Game.Beatmaps /// The to save the content against. The file referenced by will be replaced. /// The content to write. /// The beatmap content to write, null if to be omitted. - public virtual void Save(BeatmapInfo beatmapInfo, IBeatmap beatmapContent, ISkin? beatmapSkin = null) + public void Save(BeatmapInfo beatmapInfo, IBeatmap beatmapContent, ISkin? beatmapSkin = null) { var setInfo = beatmapInfo.BeatmapSet; Debug.Assert(setInfo != null); From bef0a2da210c7505adef18314ec12312b3ee59d1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Feb 2022 19:39:24 +0900 Subject: [PATCH 938/996] Remove return type from `AddDifficultyToBeatmapSet` Also removes a pointless realm encapsulation. --- osu.Game/Beatmaps/BeatmapManager.cs | 6 +++--- osu.Game/Beatmaps/BeatmapModelManager.cs | 15 +++++---------- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 9c9b995955..d60cfdee9e 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -128,10 +128,10 @@ namespace osu.Game.Beatmaps foreach (var timingPoint in referenceBeatmap.Beatmap.ControlPointInfo.TimingPoints) newBeatmap.ControlPointInfo.Add(timingPoint.Time, timingPoint.DeepClone()); - var createdBeatmapInfo = beatmapModelManager.AddDifficultyToBeatmapSet(beatmapSetInfo, newBeatmap); + beatmapModelManager.AddDifficultyToBeatmapSet(beatmapSetInfo, newBeatmap); - workingBeatmapCache.Invalidate(createdBeatmapInfo.BeatmapSet); - return GetWorkingBeatmap(createdBeatmapInfo); + workingBeatmapCache.Invalidate(beatmapSetInfo); + return GetWorkingBeatmap(newBeatmap.BeatmapInfo); } // TODO: add back support for making a copy of another difficulty diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs index a9da221e08..b9f0af8833 100644 --- a/osu.Game/Beatmaps/BeatmapModelManager.cs +++ b/osu.Game/Beatmaps/BeatmapModelManager.cs @@ -93,19 +93,14 @@ namespace osu.Game.Beatmaps /// /// Add a new difficulty to the beatmap set represented by the provided . /// - public BeatmapInfo AddDifficultyToBeatmapSet(BeatmapSetInfo beatmapSetInfo, Beatmap beatmap) + public void AddDifficultyToBeatmapSet(BeatmapSetInfo beatmapSetInfo, Beatmap beatmap) { - return Realm.Run(realm => - { - var beatmapInfo = beatmap.BeatmapInfo; + var beatmapInfo = beatmap.BeatmapInfo; - beatmapSetInfo.Beatmaps.Add(beatmapInfo); - beatmapInfo.BeatmapSet = beatmapSetInfo; + beatmapSetInfo.Beatmaps.Add(beatmapInfo); + beatmapInfo.BeatmapSet = beatmapSetInfo; - Save(beatmapInfo, beatmap); - - return beatmapInfo.Detach(); - }); + Save(beatmapInfo, beatmap); } private static string getFilename(BeatmapInfo beatmapInfo) From 40953751b542281dca26e24c323e48b73b2a6c60 Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Thu, 3 Feb 2022 13:28:49 +0100 Subject: [PATCH 939/996] Use `ScreenOrientation.FullUser` on Android tablets --- osu.Android/GameplayScreenRotationLocker.cs | 2 +- osu.Android/OsuGameActivity.cs | 25 ++++++++++++++++----- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/osu.Android/GameplayScreenRotationLocker.cs b/osu.Android/GameplayScreenRotationLocker.cs index ab91b4e3b3..1b7893e5b9 100644 --- a/osu.Android/GameplayScreenRotationLocker.cs +++ b/osu.Android/GameplayScreenRotationLocker.cs @@ -27,7 +27,7 @@ namespace osu.Android { gameActivity.RunOnUiThread(() => { - gameActivity.RequestedOrientation = userPlaying.NewValue ? ScreenOrientation.Locked : OsuGameActivity.DEFAULT_ORIENTATION; + gameActivity.RequestedOrientation = userPlaying.NewValue ? ScreenOrientation.Locked : gameActivity.ScreenOrientation; }); } } diff --git a/osu.Android/OsuGameActivity.cs b/osu.Android/OsuGameActivity.cs index e6679b61a6..7de597fe88 100644 --- a/osu.Android/OsuGameActivity.cs +++ b/osu.Android/OsuGameActivity.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -8,16 +9,18 @@ using System.Threading.Tasks; using Android.App; using Android.Content; using Android.Content.PM; -using Android.Net; +using Android.Graphics; using Android.OS; using Android.Provider; using Android.Views; using osu.Framework.Android; using osu.Game.Database; +using Debug = System.Diagnostics.Debug; +using Uri = Android.Net.Uri; namespace osu.Android { - [Activity(ConfigurationChanges = DEFAULT_CONFIG_CHANGES, Exported = true, LaunchMode = DEFAULT_LAUNCH_MODE, MainLauncher = true, ScreenOrientation = DEFAULT_ORIENTATION)] + [Activity(ConfigurationChanges = DEFAULT_CONFIG_CHANGES, Exported = true, LaunchMode = DEFAULT_LAUNCH_MODE, MainLauncher = true)] [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osz", DataHost = "*", DataMimeType = "*/*")] [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osk", DataHost = "*", DataMimeType = "*/*")] [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osr", DataHost = "*", DataMimeType = "*/*")] @@ -39,10 +42,10 @@ namespace osu.Android [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryBrowsable, Intent.CategoryDefault }, DataSchemes = new[] { "osu", "osump" })] public class OsuGameActivity : AndroidGameActivity { - public const ScreenOrientation DEFAULT_ORIENTATION = ScreenOrientation.SensorLandscape; - private static readonly string[] osu_url_schemes = { "osu", "osump" }; + public ScreenOrientation ScreenOrientation = ScreenOrientation.Unspecified; + private OsuGameAndroid game; protected override Framework.Game CreateGame() => game = new OsuGameAndroid(this); @@ -56,8 +59,20 @@ namespace osu.Android // reference: https://developer.android.com/reference/android/app/Activity#onNewIntent(android.content.Intent) handleIntent(Intent); + Debug.Assert(Window != null); + Window.AddFlags(WindowManagerFlags.Fullscreen); Window.AddFlags(WindowManagerFlags.KeepScreenOn); + + Debug.Assert(WindowManager?.DefaultDisplay != null); + Debug.Assert(Resources?.DisplayMetrics != null); + + Point displaySize = new Point(); + WindowManager.DefaultDisplay.GetSize(displaySize); + float smallestWidthDp = Math.Min(displaySize.X, displaySize.Y) / Resources.DisplayMetrics.Density; + bool isTablet = smallestWidthDp >= 600f; + + RequestedOrientation = ScreenOrientation = isTablet ? ScreenOrientation.FullUser : ScreenOrientation.SensorLandscape; } protected override void OnNewIntent(Intent intent) => handleIntent(intent); @@ -106,7 +121,7 @@ namespace osu.Android cursor.MoveToFirst(); - var filenameColumn = cursor.GetColumnIndex(OpenableColumns.DisplayName); + int filenameColumn = cursor.GetColumnIndex(OpenableColumns.DisplayName); string filename = cursor.GetString(filenameColumn); // SharpCompress requires archive streams to be seekable, which the stream opened by From f2850601486ec6a8b4b4a88d76b13c75fddd0320 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 3 Feb 2022 21:50:15 +0900 Subject: [PATCH 940/996] Fix MultiSpectatorScreen not continuing to results --- .../Multiplayer/Spectate/MultiSpectatorScreen.cs | 5 ++++- osu.Game/Screens/Play/SoloSpectator.cs | 2 +- osu.Game/Screens/Spectate/SpectatorScreen.cs | 16 ++++++++++------ 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index 4646f42d63..bbdd7a3d56 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -215,8 +215,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate protected override void StartGameplay(int userId, SpectatorGameplayState spectatorGameplayState) => instances.Single(i => i.UserId == userId).LoadScore(spectatorGameplayState.Score); - protected override void EndGameplay(int userId) + protected override void EndGameplay(int userId, SpectatorState state) { + if (state.State == SpectatingUserState.Completed || state.State == SpectatingUserState.Failed) + return; + RemoveUser(userId); var instance = instances.Single(i => i.UserId == userId); diff --git a/osu.Game/Screens/Play/SoloSpectator.cs b/osu.Game/Screens/Play/SoloSpectator.cs index b530965269..a710db6d24 100644 --- a/osu.Game/Screens/Play/SoloSpectator.cs +++ b/osu.Game/Screens/Play/SoloSpectator.cs @@ -180,7 +180,7 @@ namespace osu.Game.Screens.Play scheduleStart(spectatorGameplayState); } - protected override void EndGameplay(int userId) + protected override void EndGameplay(int userId, SpectatorState state) { scheduledStart?.Cancel(); immediateSpectatorGameplayState = null; diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs index 460352ee07..7b58f669a0 100644 --- a/osu.Game/Screens/Spectate/SpectatorScreen.cs +++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs @@ -118,8 +118,8 @@ namespace osu.Game.Screens.Spectate break; case NotifyDictionaryChangedAction.Remove: - foreach ((int userId, _) in e.OldItems.AsNonNull()) - onUserStateRemoved(userId); + foreach ((int userId, SpectatorState state) in e.OldItems.AsNonNull()) + onUserStateRemoved(userId, state); break; } } @@ -147,7 +147,7 @@ namespace osu.Game.Screens.Spectate } } - private void onUserStateRemoved(int userId) + private void onUserStateRemoved(int userId, SpectatorState state) { if (!userMap.ContainsKey(userId)) return; @@ -158,7 +158,7 @@ namespace osu.Game.Screens.Spectate gameplayState.Score.Replay.HasReceivedAllFrames = true; gameplayStates.Remove(userId); - Schedule(() => EndGameplay(userId)); + Schedule(() => EndGameplay(userId, state)); } private void updateGameplayState(int userId) @@ -212,7 +212,8 @@ namespace osu.Game.Screens.Spectate /// Ends gameplay for a user. /// /// The user to end gameplay for. - protected abstract void EndGameplay(int userId); + /// The final user state. + protected abstract void EndGameplay(int userId, SpectatorState state); /// /// Stops spectating a user. @@ -220,7 +221,10 @@ namespace osu.Game.Screens.Spectate /// The user to stop spectating. protected void RemoveUser(int userId) { - onUserStateRemoved(userId); + if (!userStates.TryGetValue(userId, out var state)) + return; + + onUserStateRemoved(userId, state); users.Remove(userId); userMap.Remove(userId); From 84171962e56faa9b5310450d01d0faedeffd2146 Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Thu, 3 Feb 2022 13:55:04 +0100 Subject: [PATCH 941/996] Change name and add xmldoc --- osu.Android/GameplayScreenRotationLocker.cs | 2 +- osu.Android/OsuGameActivity.cs | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Android/GameplayScreenRotationLocker.cs b/osu.Android/GameplayScreenRotationLocker.cs index 1b7893e5b9..2e83f784d3 100644 --- a/osu.Android/GameplayScreenRotationLocker.cs +++ b/osu.Android/GameplayScreenRotationLocker.cs @@ -27,7 +27,7 @@ namespace osu.Android { gameActivity.RunOnUiThread(() => { - gameActivity.RequestedOrientation = userPlaying.NewValue ? ScreenOrientation.Locked : gameActivity.ScreenOrientation; + gameActivity.RequestedOrientation = userPlaying.NewValue ? ScreenOrientation.Locked : gameActivity.DefaultOrientation; }); } } diff --git a/osu.Android/OsuGameActivity.cs b/osu.Android/OsuGameActivity.cs index 7de597fe88..eebd079f68 100644 --- a/osu.Android/OsuGameActivity.cs +++ b/osu.Android/OsuGameActivity.cs @@ -44,7 +44,11 @@ namespace osu.Android { private static readonly string[] osu_url_schemes = { "osu", "osump" }; - public ScreenOrientation ScreenOrientation = ScreenOrientation.Unspecified; + /// + /// The default screen orientation. + /// + /// Adjusted on startup to match expected UX for the current device type (phone/tablet). + public ScreenOrientation DefaultOrientation = ScreenOrientation.Unspecified; private OsuGameAndroid game; @@ -72,7 +76,7 @@ namespace osu.Android float smallestWidthDp = Math.Min(displaySize.X, displaySize.Y) / Resources.DisplayMetrics.Density; bool isTablet = smallestWidthDp >= 600f; - RequestedOrientation = ScreenOrientation = isTablet ? ScreenOrientation.FullUser : ScreenOrientation.SensorLandscape; + RequestedOrientation = DefaultOrientation = isTablet ? ScreenOrientation.FullUser : ScreenOrientation.SensorLandscape; } protected override void OnNewIntent(Intent intent) => handleIntent(intent); From d4ebff6ea1f381bad406c692ab15b097fbdefd2b Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 3 Feb 2022 23:18:22 +0900 Subject: [PATCH 942/996] Add failing test --- .../Multiplayer/TestSceneMultiplayer.cs | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 3563869d8b..27bf1c209c 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -871,6 +871,52 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("queue is empty", () => this.ChildrenOfType().Single().Items.Count == 0); } + [Test] + public void TestGameplayStartsWhileInSpectatorScreen() + { + createRoom(() => new Room + { + Name = { Value = "Test Room" }, + Playlist = + { + new PlaylistItem + { + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, + Ruleset = { Value = new OsuRuleset().RulesetInfo }, + } + } + }); + + AddStep("join other user and make host", () => + { + client.AddUser(new APIUser { Id = 1234 }); + client.TransferHost(1234); + }); + + AddStep("set local user spectating", () => client.ChangeUserState(API.LocalUser.Value.OnlineID, MultiplayerUserState.Spectating)); + AddUntilStep("wait for spectating state", () => client.LocalUser?.State == MultiplayerUserState.Spectating); + + runGameplay(); + + AddStep("exit gameplay for other user", () => client.ChangeUserState(1234, MultiplayerUserState.Idle)); + AddUntilStep("wait for room to be idle", () => client.Room?.State == MultiplayerRoomState.Open); + + runGameplay(); + + void runGameplay() + { + AddStep("start match by other user", () => + { + client.ChangeUserState(1234, MultiplayerUserState.Ready); + client.StartMatch(); + }); + + AddUntilStep("wait for loading", () => client.Room?.State == MultiplayerRoomState.WaitingForLoad); + AddStep("set player loaded", () => client.ChangeUserState(1234, MultiplayerUserState.Loaded)); + AddUntilStep("wait for gameplay to start", () => client.Room?.State == MultiplayerRoomState.Playing); + } + } + private void enterGameplay() { pressReadyButton(); From 6dc0f3fd960cc5a4fd914123388a0585901e0243 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 3 Feb 2022 18:14:30 +0100 Subject: [PATCH 943/996] Merge difficulty creation methods into one One of them wasn't really doing much anymore and was more obfuscating what was actually happening at this point. --- osu.Game/Beatmaps/BeatmapManager.cs | 14 +++++++++----- osu.Game/Beatmaps/BeatmapModelManager.cs | 13 ------------- 2 files changed, 9 insertions(+), 18 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index d60cfdee9e..633eb8f15e 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -120,15 +120,19 @@ namespace osu.Game.Beatmaps // but cases where this isn't true seem rather rare / pathological. var referenceBeatmap = GetWorkingBeatmap(beatmapSetInfo.Beatmaps.First()); - var newBeatmap = new Beatmap - { - BeatmapInfo = new BeatmapInfo(rulesetInfo, new BeatmapDifficulty(), referenceBeatmap.Metadata.DeepClone()) - }; + var newBeatmapInfo = new BeatmapInfo(rulesetInfo, new BeatmapDifficulty(), referenceBeatmap.Metadata.DeepClone()); + // populate circular beatmap set info <-> beatmap info references manually. + // several places like `BeatmapModelManager.Save()` or `GetWorkingBeatmap()` + // rely on them being freely traversable in both directions for correct operation. + beatmapSetInfo.Beatmaps.Add(newBeatmapInfo); + newBeatmapInfo.BeatmapSet = beatmapSetInfo; + + var newBeatmap = new Beatmap { BeatmapInfo = newBeatmapInfo }; foreach (var timingPoint in referenceBeatmap.Beatmap.ControlPointInfo.TimingPoints) newBeatmap.ControlPointInfo.Add(timingPoint.Time, timingPoint.DeepClone()); - beatmapModelManager.AddDifficultyToBeatmapSet(beatmapSetInfo, newBeatmap); + beatmapModelManager.Save(newBeatmapInfo, newBeatmap); workingBeatmapCache.Invalidate(beatmapSetInfo); return GetWorkingBeatmap(newBeatmap.BeatmapInfo); diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs index b9f0af8833..4c680bbcc9 100644 --- a/osu.Game/Beatmaps/BeatmapModelManager.cs +++ b/osu.Game/Beatmaps/BeatmapModelManager.cs @@ -90,19 +90,6 @@ namespace osu.Game.Beatmaps WorkingBeatmapCache?.Invalidate(beatmapInfo); } - /// - /// Add a new difficulty to the beatmap set represented by the provided . - /// - public void AddDifficultyToBeatmapSet(BeatmapSetInfo beatmapSetInfo, Beatmap beatmap) - { - var beatmapInfo = beatmap.BeatmapInfo; - - beatmapSetInfo.Beatmaps.Add(beatmapInfo); - beatmapInfo.BeatmapSet = beatmapSetInfo; - - Save(beatmapInfo, beatmap); - } - private static string getFilename(BeatmapInfo beatmapInfo) { var metadata = beatmapInfo.Metadata; From ee1feae8062919f2992bd41b23cabd19aa49f345 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Feb 2022 11:06:18 +0900 Subject: [PATCH 944/996] Remove unnecessary ruleset ordering Co-authored-by: Salman Ahmed --- 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 78c5c862f7..5503a62ba2 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -834,7 +834,7 @@ namespace osu.Game.Screens.Edit { var rulesetItems = new List(); - foreach (var ruleset in rulesets.AvailableRulesets.OrderBy(ruleset => ruleset.OnlineID)) + foreach (var ruleset in rulesets.AvailableRulesets) rulesetItems.Add(new EditorMenuItem(ruleset.Name, MenuItemType.Standard, () => CreateNewDifficulty(ruleset))); return new EditorMenuItem("Create new difficulty") { Items = rulesetItems }; From 4728919bcaaa7151f3d3036a629212f8e395f570 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Feb 2022 15:45:27 +0900 Subject: [PATCH 945/996] 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 f89994cd56..04c543750e 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 50cef71b26..83c3593edb 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 9ec0f1c0a0..b0c056ea21 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -60,7 +60,7 @@ - + @@ -83,7 +83,7 @@ - + From 52fdf0349f32a73ad1022a9b5ef26de9c39edc36 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Feb 2022 16:07:05 +0900 Subject: [PATCH 946/996] Add safe area support via `ScalingContainer` --- .../Graphics/Containers/ScalingContainer.cs | 10 +++++++- osu.Game/OsuGameBase.cs | 24 ++++++++++++------- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/osu.Game/Graphics/Containers/ScalingContainer.cs b/osu.Game/Graphics/Containers/ScalingContainer.cs index d2b1e5e523..f9bd571131 100644 --- a/osu.Game/Graphics/Containers/ScalingContainer.cs +++ b/osu.Game/Graphics/Containers/ScalingContainer.cs @@ -101,6 +101,9 @@ namespace osu.Game.Graphics.Containers } } + [Resolved] + private ISafeArea safeArea { get; set; } + [BackgroundDependencyLoader] private void load(OsuConfigManager config) { @@ -118,6 +121,8 @@ namespace osu.Game.Graphics.Containers posY = config.GetBindable(OsuSetting.ScalingPositionY); posY.ValueChanged += _ => updateSize(); + + safeArea.SafeAreaPadding.BindValueChanged(_ => updateSize()); } protected override void LoadComplete() @@ -161,7 +166,10 @@ namespace osu.Game.Graphics.Containers var targetSize = scaling ? new Vector2(sizeX.Value, sizeY.Value) : Vector2.One; var targetPosition = scaling ? new Vector2(posX.Value, posY.Value) * (Vector2.One - targetSize) : Vector2.Zero; - bool requiresMasking = scaling && targetSize != Vector2.One; + bool requiresMasking = scaling && targetSize != Vector2.One + // For the top level scaling container, for now we apply masking if safe areas are in use. + // In the future this can likely be removed as more of the actual UI supports overflowing into the safe areas. + || (targetMode == ScalingMode.Everything && safeArea.SafeAreaPadding.Value.Total != Vector2.Zero); if (requiresMasking) sizableContainer.Masking = true; diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 1713e73905..5f87abcfed 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -299,16 +299,22 @@ namespace osu.Game GlobalActionContainer globalBindings; - var mainContent = new Drawable[] + base.Content.Add(new SafeAreaContainer { - MenuCursorContainer = new MenuCursorContainer { RelativeSizeAxes = Axes.Both }, - // to avoid positional input being blocked by children, ensure the GlobalActionContainer is above everything. - globalBindings = new GlobalActionContainer(this) - }; - - MenuCursorContainer.Child = content = new OsuTooltipContainer(MenuCursorContainer.Cursor) { RelativeSizeAxes = Axes.Both }; - - base.Content.Add(CreateScalingContainer().WithChildren(mainContent)); + RelativeSizeAxes = Axes.Both, + Child = CreateScalingContainer().WithChildren(new Drawable[] + { + (MenuCursorContainer = new MenuCursorContainer + { + RelativeSizeAxes = Axes.Both + }).WithChild(content = new OsuTooltipContainer(MenuCursorContainer.Cursor) + { + RelativeSizeAxes = Axes.Both + }), + // to avoid positional input being blocked by children, ensure the GlobalActionContainer is above everything. + globalBindings = new GlobalActionContainer(this) + }) + }); KeyBindingStore = new RealmKeyBindingStore(realm, keyCombinationProvider); KeyBindingStore.Register(globalBindings, RulesetStore.AvailableRulesets); From 1444df4d50ce0e2d9417f0958d4a39f76ae8e85e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Feb 2022 16:37:11 +0900 Subject: [PATCH 947/996] Add test scene for playing with safe areas --- .../TestSceneSafeAreaHandling.cs | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneSafeAreaHandling.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneSafeAreaHandling.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneSafeAreaHandling.cs new file mode 100644 index 0000000000..676ae1276b --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneSafeAreaHandling.cs @@ -0,0 +1,116 @@ +// 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.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Overlays.Settings; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneSafeAreaHandling : OsuGameTestScene + { + private SafeAreaDefiningContainer safeAreaContainer; + + private static BindableSafeArea safeArea; + + private readonly Bindable safeAreaPaddingTop = new BindableFloat { MinValue = 0, MaxValue = 200 }; + private readonly Bindable safeAreaPaddingBottom = new BindableFloat { MinValue = 0, MaxValue = 200 }; + private readonly Bindable safeAreaPaddingLeft = new BindableFloat { MinValue = 0, MaxValue = 200 }; + private readonly Bindable safeAreaPaddingRight = new BindableFloat { MinValue = 0, MaxValue = 200 }; + + protected override void LoadComplete() + { + base.LoadComplete(); + + // Usually this would be placed between the host and the game, but that's a bit of a pain to do with the test scene hierarchy. + + // Add is required for the container to get a size (and give out correct metrics to the usages in SafeAreaContainer). + Add( + safeAreaContainer = new SafeAreaDefiningContainer(safeArea = new BindableSafeArea()) + { + RelativeSizeAxes = Axes.Both + }); + + // Cache is required for the test game to see the safe area. + Dependencies.CacheAs(safeAreaContainer); + } + + public override void SetUpSteps() + { + AddStep("Add adjust controls", () => + { + Add(new Container + { + Depth = float.MinValue, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both, + Alpha = 0.8f, + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + Width = 400, + Children = new Drawable[] + { + new SettingsSlider + { + Current = safeAreaPaddingTop, + LabelText = "Top" + }, + new SettingsSlider + { + Current = safeAreaPaddingBottom, + LabelText = "Bottom" + }, + new SettingsSlider + { + Current = safeAreaPaddingLeft, + LabelText = "Left" + }, + new SettingsSlider + { + Current = safeAreaPaddingRight, + LabelText = "Right" + }, + } + } + } + }); + + safeAreaPaddingTop.BindValueChanged(_ => updateSafeArea()); + safeAreaPaddingBottom.BindValueChanged(_ => updateSafeArea()); + safeAreaPaddingLeft.BindValueChanged(_ => updateSafeArea()); + safeAreaPaddingRight.BindValueChanged(_ => updateSafeArea()); + }); + + base.SetUpSteps(); + } + + private void updateSafeArea() + { + safeArea.Value = new MarginPadding + { + Top = safeAreaPaddingTop.Value, + Bottom = safeAreaPaddingBottom.Value, + Left = safeAreaPaddingLeft.Value, + Right = safeAreaPaddingRight.Value, + }; + } + + [Test] + public void TestSafeArea() + { + } + } +} From 30d2c7ba6a9355e88a3f605dd68d23728ede247a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Feb 2022 17:07:21 +0900 Subject: [PATCH 948/996] Add parenthesis to disambiguify conditionals --- osu.Game/Graphics/Containers/ScalingContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/ScalingContainer.cs b/osu.Game/Graphics/Containers/ScalingContainer.cs index f9bd571131..aa4e3a7fde 100644 --- a/osu.Game/Graphics/Containers/ScalingContainer.cs +++ b/osu.Game/Graphics/Containers/ScalingContainer.cs @@ -166,7 +166,7 @@ namespace osu.Game.Graphics.Containers var targetSize = scaling ? new Vector2(sizeX.Value, sizeY.Value) : Vector2.One; var targetPosition = scaling ? new Vector2(posX.Value, posY.Value) * (Vector2.One - targetSize) : Vector2.Zero; - bool requiresMasking = scaling && targetSize != Vector2.One + bool requiresMasking = (scaling && targetSize != Vector2.One) // For the top level scaling container, for now we apply masking if safe areas are in use. // In the future this can likely be removed as more of the actual UI supports overflowing into the safe areas. || (targetMode == ScalingMode.Everything && safeArea.SafeAreaPadding.Value.Total != Vector2.Zero); From b41655d5b905cd28425e2851f6900f24f7a82893 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 3 Feb 2022 23:22:08 +0900 Subject: [PATCH 949/996] Fix crash when gameplay starts while in multi-spectator screen --- osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs | 1 + .../OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 27bf1c209c..8f6ba6375f 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -914,6 +914,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for loading", () => client.Room?.State == MultiplayerRoomState.WaitingForLoad); AddStep("set player loaded", () => client.ChangeUserState(1234, MultiplayerUserState.Loaded)); AddUntilStep("wait for gameplay to start", () => client.Room?.State == MultiplayerRoomState.Playing); + AddUntilStep("wait for local user to enter spectator", () => multiplayerComponents.CurrentScreen is MultiSpectatorScreen); } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 4bd68f2034..020217eac6 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -457,6 +457,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer return; } + // The beatmap is queried asynchronously when the selected item changes. + // This is an issue with MultiSpectatorScreen which is effectively in an always "ready" state and receives LoadRequested() callbacks + // even when it is not truly ready (i.e. the beatmap hasn't been selected by the client yet). For the time being, a simple fix to this is to ignore the callback. + // Note that spectator will be entered automatically when the client is capable of doing so via beatmap availability callbacks (see: updateBeatmapAvailability()). + if (client.LocalUser?.State == MultiplayerUserState.Spectating && Beatmap.IsDefault) + return; + StartPlay(); readyClickOperation?.Dispose(); From 0473c6c52f9156954eb7c782a421a540cbc35e5d Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 4 Feb 2022 17:53:30 +0900 Subject: [PATCH 950/996] Also handle null SelectedItem for safety --- .../Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 020217eac6..a397493bab 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -461,7 +461,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer // This is an issue with MultiSpectatorScreen which is effectively in an always "ready" state and receives LoadRequested() callbacks // even when it is not truly ready (i.e. the beatmap hasn't been selected by the client yet). For the time being, a simple fix to this is to ignore the callback. // Note that spectator will be entered automatically when the client is capable of doing so via beatmap availability callbacks (see: updateBeatmapAvailability()). - if (client.LocalUser?.State == MultiplayerUserState.Spectating && Beatmap.IsDefault) + if (client.LocalUser?.State == MultiplayerUserState.Spectating && (SelectedItem.Value == null || Beatmap.IsDefault)) return; StartPlay(); From 8fc4d0c6f51053184b5d8952214aa652cf6f7c1a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Feb 2022 18:20:17 +0900 Subject: [PATCH 951/996] Add override edge rule to overflow above home indicator on iOS --- osu.Game/OsuGameBase.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 5f87abcfed..594e7a10c4 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -301,6 +301,7 @@ namespace osu.Game base.Content.Add(new SafeAreaContainer { + SafeAreaOverrideEdges = Edges.Bottom, RelativeSizeAxes = Axes.Both, Child = CreateScalingContainer().WithChildren(new Drawable[] { From 6457cf8d9b70264e46f757b01116b85ef393c163 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Feb 2022 18:45:38 +0900 Subject: [PATCH 952/996] Fix weird formatting in `TestSceneSafeArea` --- .../Visual/UserInterface/TestSceneSafeAreaHandling.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneSafeAreaHandling.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneSafeAreaHandling.cs index 676ae1276b..8b4e3f6d3a 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneSafeAreaHandling.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneSafeAreaHandling.cs @@ -29,11 +29,10 @@ namespace osu.Game.Tests.Visual.UserInterface // Usually this would be placed between the host and the game, but that's a bit of a pain to do with the test scene hierarchy. // Add is required for the container to get a size (and give out correct metrics to the usages in SafeAreaContainer). - Add( - safeAreaContainer = new SafeAreaDefiningContainer(safeArea = new BindableSafeArea()) - { - RelativeSizeAxes = Axes.Both - }); + Add(safeAreaContainer = new SafeAreaDefiningContainer(safeArea = new BindableSafeArea()) + { + RelativeSizeAxes = Axes.Both + }); // Cache is required for the test game to see the safe area. Dependencies.CacheAs(safeAreaContainer); From 915d63f6dea742d853f7e635c3d18093d30de815 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Feb 2022 18:58:29 +0900 Subject: [PATCH 953/996] Limit safe area bottom override to iOS only --- osu.Game/OsuGameBase.cs | 8 +++++++- osu.iOS/OsuGameIOS.cs | 6 ++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 594e7a10c4..97d2e64072 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -89,6 +89,12 @@ namespace osu.Game } } + /// + /// The that the game should be drawn over at a top level. + /// Defaults to . + /// + public virtual Edges SafeAreaOverrideEdges { get; set; } + protected OsuConfigManager LocalConfig { get; private set; } protected SessionStatics SessionStatics { get; private set; } @@ -301,7 +307,7 @@ namespace osu.Game base.Content.Add(new SafeAreaContainer { - SafeAreaOverrideEdges = Edges.Bottom, + SafeAreaOverrideEdges = SafeAreaOverrideEdges, RelativeSizeAxes = Axes.Both, Child = CreateScalingContainer().WithChildren(new Drawable[] { diff --git a/osu.iOS/OsuGameIOS.cs b/osu.iOS/OsuGameIOS.cs index 702aef45f5..cf14745be1 100644 --- a/osu.iOS/OsuGameIOS.cs +++ b/osu.iOS/OsuGameIOS.cs @@ -3,6 +3,7 @@ using System; using Foundation; +using osu.Framework.Graphics; using osu.Game; using osu.Game.Updater; using osu.Game.Utils; @@ -18,6 +19,11 @@ namespace osu.iOS protected override BatteryInfo CreateBatteryInfo() => new IOSBatteryInfo(); + public override Edges SafeAreaOverrideEdges => + // iOS shows a home indicator at the bottom, and adds a safe area to account for this. + // Because we have the home indicator (mostly) hidden we don't really care about drawing in this region. + Edges.Bottom; + private class IOSBatteryInfo : BatteryInfo { public override double ChargeLevel => Battery.ChargeLevel; From dd63b1a3500be04b494623a51806121364720ba2 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 4 Feb 2022 19:03:52 +0900 Subject: [PATCH 954/996] Fix broken spectator playback test scene --- .../Gameplay/TestSceneSpectatorPlayback.cs | 221 +++++++----------- 1 file changed, 85 insertions(+), 136 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs index 4af254866a..69bf2a7a38 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs @@ -3,12 +3,8 @@ using System; using System.Collections.Generic; -using System.Collections.Specialized; -using System.Diagnostics; using System.Linq; using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -20,7 +16,6 @@ using osu.Framework.Testing; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Graphics.Sprites; -using osu.Game.Online.API; using osu.Game.Online.Spectator; using osu.Game.Replays; using osu.Game.Replays.Legacy; @@ -32,6 +27,7 @@ using osu.Game.Rulesets.Replays.Types; using osu.Game.Rulesets.UI; using osu.Game.Scoring; using osu.Game.Screens.Play; +using osu.Game.Tests.Visual.Spectator; using osu.Game.Tests.Visual.UserInterface; using osuTK; using osuTK.Graphics; @@ -47,138 +43,105 @@ namespace osu.Game.Tests.Visual.Gameplay private Replay replay; - private readonly IBindableList users = new BindableList(); - - private TestReplayRecorder recorder; - private ManualClock manualClock; private OsuSpriteText latencyDisplay; private TestFramedReplayInputHandler replayHandler; - [Resolved] - private IAPIProvider api { get; set; } - - [Resolved] - private SpectatorClient spectatorClient { get; set; } - - [Cached] - private GameplayState gameplayState = new GameplayState(new Beatmap(), new OsuRuleset(), Array.Empty()); - [SetUpSteps] public void SetUpSteps() { - AddStep("Reset recorder state", cleanUpState); - AddStep("Setup containers", () => { replay = new Replay(); manualClock = new ManualClock(); + SpectatorClient spectatorClient; + + Child = new DependencyProvidingContainer + { + RelativeSizeAxes = Axes.Both, + CachedDependencies = new[] + { + (typeof(SpectatorClient), (object)(spectatorClient = new TestSpectatorClient())), + (typeof(GameplayState), new GameplayState(new Beatmap(), new OsuRuleset(), Array.Empty())) + }, + Children = new Drawable[] + { + spectatorClient, + new GridContainer + { + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] + { + recordingManager = new TestRulesetInputManager(TestSceneModSettings.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique) + { + Recorder = new TestReplayRecorder + { + ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos), + }, + Child = new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + Colour = Color4.Brown, + RelativeSizeAxes = Axes.Both, + }, + new OsuSpriteText + { + Text = "Sending", + Scale = new Vector2(3), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + new TestInputConsumer() + } + }, + } + }, + new Drawable[] + { + playbackManager = new TestRulesetInputManager(TestSceneModSettings.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique) + { + Clock = new FramedClock(manualClock), + ReplayInputHandler = replayHandler = new TestFramedReplayInputHandler(replay) + { + GamefieldToScreenSpace = pos => playbackManager.ToScreenSpace(pos), + }, + Child = new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + Colour = Color4.DarkBlue, + RelativeSizeAxes = Axes.Both, + }, + new OsuSpriteText + { + Text = "Receiving", + Scale = new Vector2(3), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + new TestInputConsumer() + } + }, + } + } + } + }, + latencyDisplay = new OsuSpriteText() + } + }; spectatorClient.OnNewFrames += onNewFrames; - - users.BindTo(spectatorClient.PlayingUsers); - users.BindCollectionChanged((obj, args) => - { - switch (args.Action) - { - case NotifyCollectionChangedAction.Add: - Debug.Assert(args.NewItems != null); - - foreach (int user in args.NewItems) - { - if (user == api.LocalUser.Value.Id) - spectatorClient.WatchUser(user); - } - - break; - - case NotifyCollectionChangedAction.Remove: - Debug.Assert(args.OldItems != null); - - foreach (int user in args.OldItems) - { - if (user == api.LocalUser.Value.Id) - spectatorClient.StopWatchingUser(user); - } - - break; - } - }, true); - - Children = new Drawable[] - { - new GridContainer - { - RelativeSizeAxes = Axes.Both, - Content = new[] - { - new Drawable[] - { - recordingManager = new TestRulesetInputManager(TestSceneModSettings.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique) - { - Recorder = recorder = new TestReplayRecorder - { - ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos), - }, - Child = new Container - { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - new Box - { - Colour = Color4.Brown, - RelativeSizeAxes = Axes.Both, - }, - new OsuSpriteText - { - Text = "Sending", - Scale = new Vector2(3), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - new TestInputConsumer() - } - }, - } - }, - new Drawable[] - { - playbackManager = new TestRulesetInputManager(TestSceneModSettings.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique) - { - Clock = new FramedClock(manualClock), - ReplayInputHandler = replayHandler = new TestFramedReplayInputHandler(replay) - { - GamefieldToScreenSpace = pos => playbackManager.ToScreenSpace(pos), - }, - Child = new Container - { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - new Box - { - Colour = Color4.DarkBlue, - RelativeSizeAxes = Axes.Both, - }, - new OsuSpriteText - { - Text = "Receiving", - Scale = new Vector2(3), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - new TestInputConsumer() - } - }, - } - } - } - }, - latencyDisplay = new OsuSpriteText() - }; }); } @@ -238,20 +201,6 @@ namespace osu.Game.Tests.Visual.Gameplay manualClock.CurrentTime = time.Value; } - [TearDownSteps] - public void TearDown() - { - AddStep("stop recorder", cleanUpState); - } - - private void cleanUpState() - { - // Ensure previous recorder is disposed else it may affect the global playing state of `SpectatorClient`. - recorder?.RemoveAndDisposeImmediately(); - recorder = null; - spectatorClient.OnNewFrames -= onNewFrames; - } - public class TestFramedReplayInputHandler : FramedReplayInputHandler { public TestFramedReplayInputHandler(Replay replay) From fa3d1115fa9535d288e9cc74756b3cc68548ed5b Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 4 Feb 2022 19:17:50 +0900 Subject: [PATCH 955/996] Remove online api requirement --- osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs index 69bf2a7a38..a4d8460846 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs @@ -36,8 +36,6 @@ namespace osu.Game.Tests.Visual.Gameplay { public class TestSceneSpectatorPlayback : OsuManualInputManagerTestScene { - protected override bool UseOnlineAPI => true; - private TestRulesetInputManager playbackManager; private TestRulesetInputManager recordingManager; From 503025b970b1df6bb4935c13df7c4f9d82e56036 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Feb 2022 19:19:44 +0900 Subject: [PATCH 956/996] Fix completely incorrect and dangerous usage of bindable binding --- osu.Game/Graphics/Containers/ScalingContainer.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Graphics/Containers/ScalingContainer.cs b/osu.Game/Graphics/Containers/ScalingContainer.cs index aa4e3a7fde..f505a62a33 100644 --- a/osu.Game/Graphics/Containers/ScalingContainer.cs +++ b/osu.Game/Graphics/Containers/ScalingContainer.cs @@ -23,6 +23,8 @@ namespace osu.Game.Graphics.Containers private Bindable posX; private Bindable posY; + private Bindable safeAreaPadding; + private readonly ScalingMode? targetMode; private Bindable scalingMode; @@ -101,11 +103,8 @@ namespace osu.Game.Graphics.Containers } } - [Resolved] - private ISafeArea safeArea { get; set; } - [BackgroundDependencyLoader] - private void load(OsuConfigManager config) + private void load(OsuConfigManager config, ISafeArea safeArea) { scalingMode = config.GetBindable(OsuSetting.Scaling); scalingMode.ValueChanged += _ => updateSize(); @@ -122,7 +121,8 @@ namespace osu.Game.Graphics.Containers posY = config.GetBindable(OsuSetting.ScalingPositionY); posY.ValueChanged += _ => updateSize(); - safeArea.SafeAreaPadding.BindValueChanged(_ => updateSize()); + safeAreaPadding = safeArea.SafeAreaPadding.GetBoundCopy(); + safeAreaPadding.BindValueChanged(_ => updateSize()); } protected override void LoadComplete() @@ -169,7 +169,7 @@ namespace osu.Game.Graphics.Containers bool requiresMasking = (scaling && targetSize != Vector2.One) // For the top level scaling container, for now we apply masking if safe areas are in use. // In the future this can likely be removed as more of the actual UI supports overflowing into the safe areas. - || (targetMode == ScalingMode.Everything && safeArea.SafeAreaPadding.Value.Total != Vector2.Zero); + || (targetMode == ScalingMode.Everything && safeAreaPadding.Value.Total != Vector2.Zero); if (requiresMasking) sizableContainer.Masking = true; From 0f48c0131ca99a6b9f3283ebbd037cd991e181b6 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 4 Feb 2022 15:42:52 +0900 Subject: [PATCH 957/996] Layer playback of beatmap-changed and random-beatmap samples --- osu.Game/Screens/Select/BeatmapCarousel.cs | 4 ++++ osu.Game/Screens/Select/SongSelect.cs | 21 +++------------------ 2 files changed, 7 insertions(+), 18 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index f17daa8697..3d5ed70dda 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -154,6 +154,7 @@ namespace osu.Game.Screens.Select private readonly DrawablePool setPool = new DrawablePool(100); private Sample spinSample; + private Sample randomSelectSample; private int visibleSetsCount; @@ -178,6 +179,7 @@ namespace osu.Game.Screens.Select private void load(OsuConfigManager config, AudioManager audio) { spinSample = audio.Samples.Get("SongSelect/random-spin"); + randomSelectSample = audio.Samples.Get(@"SongSelect/select-random"); config.BindWith(OsuSetting.RandomSelectAlgorithm, RandomAlgorithm); config.BindWith(OsuSetting.SongSelectRightMouseScroll, RightClickScrollingEnabled); @@ -495,6 +497,8 @@ namespace osu.Game.Screens.Select var chan = spinSample.GetChannel(); chan.Frequency.Value = 1f + Math.Min(1f, distance / visibleSetsCount); chan.Play(); + + randomSelectSample?.Play(); } private void select(CarouselItem item) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index ee807762bf..f5b11448f8 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -100,7 +100,6 @@ namespace osu.Game.Screens.Select private Sample sampleChangeDifficulty; private Sample sampleChangeBeatmap; - private Sample sampleRandomBeatmap; private Container carouselContainer; @@ -110,8 +109,6 @@ namespace osu.Game.Screens.Select private double audioFeedbackLastPlaybackTime; - private bool randomSelectionPending; - [Resolved] private MusicController music { get; set; } @@ -291,7 +288,6 @@ namespace osu.Game.Screens.Select sampleChangeDifficulty = audio.Samples.Get(@"SongSelect/select-difficulty"); sampleChangeBeatmap = audio.Samples.Get(@"SongSelect/select-expand"); - sampleRandomBeatmap = audio.Samples.Get(@"SongSelect/select-random"); SampleConfirm = audio.Samples.Get(@"SongSelect/confirm-selection"); if (dialogOverlay != null) @@ -319,16 +315,8 @@ namespace osu.Game.Screens.Select (new FooterButtonMods { Current = Mods }, ModSelect), (new FooterButtonRandom { - NextRandom = () => - { - randomSelectionPending = true; - Carousel.SelectNextRandom(); - }, - PreviousRandom = () => - { - randomSelectionPending = true; - Carousel.SelectPreviousRandom(); - } + NextRandom = () => Carousel.SelectNextRandom(), + PreviousRandom = Carousel.SelectPreviousRandom }, null), (new FooterButtonOptions(), BeatmapOptions) }; @@ -498,9 +486,7 @@ namespace osu.Game.Screens.Select { if (beatmap != null && beatmapInfoPrevious != null && Time.Current - audioFeedbackLastPlaybackTime >= 50) { - if (randomSelectionPending) - sampleRandomBeatmap.Play(); - else if (beatmap.BeatmapSet?.ID == beatmapInfoPrevious.BeatmapSet?.ID) + if (beatmap.BeatmapSet?.ID == beatmapInfoPrevious.BeatmapSet?.ID) sampleChangeDifficulty.Play(); else sampleChangeBeatmap.Play(); @@ -508,7 +494,6 @@ namespace osu.Game.Screens.Select audioFeedbackLastPlaybackTime = Time.Current; } - randomSelectionPending = false; beatmapInfoPrevious = beatmap; } From e2262bf3b25f14f625e76bec34118203299c31d0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Feb 2022 20:33:15 +0900 Subject: [PATCH 958/996] Schedule all calls to `updateSize` for safety --- osu.Game/Graphics/Containers/ScalingContainer.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game/Graphics/Containers/ScalingContainer.cs b/osu.Game/Graphics/Containers/ScalingContainer.cs index f505a62a33..b3423345b1 100644 --- a/osu.Game/Graphics/Containers/ScalingContainer.cs +++ b/osu.Game/Graphics/Containers/ScalingContainer.cs @@ -52,7 +52,7 @@ namespace osu.Game.Graphics.Containers return; allowScaling = value; - if (IsLoaded) updateSize(); + if (IsLoaded) Scheduler.AddOnce(updateSize); } } @@ -107,29 +107,29 @@ namespace osu.Game.Graphics.Containers private void load(OsuConfigManager config, ISafeArea safeArea) { scalingMode = config.GetBindable(OsuSetting.Scaling); - scalingMode.ValueChanged += _ => updateSize(); + scalingMode.ValueChanged += _ => Scheduler.AddOnce(updateSize); sizeX = config.GetBindable(OsuSetting.ScalingSizeX); - sizeX.ValueChanged += _ => updateSize(); + sizeX.ValueChanged += _ => Scheduler.AddOnce(updateSize); sizeY = config.GetBindable(OsuSetting.ScalingSizeY); - sizeY.ValueChanged += _ => updateSize(); + sizeY.ValueChanged += _ => Scheduler.AddOnce(updateSize); posX = config.GetBindable(OsuSetting.ScalingPositionX); - posX.ValueChanged += _ => updateSize(); + posX.ValueChanged += _ => Scheduler.AddOnce(updateSize); posY = config.GetBindable(OsuSetting.ScalingPositionY); - posY.ValueChanged += _ => updateSize(); + posY.ValueChanged += _ => Scheduler.AddOnce(updateSize); safeAreaPadding = safeArea.SafeAreaPadding.GetBoundCopy(); - safeAreaPadding.BindValueChanged(_ => updateSize()); + safeAreaPadding.BindValueChanged(_ => Scheduler.AddOnce(updateSize)); } protected override void LoadComplete() { base.LoadComplete(); - updateSize(); + Scheduler.AddOnce(updateSize); sizableContainer.FinishTransforms(); } From b5dde6f1ad22d1c80502d307dee061556e3e404f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Feb 2022 21:38:52 +0900 Subject: [PATCH 959/996] Update resources --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 04c543750e..71525a7acb 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -51,7 +51,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 83c3593edb..c8f634284b 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -37,7 +37,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index b0c056ea21..5978f6d685 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + From 5e47ce333ce42831d8b4d48fdd4aa4437956587a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 4 Feb 2022 16:10:49 +0300 Subject: [PATCH 960/996] Change `SafeAreaOverrideEdges` to be get-only and protected --- osu.Game/OsuGameBase.cs | 2 +- osu.iOS/OsuGameIOS.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 97d2e64072..5b2eb5607a 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -93,7 +93,7 @@ namespace osu.Game /// The that the game should be drawn over at a top level. /// Defaults to . /// - public virtual Edges SafeAreaOverrideEdges { get; set; } + protected virtual Edges SafeAreaOverrideEdges => Edges.None; protected OsuConfigManager LocalConfig { get; private set; } diff --git a/osu.iOS/OsuGameIOS.cs b/osu.iOS/OsuGameIOS.cs index cf14745be1..9c1795e45e 100644 --- a/osu.iOS/OsuGameIOS.cs +++ b/osu.iOS/OsuGameIOS.cs @@ -19,7 +19,7 @@ namespace osu.iOS protected override BatteryInfo CreateBatteryInfo() => new IOSBatteryInfo(); - public override Edges SafeAreaOverrideEdges => + protected override Edges SafeAreaOverrideEdges => // iOS shows a home indicator at the bottom, and adds a safe area to account for this. // Because we have the home indicator (mostly) hidden we don't really care about drawing in this region. Edges.Bottom; From d62885f30b64cd2cfbac451591b952ff9663a5a9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Feb 2022 22:31:41 +0900 Subject: [PATCH 961/996] Don't schedule call to `updateSize` in `LoadComplete` to ensure following `FinishTransforms` runs as expected Co-authored-by: Salman Ahmed --- osu.Game/Graphics/Containers/ScalingContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/ScalingContainer.cs b/osu.Game/Graphics/Containers/ScalingContainer.cs index b3423345b1..0d543bdbc8 100644 --- a/osu.Game/Graphics/Containers/ScalingContainer.cs +++ b/osu.Game/Graphics/Containers/ScalingContainer.cs @@ -129,7 +129,7 @@ namespace osu.Game.Graphics.Containers { base.LoadComplete(); - Scheduler.AddOnce(updateSize); + updateSize(); sizableContainer.FinishTransforms(); } From 750f90e7288b04f0abe4b7c4abb4805d52d5fda8 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 4 Feb 2022 22:38:40 +0900 Subject: [PATCH 962/996] Simplify TestSpectatorClient implementation --- .../Tests/Visual/Spectator/TestSpectatorClient.cs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs index 1a1d493249..453c086604 100644 --- a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs +++ b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs @@ -6,7 +6,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -40,11 +39,7 @@ namespace osu.Game.Tests.Visual.Spectator public TestSpectatorClient() { - OnNewFrames += (i, bundle) => - { - if (PlayingUsers.Contains(i)) - lastReceivedUserFrames[i] = bundle.Frames[^1]; - }; + OnNewFrames += (i, bundle) => lastReceivedUserFrames[i] = bundle.Frames[^1]; } /// @@ -65,7 +60,7 @@ namespace osu.Game.Tests.Visual.Spectator /// The user to end play for. public void EndPlay(int userId) { - if (!PlayingUsers.Contains(userId)) + if (!userBeatmapDictionary.ContainsKey(userId)) return; ((ISpectatorClient)this).UserFinishedPlaying(userId, new SpectatorState @@ -73,6 +68,8 @@ namespace osu.Game.Tests.Visual.Spectator BeatmapID = userBeatmapDictionary[userId], RulesetID = 0, }); + + userBeatmapDictionary.Remove(userId); } public new void Schedule(Action action) => base.Schedule(action); @@ -131,7 +128,7 @@ namespace osu.Game.Tests.Visual.Spectator protected override Task WatchUserInternal(int userId) { // When newly watching a user, the server sends the playing state immediately. - if (PlayingUsers.Contains(userId)) + if (userBeatmapDictionary.ContainsKey(userId)) sendPlayingState(userId); return Task.CompletedTask; From eb25730b614ececc5f6672e298dc5e786984c92e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 5 Feb 2022 16:12:58 +0900 Subject: [PATCH 963/996] Revert "Merge pull request #16716 from peppy/carousel-less-invalidations" This reverts commit 8d13e0514b27ce5e6f587ba8918c165d3e1ddbbf, reversing changes made to 95582a9023488da37edb3b9236f6b4aeb197ccb9. --- osu.Game/Screens/Select/BeatmapCarousel.cs | 6 +- .../Screens/Select/Carousel/CarouselHeader.cs | 29 ++++---- .../Carousel/DrawableCarouselBeatmap.cs | 10 ++- .../Carousel/DrawableCarouselBeatmapSet.cs | 6 +- .../Select/Carousel/DrawableCarouselItem.cs | 71 +++++++------------ 5 files changed, 54 insertions(+), 68 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 3d5ed70dda..c3d340ac61 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -925,8 +925,10 @@ namespace osu.Game.Screens.Select // child items (difficulties) are still visible. item.Header.X = offsetX(dist, visibleHalfHeight) - (parent?.X ?? 0); - // We are applying alpha to the header here such that we can layer alpha transformations on top. - item.Header.Alpha = Math.Clamp(1.75f - 1.5f * dist, 0, 1); + // We are applying a multiplicative alpha (which is internally done by nesting an + // additional container and setting that container's alpha) such that we can + // layer alpha transformations on top. + item.SetMultiplicativeAlpha(Math.Clamp(1.75f - 1.5f * dist, 0, 1)); } private enum PendingScrollOperation diff --git a/osu.Game/Screens/Select/Carousel/CarouselHeader.cs b/osu.Game/Screens/Select/Carousel/CarouselHeader.cs index 533694b265..ed3aea3445 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselHeader.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselHeader.cs @@ -21,6 +21,8 @@ namespace osu.Game.Screens.Select.Carousel { public class CarouselHeader : Container { + public Container BorderContainer; + public readonly Bindable State = new Bindable(CarouselItemState.NotSelected); private readonly HoverLayer hoverLayer; @@ -35,14 +37,17 @@ namespace osu.Game.Screens.Select.Carousel RelativeSizeAxes = Axes.X; Height = DrawableCarouselItem.MAX_HEIGHT; - Masking = true; - CornerRadius = corner_radius; - BorderColour = new Color4(221, 255, 255, 255); - - InternalChildren = new Drawable[] + InternalChild = BorderContainer = new Container { - Content, - hoverLayer = new HoverLayer() + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = corner_radius, + BorderColour = new Color4(221, 255, 255, 255), + Children = new Drawable[] + { + Content, + hoverLayer = new HoverLayer() + } }; } @@ -61,21 +66,21 @@ namespace osu.Game.Screens.Select.Carousel case CarouselItemState.NotSelected: hoverLayer.InsetForBorder = false; - BorderThickness = 0; - EdgeEffect = new EdgeEffectParameters + BorderContainer.BorderThickness = 0; + BorderContainer.EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Shadow, Offset = new Vector2(1), Radius = 10, - Colour = Color4.Black.Opacity(0.5f), + Colour = Color4.Black.Opacity(100), }; break; case CarouselItemState.Selected: hoverLayer.InsetForBorder = true; - BorderThickness = border_thickness; - EdgeEffect = new EdgeEffectParameters + BorderContainer.BorderThickness = border_thickness; + BorderContainer.EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Glow, Colour = new Color4(130, 204, 255, 150), diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index a3483aa60a..3576b77ae8 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -36,9 +36,9 @@ namespace osu.Game.Screens.Select.Carousel /// /// The height of a carousel beatmap, including vertical spacing. /// - public const float HEIGHT = header_height + CAROUSEL_BEATMAP_SPACING; + public const float HEIGHT = height + CAROUSEL_BEATMAP_SPACING; - private const float header_height = MAX_HEIGHT * 0.6f; + private const float height = MAX_HEIGHT * 0.6f; private readonly BeatmapInfo beatmapInfo; @@ -67,18 +67,16 @@ namespace osu.Game.Screens.Select.Carousel private CancellationTokenSource starDifficultyCancellationSource; public DrawableCarouselBeatmap(CarouselBeatmap panel) - : base(header_height) { beatmapInfo = panel.BeatmapInfo; Item = panel; - - // Difficulty panels should start hidden for a better initial effect. - Hide(); } [BackgroundDependencyLoader(true)] private void load(BeatmapManager manager, SongSelect songSelect) { + Header.Height = height; + if (songSelect != null) { startRequested = b => songSelect.FinaliseSelection(b); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 63c004f4bc..618c5cf5ec 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -122,10 +122,12 @@ namespace osu.Game.Screens.Select.Carousel }, }; - background.DelayedLoadComplete += d => d.FadeInFromZero(750, Easing.OutQuint); - mainFlow.DelayedLoadComplete += d => d.FadeInFromZero(500, Easing.OutQuint); + background.DelayedLoadComplete += fadeContentIn; + mainFlow.DelayedLoadComplete += fadeContentIn; } + private void fadeContentIn(Drawable d) => d.FadeInFromZero(750, Easing.OutQuint); + protected override void Deselected() { base.Deselected(); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs index 5e7ca0825a..cde3edad39 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs @@ -60,10 +60,12 @@ namespace osu.Game.Screens.Select.Carousel } } - protected DrawableCarouselItem(float headerHeight = MAX_HEIGHT) + protected DrawableCarouselItem() { RelativeSizeAxes = Axes.X; + Alpha = 0; + InternalChildren = new Drawable[] { MovementContainer = new Container @@ -71,20 +73,18 @@ namespace osu.Game.Screens.Select.Carousel RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - Header = new CarouselHeader - { - Height = headerHeight, - }, + Header = new CarouselHeader(), Content = new Container { RelativeSizeAxes = Axes.Both, - Y = headerHeight, } } }, }; } + public void SetMultiplicativeAlpha(float alpha) => Header.BorderContainer.Alpha = alpha; + protected override void LoadComplete() { base.LoadComplete(); @@ -92,6 +92,12 @@ namespace osu.Game.Screens.Select.Carousel UpdateItem(); } + protected override void Update() + { + base.Update(); + Content.Y = Header.Height; + } + protected virtual void UpdateItem() { if (item == null) @@ -115,56 +121,29 @@ namespace osu.Game.Screens.Select.Carousel private void onStateChange(ValueChangedEvent _) => Scheduler.AddOnce(ApplyState); - private CarouselItemState? lastAppliedState; - protected virtual void ApplyState() { + // Use the fact that we know the precise height of the item from the model to avoid the need for AutoSize overhead. + // Additionally, AutoSize doesn't work well due to content starting off-screen and being masked away. + Height = Item.TotalHeight; + Debug.Assert(Item != null); - if (lastAppliedState != Item.State.Value) + switch (Item.State.Value) { - lastAppliedState = Item.State.Value; + case CarouselItemState.NotSelected: + Deselected(); + break; - // Use the fact that we know the precise height of the item from the model to avoid the need for AutoSize overhead. - // Additionally, AutoSize doesn't work well due to content starting off-screen and being masked away. - Height = Item.TotalHeight; - - switch (lastAppliedState) - { - case CarouselItemState.NotSelected: - Deselected(); - break; - - case CarouselItemState.Selected: - Selected(); - break; - } + case CarouselItemState.Selected: + Selected(); + break; } if (!Item.Visible) - Hide(); + this.FadeOut(300, Easing.OutQuint); else - Show(); - } - - private bool isVisible = true; - - public override void Show() - { - if (isVisible) - return; - - isVisible = true; - this.FadeIn(250); - } - - public override void Hide() - { - if (!isVisible) - return; - - isVisible = false; - this.FadeOut(300, Easing.OutQuint); + this.FadeIn(250); } protected virtual void Selected() From 2e1a9f137994a3085a592d216c075054126ceb38 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Sat, 5 Feb 2022 21:13:16 +0800 Subject: [PATCH 964/996] Add performance breakdown as statistic item in extended statistics panel --- osu.Game.Rulesets.Osu/OsuRuleset.cs | 11 + .../Statistics/PerformanceBreakdownChart.cs | 244 ++++++++++++++++++ 2 files changed, 255 insertions(+) create mode 100644 osu.Game/Screens/Ranking/Statistics/PerformanceBreakdownChart.cs diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 428e7b9df5..769e83ec00 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -298,6 +298,17 @@ namespace osu.Game.Rulesets.Osu } }, new StatisticRow + { + Columns = new[] + { + new StatisticItem("Performance Breakdown", () => new PerformanceBreakdownChart(score) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y + }), + } + }, + new StatisticRow { Columns = new[] { diff --git a/osu.Game/Screens/Ranking/Statistics/PerformanceBreakdownChart.cs b/osu.Game/Screens/Ranking/Statistics/PerformanceBreakdownChart.cs new file mode 100644 index 0000000000..b58974eb96 --- /dev/null +++ b/osu.Game/Screens/Ranking/Statistics/PerformanceBreakdownChart.cs @@ -0,0 +1,244 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using JetBrains.Annotations; +using osu.Framework.Allocation; +using osu.Framework.Extensions; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Extensions.LocalisationExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets.Difficulty; +using osu.Game.Scoring; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.Ranking.Statistics +{ + public class PerformanceBreakdownChart : Container + { + private readonly ScoreInfo score; + + private Drawable spinner; + private Drawable content; + private GridContainer chart; + private OsuSpriteText achievedPerformance; + private OsuSpriteText maximumPerformance; + + private readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + + [Resolved] + private ScorePerformanceCache performanceCache { get; set; } + + [Resolved] + private BeatmapDifficultyCache difficultyCache { get; set; } + + [Resolved] + private BeatmapManager beatmapManager { get; set; } + + public PerformanceBreakdownChart(ScoreInfo score) + { + this.score = score; + } + + [BackgroundDependencyLoader] + private void load() + { + Children = new[] + { + spinner = new LoadingSpinner(true) + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre + }, + content = new FillFlowContainer + { + Alpha = 0, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Width = 0.6f, + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Spacing = new Vector2(15, 15), + Children = new Drawable[] + { + new GridContainer + { + RelativeSizeAxes = Axes.X, + Width = 0.8f, + AutoSizeAxes = Axes.Y, + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + ColumnDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.AutoSize) + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize) + }, + Content = new[] + { + new Drawable[] + { + new OsuSpriteText + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Font = OsuFont.GetFont(weight: FontWeight.Regular, size: 18), + Text = "Achieved PP", + Colour = Color4Extensions.FromHex("#66FFCC") + }, + achievedPerformance = new OsuSpriteText + { + Origin = Anchor.CentreRight, + Anchor = Anchor.CentreRight, + Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 18), + Colour = Color4Extensions.FromHex("#66FFCC") + } + }, + new Drawable[] + { + new OsuSpriteText + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Font = OsuFont.GetFont(weight: FontWeight.Regular, size: 18), + Text = "Maximum", + Colour = OsuColour.Gray(0.7f) + }, + maximumPerformance = new OsuSpriteText + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Font = OsuFont.GetFont(weight: FontWeight.Regular, size: 18), + Colour = OsuColour.Gray(0.7f) + } + } + } + }, + chart = new GridContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(), + new Dimension(GridSizeMode.AutoSize) + } + } + } + } + }; + + spinner.Show(); + + new PerformanceBreakdownCalculator(beatmapManager, difficultyCache, performanceCache) + .CalculateAsync(score, cancellationTokenSource.Token) + .ContinueWith(t => Schedule(() => setPerformanceValue(t.GetResultSafely()))); + } + + private void setPerformanceValue(PerformanceBreakdown breakdown) + { + spinner.Hide(); + content.FadeIn(200); + + var displayAttributes = breakdown.Performance.GetAttributesForDisplay(); + var perfectDisplayAttributes = breakdown.PerfectPerformance.GetAttributesForDisplay(); + + setTotalValues( + displayAttributes.First(a => a.PropertyName == nameof(PerformanceAttributes.Total)), + perfectDisplayAttributes.First(a => a.PropertyName == nameof(PerformanceAttributes.Total)) + ); + + var rowDimensions = new List(); + var rows = new List(); + + foreach (PerformanceDisplayAttribute attr in displayAttributes) + { + if (attr.PropertyName == nameof(PerformanceAttributes.Total)) continue; + + var row = createAttributeRow(attr, perfectDisplayAttributes.First(a => a.PropertyName == attr.PropertyName)); + + if (row != null) + { + rows.Add(row); + rowDimensions.Add(new Dimension(GridSizeMode.AutoSize)); + } + } + + chart.RowDimensions = rowDimensions.ToArray(); + chart.Content = rows.ToArray(); + } + + private void setTotalValues(PerformanceDisplayAttribute attribute, PerformanceDisplayAttribute perfectAttribute) + { + achievedPerformance.Text = Math.Round(attribute.Value, MidpointRounding.AwayFromZero).ToLocalisableString(); + maximumPerformance.Text = Math.Round(perfectAttribute.Value, MidpointRounding.AwayFromZero).ToLocalisableString(); + } + + [CanBeNull] + private Drawable[] createAttributeRow(PerformanceDisplayAttribute attribute, PerformanceDisplayAttribute perfectAttribute) + { + float percentage = (float)(attribute.Value / perfectAttribute.Value); + if (float.IsNaN(percentage)) + return null; + + return new Drawable[] + { + new OsuSpriteText + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Font = OsuFont.GetFont(weight: FontWeight.Regular), + Text = attribute.DisplayName, + Colour = Colour4.White + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = 10, Right = 10 }, + Child = new Bar + { + RelativeSizeAxes = Axes.X, + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + CornerRadius = 2.5f, + Masking = true, + Height = 5, + BackgroundColour = Color4.White.Opacity(0.5f), + AccentColour = Color4Extensions.FromHex("#66FFCC"), + Length = percentage + } + }, + new OsuSpriteText + { + Origin = Anchor.CentreRight, + Anchor = Anchor.CentreRight, + Font = OsuFont.GetFont(weight: FontWeight.SemiBold), + Text = percentage.ToLocalisableString("0%"), + Colour = Colour4.White + } + }; + } + + protected override void Dispose(bool isDisposing) + { + cancellationTokenSource?.Cancel(); + base.Dispose(isDisposing); + } + } +} From c35ef917a171564750d54c3bee09d24339d27ee2 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Sat, 5 Feb 2022 21:18:23 +0800 Subject: [PATCH 965/996] Remove tooltip from performance statistic --- .../Statistics/PerformanceStatistic.cs | 43 ++-- .../Statistics/PerformanceStatisticTooltip.cs | 212 ------------------ 2 files changed, 14 insertions(+), 241 deletions(-) delete mode 100644 osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs index 158fd82b29..859b42d66d 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs @@ -2,21 +2,17 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Linq; using System.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Cursor; -using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; -using osu.Game.Rulesets.Difficulty; using osu.Game.Scoring; namespace osu.Game.Screens.Ranking.Expanded.Statistics { - public class PerformanceStatistic : StatisticDisplay, IHasCustomTooltip + public class PerformanceStatistic : StatisticDisplay { private readonly ScoreInfo score; @@ -26,15 +22,6 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics private RollingCounter counter; - [Resolved] - private ScorePerformanceCache performanceCache { get; set; } - - [Resolved] - private BeatmapDifficultyCache difficultyCache { get; set; } - - [Resolved] - private BeatmapManager beatmapManager { get; set; } - public PerformanceStatistic(ScoreInfo score) : base("PP") { @@ -42,21 +29,23 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics } [BackgroundDependencyLoader] - private void load() + private void load(ScorePerformanceCache performanceCache) { - new PerformanceBreakdownCalculator(beatmapManager, difficultyCache, performanceCache) - .CalculateAsync(score, cancellationTokenSource.Token) - .ContinueWith(t => Schedule(() => setPerformanceValue(t.GetResultSafely()))); + if (score.PP.HasValue) + { + setPerformanceValue(score.PP.Value); + } + else + { + performanceCache.CalculatePerformanceAsync(score, cancellationTokenSource.Token) + .ContinueWith(t => Schedule(() => setPerformanceValue(t.GetResultSafely().Total)), cancellationTokenSource.Token); + } } - private void setPerformanceValue(PerformanceBreakdown breakdown) + private void setPerformanceValue(double? pp) { - // Don't display the tooltip if "Total" is the only item - if (breakdown != null && breakdown.Performance.GetAttributesForDisplay().Count() > 1) - { - TooltipContent = breakdown; - performance.Value = (int)Math.Round(breakdown.Performance.Total, MidpointRounding.AwayFromZero); - } + if (pp.HasValue) + performance.Value = (int)Math.Round(pp.Value, MidpointRounding.AwayFromZero); } public override void Appear() @@ -76,9 +65,5 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre }; - - public ITooltip GetCustomTooltip() => new PerformanceStatisticTooltip(); - - public PerformanceBreakdown TooltipContent { get; private set; } } } diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs deleted file mode 100644 index bd6eb057c6..0000000000 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs +++ /dev/null @@ -1,212 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Extensions.LocalisationExtensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; -using osu.Game.Rulesets.Difficulty; -using osuTK; -using osuTK.Graphics; - -namespace osu.Game.Screens.Ranking.Expanded.Statistics -{ - public class PerformanceStatisticTooltip : VisibilityContainer, ITooltip - { - private readonly Box background; - private Colour4 titleColor; - private Colour4 textColour; - - protected override Container Content { get; } - - public PerformanceStatisticTooltip() - { - AutoSizeAxes = Axes.Both; - Masking = true; - CornerRadius = 5; - - InternalChildren = new Drawable[] - { - background = new Box - { - RelativeSizeAxes = Axes.Both - }, - Content = new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Padding = new MarginPadding { Left = 10, Right = 10, Top = 5, Bottom = 5 } - }, - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - background.Colour = colours.Gray3; - titleColor = colours.Blue; - textColour = colours.BlueLighter; - } - - protected override void PopIn() => this.FadeIn(200, Easing.OutQuint); - - protected override void PopOut() => this.FadeOut(200, Easing.OutQuint); - - private PerformanceBreakdown currentPerformance; - - public void SetContent(PerformanceBreakdown performance) - { - if (performance == currentPerformance) - return; - - currentPerformance = performance; - - UpdateDisplay(performance); - } - - private Drawable createItemForTotal(PerformanceDisplayAttribute attribute, PerformanceDisplayAttribute perfectAttribute) - { - return - new GridContainer - { - AutoSizeAxes = Axes.Both, - Margin = new MarginPadding { Bottom = 10 }, - ColumnDimensions = new[] - { - new Dimension(GridSizeMode.Absolute, 250), - new Dimension(GridSizeMode.AutoSize) - }, - RowDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.AutoSize) - }, - Content = new[] - { - new Drawable[] - { - new OsuSpriteText - { - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - Font = OsuFont.GetFont(weight: FontWeight.Regular), - Text = attribute.DisplayName, - Colour = titleColor - }, - new OsuSpriteText - { - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - Font = OsuFont.GetFont(weight: FontWeight.SemiBold), - Text = Math.Round(attribute.Value, MidpointRounding.AwayFromZero).ToLocalisableString(), - Colour = titleColor - } - }, - new Drawable[] - { - new OsuSpriteText - { - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - Font = OsuFont.GetFont(weight: FontWeight.Regular), - Text = "Maximum", - Colour = OsuColour.Gray(0.7f) - }, - new OsuSpriteText - { - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - Font = OsuFont.GetFont(weight: FontWeight.Regular), - Text = Math.Round(perfectAttribute.Value, MidpointRounding.AwayFromZero).ToLocalisableString(), - Colour = OsuColour.Gray(0.7f) - } - } - } - }; - } - - private Drawable createItemForAttribute(PerformanceDisplayAttribute attribute, PerformanceDisplayAttribute perfectAttribute) - { - float percentage = (float)(attribute.Value / perfectAttribute.Value); - if (float.IsNaN(percentage)) - return null; - - return new GridContainer - { - AutoSizeAxes = Axes.Both, - ColumnDimensions = new[] - { - new Dimension(GridSizeMode.Absolute, 110), - new Dimension(GridSizeMode.Absolute, 140), - new Dimension(GridSizeMode.AutoSize) - }, - RowDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize) - }, - Content = new[] - { - new Drawable[] - { - new OsuSpriteText - { - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - Font = OsuFont.GetFont(weight: FontWeight.Regular), - Text = attribute.DisplayName, - Colour = textColour - }, - new Bar - { - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - Width = 130, - Height = 5, - BackgroundColour = Color4.White.Opacity(0.5f), - Colour = textColour, - Length = percentage, - Margin = new MarginPadding { Left = 5, Right = 5 } - }, - new OsuSpriteText - { - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - Font = OsuFont.GetFont(weight: FontWeight.SemiBold), - Text = percentage.ToLocalisableString("0%"), - Colour = textColour - } - } - } - }; - } - - protected virtual void UpdateDisplay(PerformanceBreakdown performance) - { - Content.Clear(); - - var displayAttributes = performance.Performance.GetAttributesForDisplay(); - - var perfectDisplayAttributes = performance.PerfectPerformance.GetAttributesForDisplay(); - - foreach (PerformanceDisplayAttribute attr in displayAttributes) - { - var attributeItem = attr.PropertyName == nameof(PerformanceAttributes.Total) - ? createItemForTotal(attr, perfectDisplayAttributes.First(a => a.PropertyName == attr.PropertyName)) - : createItemForAttribute(attr, perfectDisplayAttributes.First(a => a.PropertyName == attr.PropertyName)); - - if (attributeItem != null) - Content.Add(attributeItem); - } - } - - public void Move(Vector2 pos) => Position = pos; - } -} From 440b674bb017f54affd7f201580936ced8a8a126 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Sat, 5 Feb 2022 21:30:35 +0800 Subject: [PATCH 966/996] Add statistic item for mania & taiko --- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 11 +++++++++++ osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 11 +++++++++++ 2 files changed, 22 insertions(+) diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index ffb26b224f..aa9b4d5f1d 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -378,6 +378,17 @@ namespace osu.Game.Rulesets.Mania } }, new StatisticRow + { + Columns = new[] + { + new StatisticItem("Performance Breakdown", () => new PerformanceBreakdownChart(score) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y + }), + } + }, + new StatisticRow { Columns = new[] { diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 21c99c0d2f..37e959f8b4 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -221,6 +221,17 @@ namespace osu.Game.Rulesets.Taiko } }, new StatisticRow + { + Columns = new[] + { + new StatisticItem("Performance Breakdown", () => new PerformanceBreakdownChart(score) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y + }), + } + }, + new StatisticRow { Columns = new[] { From 0b1fef38af1660d6c17c87bdb0c94cff9770cfb0 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Sat, 5 Feb 2022 21:36:34 +0800 Subject: [PATCH 967/996] Use the playable beatmap provided in `CreateStatisticsForScore` --- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 2 +- osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 +- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 2 +- .../PerformanceBreakdownCalculator.cs | 17 ++++++++--------- .../Statistics/PerformanceBreakdownChart.cs | 9 ++++----- 5 files changed, 15 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index aa9b4d5f1d..9027b71069 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -381,7 +381,7 @@ namespace osu.Game.Rulesets.Mania { Columns = new[] { - new StatisticItem("Performance Breakdown", () => new PerformanceBreakdownChart(score) + new StatisticItem("Performance Breakdown", () => new PerformanceBreakdownChart(score, playableBeatmap) { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 769e83ec00..68e91e42a7 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -301,7 +301,7 @@ namespace osu.Game.Rulesets.Osu { Columns = new[] { - new StatisticItem("Performance Breakdown", () => new PerformanceBreakdownChart(score) + new StatisticItem("Performance Breakdown", () => new PerformanceBreakdownChart(score, playableBeatmap) { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 37e959f8b4..ddc594a70a 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -224,7 +224,7 @@ namespace osu.Game.Rulesets.Taiko { Columns = new[] { - new StatisticItem("Performance Breakdown", () => new PerformanceBreakdownChart(score) + new StatisticItem("Performance Breakdown", () => new PerformanceBreakdownChart(score, playableBeatmap) { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y diff --git a/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs b/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs index 46342b237c..d68c2edb40 100644 --- a/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs @@ -16,13 +16,13 @@ namespace osu.Game.Rulesets.Difficulty { public class PerformanceBreakdownCalculator { - private readonly BeatmapManager beatmapManager; + private readonly IBeatmap playableBeatmap; private readonly BeatmapDifficultyCache difficultyCache; private readonly ScorePerformanceCache performanceCache; - public PerformanceBreakdownCalculator(BeatmapManager beatmapManager, BeatmapDifficultyCache difficultyCache, ScorePerformanceCache performanceCache) + public PerformanceBreakdownCalculator(IBeatmap playableBeatmap, BeatmapDifficultyCache difficultyCache, ScorePerformanceCache performanceCache) { - this.beatmapManager = beatmapManager; + this.playableBeatmap = playableBeatmap; this.difficultyCache = difficultyCache; this.performanceCache = performanceCache; } @@ -46,14 +46,13 @@ namespace osu.Game.Rulesets.Difficulty return Task.Run(async () => { Ruleset ruleset = score.Ruleset.CreateInstance(); - IBeatmap beatmap = beatmapManager.GetWorkingBeatmap(score.BeatmapInfo).GetPlayableBeatmap(score.Ruleset, score.Mods); ScoreInfo perfectPlay = score.DeepClone(); perfectPlay.Accuracy = 1; perfectPlay.Passed = true; // calculate max combo var difficulty = await difficultyCache.GetDifficultyAsync( - beatmap.BeatmapInfo, + playableBeatmap.BeatmapInfo, score.Ruleset, score.Mods, cancellationToken @@ -65,10 +64,10 @@ namespace osu.Game.Rulesets.Difficulty perfectPlay.MaxCombo = difficulty.Value.MaxCombo; // create statistics assuming all hit objects have perfect hit result - var statistics = beatmap.HitObjects - .SelectMany(getPerfectHitResults) - .GroupBy(hr => hr, (hr, list) => (hitResult: hr, count: list.Count())) - .ToDictionary(pair => pair.hitResult, pair => pair.count); + var statistics = playableBeatmap.HitObjects + .SelectMany(getPerfectHitResults) + .GroupBy(hr => hr, (hr, list) => (hitResult: hr, count: list.Count())) + .ToDictionary(pair => pair.hitResult, pair => pair.count); perfectPlay.Statistics = statistics; // calculate total score diff --git a/osu.Game/Screens/Ranking/Statistics/PerformanceBreakdownChart.cs b/osu.Game/Screens/Ranking/Statistics/PerformanceBreakdownChart.cs index b58974eb96..fb9c93f0e6 100644 --- a/osu.Game/Screens/Ranking/Statistics/PerformanceBreakdownChart.cs +++ b/osu.Game/Screens/Ranking/Statistics/PerformanceBreakdownChart.cs @@ -26,6 +26,7 @@ namespace osu.Game.Screens.Ranking.Statistics public class PerformanceBreakdownChart : Container { private readonly ScoreInfo score; + private readonly IBeatmap playableBeatmap; private Drawable spinner; private Drawable content; @@ -41,12 +42,10 @@ namespace osu.Game.Screens.Ranking.Statistics [Resolved] private BeatmapDifficultyCache difficultyCache { get; set; } - [Resolved] - private BeatmapManager beatmapManager { get; set; } - - public PerformanceBreakdownChart(ScoreInfo score) + public PerformanceBreakdownChart(ScoreInfo score, IBeatmap playableBeatmap) { this.score = score; + this.playableBeatmap = playableBeatmap; } [BackgroundDependencyLoader] @@ -146,7 +145,7 @@ namespace osu.Game.Screens.Ranking.Statistics spinner.Show(); - new PerformanceBreakdownCalculator(beatmapManager, difficultyCache, performanceCache) + new PerformanceBreakdownCalculator(playableBeatmap, difficultyCache, performanceCache) .CalculateAsync(score, cancellationTokenSource.Token) .ContinueWith(t => Schedule(() => setPerformanceValue(t.GetResultSafely()))); } From ee6d4b25836d8051e4802b02c68075c28e2494b4 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Sat, 5 Feb 2022 21:39:01 +0800 Subject: [PATCH 968/996] Move performance breakdown to the top to prevent re-ordering after watching replay --- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 12 ++++++------ osu.Game.Rulesets.Osu/OsuRuleset.cs | 22 +++++++++++----------- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 12 ++++++------ 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 9027b71069..180b9ef71b 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -370,22 +370,22 @@ namespace osu.Game.Rulesets.Mania { Columns = new[] { - new StatisticItem("Timing Distribution", () => new HitEventTimingDistributionGraph(score.HitEvents) + new StatisticItem("Performance Breakdown", () => new PerformanceBreakdownChart(score, playableBeatmap) { RelativeSizeAxes = Axes.X, - Height = 250 - }, true), + AutoSizeAxes = Axes.Y + }), } }, new StatisticRow { Columns = new[] { - new StatisticItem("Performance Breakdown", () => new PerformanceBreakdownChart(score, playableBeatmap) + new StatisticItem("Timing Distribution", () => new HitEventTimingDistributionGraph(score.HitEvents) { RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y - }), + Height = 250 + }, true), } }, new StatisticRow diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 68e91e42a7..ad00a025a1 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -275,6 +275,17 @@ namespace osu.Game.Rulesets.Osu return new[] { + new StatisticRow + { + Columns = new[] + { + new StatisticItem("Performance Breakdown", () => new PerformanceBreakdownChart(score, playableBeatmap) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y + }), + } + }, new StatisticRow { Columns = new[] @@ -298,17 +309,6 @@ namespace osu.Game.Rulesets.Osu } }, new StatisticRow - { - Columns = new[] - { - new StatisticItem("Performance Breakdown", () => new PerformanceBreakdownChart(score, playableBeatmap) - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y - }), - } - }, - new StatisticRow { Columns = new[] { diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index ddc594a70a..e56aabaf9d 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -213,22 +213,22 @@ namespace osu.Game.Rulesets.Taiko { Columns = new[] { - new StatisticItem("Timing Distribution", () => new HitEventTimingDistributionGraph(timedHitEvents) + new StatisticItem("Performance Breakdown", () => new PerformanceBreakdownChart(score, playableBeatmap) { RelativeSizeAxes = Axes.X, - Height = 250 - }, true), + AutoSizeAxes = Axes.Y + }), } }, new StatisticRow { Columns = new[] { - new StatisticItem("Performance Breakdown", () => new PerformanceBreakdownChart(score, playableBeatmap) + new StatisticItem("Timing Distribution", () => new HitEventTimingDistributionGraph(timedHitEvents) { RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y - }), + Height = 250 + }, true), } }, new StatisticRow From b31c1513f6ef0e7c17e7a3af39bb19174084255a Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Sat, 5 Feb 2022 22:41:04 +0800 Subject: [PATCH 969/996] Fix test failure The cursor was clicking too far to the right. --- osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index 988f429ff5..167acc94c4 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs @@ -130,7 +130,7 @@ namespace osu.Game.Tests.Visual.Ranking AddStep("click to right of panel", () => { var expandedPanel = this.ChildrenOfType().Single(p => p.State == PanelState.Expanded); - InputManager.MoveMouseTo(expandedPanel.ScreenSpaceDrawQuad.TopRight + new Vector2(100, 0)); + InputManager.MoveMouseTo(expandedPanel.ScreenSpaceDrawQuad.TopRight + new Vector2(50, 0)); InputManager.Click(MouseButton.Left); }); From f78c853bc752f33d5fff6688fd286182e8ecd476 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Sun, 6 Feb 2022 10:59:53 +0800 Subject: [PATCH 970/996] Calculate max combo locally in `PerformanceBreakdownCalculator` --- .../PerformanceBreakdownCalculator.cs | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs b/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs index d68c2edb40..87e60fbe9d 100644 --- a/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs @@ -51,17 +51,7 @@ namespace osu.Game.Rulesets.Difficulty perfectPlay.Passed = true; // calculate max combo - var difficulty = await difficultyCache.GetDifficultyAsync( - playableBeatmap.BeatmapInfo, - score.Ruleset, - score.Mods, - cancellationToken - ).ConfigureAwait(false); - - if (difficulty == null) - return null; - - perfectPlay.MaxCombo = difficulty.Value.MaxCombo; + perfectPlay.MaxCombo = calculateMaxCombo(playableBeatmap); // create statistics assuming all hit objects have perfect hit result var statistics = playableBeatmap.HitObjects @@ -86,11 +76,23 @@ namespace osu.Game.Rulesets.Difficulty } // calculate performance for this perfect score + var difficulty = await difficultyCache.GetDifficultyAsync( + playableBeatmap.BeatmapInfo, + score.Ruleset, + score.Mods, + cancellationToken + ).ConfigureAwait(false); + // ScorePerformanceCache is not used to avoid caching multiple copies of essentially identical perfect performance attributes - return ruleset.CreatePerformanceCalculator(difficulty.Value.Attributes, perfectPlay)?.Calculate(); + return difficulty == null ? null : ruleset.CreatePerformanceCalculator(difficulty.Value.Attributes, perfectPlay)?.Calculate(); }, cancellationToken); } + private int calculateMaxCombo(IBeatmap beatmap) + { + return beatmap.HitObjects.SelectMany(getPerfectHitResults).Count(r => r.AffectsCombo()); + } + private IEnumerable getPerfectHitResults(HitObject hitObject) { foreach (HitObject nested in hitObject.NestedHitObjects) From 56c90a21ceec78749b3b76f26c46c54cb3533169 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Sun, 6 Feb 2022 11:22:12 +0800 Subject: [PATCH 971/996] Add a todo --- osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs b/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs index 87e60fbe9d..3d384f5914 100644 --- a/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs @@ -51,6 +51,7 @@ namespace osu.Game.Rulesets.Difficulty perfectPlay.Passed = true; // calculate max combo + // todo: Get max combo from difficulty calculator instead when diffcalc properly supports lazer-first scores perfectPlay.MaxCombo = calculateMaxCombo(playableBeatmap); // create statistics assuming all hit objects have perfect hit result From c2416c490e3b81c1fe7aa61c90200f9757fed7c8 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 8 Feb 2022 11:29:39 +0900 Subject: [PATCH 972/996] Fix crash on disconnection during multi-spectate --- .../OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index 4646f42d63..bff7023102 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Diagnostics; using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; @@ -228,7 +227,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate public override bool OnBackButton() { - Debug.Assert(multiplayerClient.Room != null); + if (multiplayerClient.Room == null) + return base.OnBackButton(); // On a manual exit, set the player back to idle unless gameplay has finished. if (multiplayerClient.Room.State != MultiplayerRoomState.Open) From 10bdb7240ff3615f53cb7a0ce61b88f5b3d3428c Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Tue, 8 Feb 2022 14:36:29 +0800 Subject: [PATCH 973/996] Pre-check for divisor zero and add explanation --- .../Ranking/Statistics/PerformanceBreakdownChart.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/PerformanceBreakdownChart.cs b/osu.Game/Screens/Ranking/Statistics/PerformanceBreakdownChart.cs index fb9c93f0e6..5b42554716 100644 --- a/osu.Game/Screens/Ranking/Statistics/PerformanceBreakdownChart.cs +++ b/osu.Game/Screens/Ranking/Statistics/PerformanceBreakdownChart.cs @@ -12,6 +12,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -192,10 +193,13 @@ namespace osu.Game.Screens.Ranking.Statistics [CanBeNull] private Drawable[] createAttributeRow(PerformanceDisplayAttribute attribute, PerformanceDisplayAttribute perfectAttribute) { - float percentage = (float)(attribute.Value / perfectAttribute.Value); - if (float.IsNaN(percentage)) + // Don't display the attribute if its maximum is 0 + // For example, flashlight bonus would be zero if flashlight mod isn't on + if (Precision.AlmostEquals(perfectAttribute.Value, 0f)) return null; + float percentage = (float)(attribute.Value / perfectAttribute.Value); + return new Drawable[] { new OsuSpriteText From 9c2d57d70783cdc71c8a8ee5439dcbc5eec41073 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 8 Feb 2022 19:36:16 +0900 Subject: [PATCH 974/996] Add failing test --- .../Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index 94e61eaee0..343fc7e6e0 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -92,7 +92,8 @@ namespace osu.Game.Tests.Online AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true)); AddUntilStep("wait for import", () => beatmaps.CurrentImport != null); - addAvailabilityCheckStep("state locally available", BeatmapAvailability.LocallyAvailable); + AddAssert("ensure beatmap available", () => beatmaps.IsAvailableLocally(testBeatmapSet)); + addAvailabilityCheckStep("state is locally available", BeatmapAvailability.LocallyAvailable); } [Test] From b1a73996ba69de0cd1e02c9cb25266d52d29e2b7 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 8 Feb 2022 19:36:41 +0900 Subject: [PATCH 975/996] Fix incorrect check for beatmap availability --- osu.Game/Stores/BeatmapImporter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Stores/BeatmapImporter.cs b/osu.Game/Stores/BeatmapImporter.cs index 709dd67087..2a2786b861 100644 --- a/osu.Game/Stores/BeatmapImporter.cs +++ b/osu.Game/Stores/BeatmapImporter.cs @@ -165,7 +165,7 @@ namespace osu.Game.Stores public override bool IsAvailableLocally(BeatmapSetInfo model) { - return Realm.Run(realm => realm.All().Any(b => b.OnlineID == model.OnlineID)); + return Realm.Run(realm => realm.All().Any(b => b.OnlineID == model.OnlineID)); } public override string HumanisedModelName => "beatmap"; From 0d99017178af1b373212f4174242e7bf8ee224a3 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 8 Feb 2022 20:27:08 +0900 Subject: [PATCH 976/996] Add state tests --- .../Visual/Gameplay/TestSceneSpectator.cs | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index 9f8470446c..65583919d6 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -235,6 +235,71 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("last frame has header", () => lastBundle.Frames[^1].Header != null); } + [Test] + public void TestPlayingState() + { + loadSpectatingScreen(); + + start(); + sendFrames(); + waitForPlayer(); + AddUntilStep("state is playing", () => spectatorClient.WatchingUserStates[streamingUser.Id].State == SpectatingUserState.Playing); + } + + [Test] + public void TestCompletionState() + { + loadSpectatingScreen(); + + start(); + sendFrames(); + waitForPlayer(); + + AddStep("send completion", () => spectatorClient.EndPlay(streamingUser.Id, SpectatingUserState.Completed)); + AddUntilStep("state is completed", () => spectatorClient.WatchingUserStates[streamingUser.Id].State == SpectatingUserState.Completed); + + start(); + sendFrames(); + waitForPlayer(); + AddUntilStep("state is playing", () => spectatorClient.WatchingUserStates[streamingUser.Id].State == SpectatingUserState.Playing); + } + + [Test] + public void TestQuitState() + { + loadSpectatingScreen(); + + start(); + sendFrames(); + waitForPlayer(); + + AddStep("send quit", () => spectatorClient.EndPlay(streamingUser.Id, SpectatingUserState.Quit)); + AddUntilStep("state is quit", () => spectatorClient.WatchingUserStates[streamingUser.Id].State == SpectatingUserState.Quit); + + start(); + sendFrames(); + waitForPlayer(); + AddUntilStep("state is playing", () => spectatorClient.WatchingUserStates[streamingUser.Id].State == SpectatingUserState.Playing); + } + + [Test] + public void TestFailedState() + { + loadSpectatingScreen(); + + start(); + sendFrames(); + waitForPlayer(); + + AddStep("send failed", () => spectatorClient.EndPlay(streamingUser.Id, SpectatingUserState.Failed)); + AddUntilStep("state is failed", () => spectatorClient.WatchingUserStates[streamingUser.Id].State == SpectatingUserState.Failed); + + start(); + sendFrames(); + waitForPlayer(); + AddUntilStep("state is playing", () => spectatorClient.WatchingUserStates[streamingUser.Id].State == SpectatingUserState.Playing); + } + private OsuFramedReplayInputHandler replayHandler => (OsuFramedReplayInputHandler)Stack.ChildrenOfType().First().ReplayInputHandler; From 4c76027178933d9bcade478495f0588746220a18 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 8 Feb 2022 20:29:49 +0900 Subject: [PATCH 977/996] Rename completed state to passed --- osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs | 6 +++--- osu.Game/Online/Spectator/SpectatingUserState.cs | 10 +++++----- osu.Game/Online/Spectator/SpectatorClient.cs | 2 +- .../Multiplayer/Spectate/MultiSpectatorScreen.cs | 2 +- osu.Game/Screens/Spectate/SpectatorScreen.cs | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index 65583919d6..a263bb1e6f 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -247,7 +247,7 @@ namespace osu.Game.Tests.Visual.Gameplay } [Test] - public void TestCompletionState() + public void TestPassedState() { loadSpectatingScreen(); @@ -255,8 +255,8 @@ namespace osu.Game.Tests.Visual.Gameplay sendFrames(); waitForPlayer(); - AddStep("send completion", () => spectatorClient.EndPlay(streamingUser.Id, SpectatingUserState.Completed)); - AddUntilStep("state is completed", () => spectatorClient.WatchingUserStates[streamingUser.Id].State == SpectatingUserState.Completed); + AddStep("send passed", () => spectatorClient.EndPlay(streamingUser.Id, SpectatingUserState.Passed)); + AddUntilStep("state is passed", () => spectatorClient.WatchingUserStates[streamingUser.Id].State == SpectatingUserState.Passed); start(); sendFrames(); diff --git a/osu.Game/Online/Spectator/SpectatingUserState.cs b/osu.Game/Online/Spectator/SpectatingUserState.cs index c7ba4ba248..30be57b739 100644 --- a/osu.Game/Online/Spectator/SpectatingUserState.cs +++ b/osu.Game/Online/Spectator/SpectatingUserState.cs @@ -6,7 +6,7 @@ namespace osu.Game.Online.Spectator public enum SpectatingUserState { /// - /// The spectated user has not yet played. + /// The spectated user is not yet playing. /// Idle, @@ -16,17 +16,17 @@ namespace osu.Game.Online.Spectator Playing, /// - /// The spectated user has successfully completed gameplay. + /// The spectated user has passed gameplay. /// - Completed, + Passed, /// - /// The spectator user has failed during gameplay. + /// The spectator user has failed gameplay. /// Failed, /// - /// The spectated user has quit during gameplay. + /// The spectated user has quit gameplay. /// Quit } diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index 9e168411b0..ee5fdc917a 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -186,7 +186,7 @@ namespace osu.Game.Online.Spectator currentBeatmap = null; if (state.HasPassed) - currentState.State = SpectatingUserState.Completed; + currentState.State = SpectatingUserState.Passed; else if (state.HasFailed) currentState.State = SpectatingUserState.Failed; else diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index 125e4b261c..60bf87ff61 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -216,7 +216,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate protected override void EndGameplay(int userId, SpectatorState state) { - if (state.State == SpectatingUserState.Completed || state.State == SpectatingUserState.Failed) + if (state.State == SpectatingUserState.Passed || state.State == SpectatingUserState.Failed) return; RemoveUser(userId); diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs index 7b58f669a0..394afb3a4c 100644 --- a/osu.Game/Screens/Spectate/SpectatorScreen.cs +++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs @@ -134,7 +134,7 @@ namespace osu.Game.Screens.Spectate switch (newState.State) { - case SpectatingUserState.Completed: + case SpectatingUserState.Passed: // Make sure that gameplay completes to the end. if (gameplayStates.TryGetValue(userId, out var gameplayState)) gameplayState.Score.Replay.HasReceivedAllFrames = true; From c1766d8a411f1626255c1d74fbf0b634e7bc4610 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 8 Feb 2022 20:29:53 +0900 Subject: [PATCH 978/996] Add paused state --- osu.Game/Online/Spectator/SpectatingUserState.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Online/Spectator/SpectatingUserState.cs b/osu.Game/Online/Spectator/SpectatingUserState.cs index 30be57b739..e00c2e1947 100644 --- a/osu.Game/Online/Spectator/SpectatingUserState.cs +++ b/osu.Game/Online/Spectator/SpectatingUserState.cs @@ -15,6 +15,11 @@ namespace osu.Game.Online.Spectator /// Playing, + /// + /// The spectated user is currently paused. Unused for the time being. + /// + Paused, + /// /// The spectated user has passed gameplay. /// From 79d1d54e336c2cb0eff7399912933eb32f24b19e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Feb 2022 20:35:38 +0900 Subject: [PATCH 979/996] Rename parameter to match other usages --- osu.Game/Stores/BeatmapImporter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Stores/BeatmapImporter.cs b/osu.Game/Stores/BeatmapImporter.cs index 2a2786b861..e6b655589c 100644 --- a/osu.Game/Stores/BeatmapImporter.cs +++ b/osu.Game/Stores/BeatmapImporter.cs @@ -165,7 +165,7 @@ namespace osu.Game.Stores public override bool IsAvailableLocally(BeatmapSetInfo model) { - return Realm.Run(realm => realm.All().Any(b => b.OnlineID == model.OnlineID)); + return Realm.Run(realm => realm.All().Any(s => s.OnlineID == model.OnlineID)); } public override string HumanisedModelName => "beatmap"; From 886d1d2df637dcbac1e3b7147b3062ba576ce178 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 8 Feb 2022 21:20:33 +0900 Subject: [PATCH 980/996] Refactorings --- .../Visual/Gameplay/TestSceneSpectator.cs | 3 ++- osu.Game/Online/Spectator/SpectatorClient.cs | 2 +- .../Dashboard/CurrentlyPlayingDisplay.cs | 20 +++++++++---------- .../Spectate/MultiSpectatorScreen.cs | 2 ++ osu.Game/Screens/Play/GameplayState.cs | 2 +- 5 files changed, 16 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index a263bb1e6f..e0946df2dc 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -160,7 +160,8 @@ namespace osu.Game.Tests.Visual.Gameplay finish(SpectatingUserState.Failed); checkPaused(false); // Should continue playing until out of frames - checkPaused(true); + checkPaused(true); // And eventually stop after running out of frames and fail. + // Todo: Should check for + display a failed message. } [Test] diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index ee5fdc917a..ed6e355ddf 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -98,8 +98,8 @@ namespace osu.Game.Online.Spectator } else { - watchingUserStates.Clear(); playingUsers.Clear(); + watchingUserStates.Clear(); } }), true); } diff --git a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs index 02ef28f825..117de88166 100644 --- a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs +++ b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs @@ -69,17 +69,17 @@ namespace osu.Game.Overlays.Dashboard { var user = task.GetResultSafely(); - if (user != null) - { - Schedule(() => - { - // user may no longer be playing. - if (!playingUsers.Contains(user.Id)) - return; + if (user == null) + return; - userFlow.Add(createUserPanel(user)); - }); - } + Schedule(() => + { + // user may no longer be playing. + if (!playingUsers.Contains(user.Id)) + return; + + userFlow.Add(createUserPanel(user)); + }); }); } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index 60bf87ff61..55df8301da 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -216,6 +216,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate protected override void EndGameplay(int userId, SpectatorState state) { + // Allowed passed/failed users to complete their remaining replay frames. + // The failed state isn't really possible in multiplayer (yet?) but is added here just for safety in case it starts being used. if (state.State == SpectatingUserState.Passed || state.State == SpectatingUserState.Failed) return; diff --git a/osu.Game/Screens/Play/GameplayState.cs b/osu.Game/Screens/Play/GameplayState.cs index 64e873b3bb..c6a072da74 100644 --- a/osu.Game/Screens/Play/GameplayState.cs +++ b/osu.Game/Screens/Play/GameplayState.cs @@ -50,7 +50,7 @@ namespace osu.Game.Screens.Play public bool HasFailed { get; set; } /// - /// Whether the user quit gameplay without either having either passed or failed. + /// Whether the user quit gameplay without having either passed or failed. /// public bool HasQuit { get; set; } From c242a63b1177b4e772fc52dff5b4eb2030bd48b9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Feb 2022 10:16:43 +0900 Subject: [PATCH 981/996] Fix playlist overlay null reference when attempting an empty selection As reported at https://github.com/ppy/osu/discussions/16829. --- osu.Game/Overlays/Music/PlaylistOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs index 59ade0918d..ce816f84f0 100644 --- a/osu.Game/Overlays/Music/PlaylistOverlay.cs +++ b/osu.Game/Overlays/Music/PlaylistOverlay.cs @@ -85,7 +85,7 @@ namespace osu.Game.Overlays.Music filter.Search.OnCommit += (sender, newText) => { - list.FirstVisibleSet.PerformRead(set => + list.FirstVisibleSet?.PerformRead(set => { BeatmapInfo toSelect = set.Beatmaps.FirstOrDefault(); From 4966c4e974c9a3e7b98910adeee5b658d6889e22 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 9 Feb 2022 11:51:47 +0900 Subject: [PATCH 982/996] Remove redundant parameter --- osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index e0946df2dc..7235bce789 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -274,7 +274,7 @@ namespace osu.Game.Tests.Visual.Gameplay sendFrames(); waitForPlayer(); - AddStep("send quit", () => spectatorClient.EndPlay(streamingUser.Id, SpectatingUserState.Quit)); + AddStep("send quit", () => spectatorClient.EndPlay(streamingUser.Id)); AddUntilStep("state is quit", () => spectatorClient.WatchingUserStates[streamingUser.Id].State == SpectatingUserState.Quit); start(); From ffc4c64f7e7fb0951c9c3f3109497b37f50196b3 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 9 Feb 2022 12:09:04 +0900 Subject: [PATCH 983/996] Unify namings across the board --- .../Visual/Gameplay/TestSceneSpectator.cs | 22 ++++++------ .../Visual/Gameplay/TestSceneSpectatorHost.cs | 4 +-- ...TestSceneMultiplayerGameplayLeaderboard.cs | 2 +- ...tingUserState.cs => SpectatedUserState.cs} | 4 +-- osu.Game/Online/Spectator/SpectatorClient.cs | 36 +++++++++---------- osu.Game/Online/Spectator/SpectatorState.cs | 2 +- .../Spectate/MultiSpectatorScreen.cs | 2 +- osu.Game/Screens/Spectate/SpectatorScreen.cs | 6 ++-- .../Visual/Spectator/TestSpectatorClient.cs | 4 +-- 9 files changed, 41 insertions(+), 41 deletions(-) rename osu.Game/Online/Spectator/{SpectatingUserState.cs => SpectatedUserState.cs} (90%) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index 7235bce789..157c248d69 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -157,7 +157,7 @@ namespace osu.Game.Tests.Visual.Gameplay checkPaused(true); sendFrames(); - finish(SpectatingUserState.Failed); + finish(SpectatedUserState.Failed); checkPaused(false); // Should continue playing until out of frames checkPaused(true); // And eventually stop after running out of frames and fail. @@ -244,7 +244,7 @@ namespace osu.Game.Tests.Visual.Gameplay start(); sendFrames(); waitForPlayer(); - AddUntilStep("state is playing", () => spectatorClient.WatchingUserStates[streamingUser.Id].State == SpectatingUserState.Playing); + AddUntilStep("state is playing", () => spectatorClient.WatchedUserStates[streamingUser.Id].State == SpectatedUserState.Playing); } [Test] @@ -256,13 +256,13 @@ namespace osu.Game.Tests.Visual.Gameplay sendFrames(); waitForPlayer(); - AddStep("send passed", () => spectatorClient.EndPlay(streamingUser.Id, SpectatingUserState.Passed)); - AddUntilStep("state is passed", () => spectatorClient.WatchingUserStates[streamingUser.Id].State == SpectatingUserState.Passed); + AddStep("send passed", () => spectatorClient.EndPlay(streamingUser.Id, SpectatedUserState.Passed)); + AddUntilStep("state is passed", () => spectatorClient.WatchedUserStates[streamingUser.Id].State == SpectatedUserState.Passed); start(); sendFrames(); waitForPlayer(); - AddUntilStep("state is playing", () => spectatorClient.WatchingUserStates[streamingUser.Id].State == SpectatingUserState.Playing); + AddUntilStep("state is playing", () => spectatorClient.WatchedUserStates[streamingUser.Id].State == SpectatedUserState.Playing); } [Test] @@ -275,12 +275,12 @@ namespace osu.Game.Tests.Visual.Gameplay waitForPlayer(); AddStep("send quit", () => spectatorClient.EndPlay(streamingUser.Id)); - AddUntilStep("state is quit", () => spectatorClient.WatchingUserStates[streamingUser.Id].State == SpectatingUserState.Quit); + AddUntilStep("state is quit", () => spectatorClient.WatchedUserStates[streamingUser.Id].State == SpectatedUserState.Quit); start(); sendFrames(); waitForPlayer(); - AddUntilStep("state is playing", () => spectatorClient.WatchingUserStates[streamingUser.Id].State == SpectatingUserState.Playing); + AddUntilStep("state is playing", () => spectatorClient.WatchedUserStates[streamingUser.Id].State == SpectatedUserState.Playing); } [Test] @@ -292,13 +292,13 @@ namespace osu.Game.Tests.Visual.Gameplay sendFrames(); waitForPlayer(); - AddStep("send failed", () => spectatorClient.EndPlay(streamingUser.Id, SpectatingUserState.Failed)); - AddUntilStep("state is failed", () => spectatorClient.WatchingUserStates[streamingUser.Id].State == SpectatingUserState.Failed); + AddStep("send failed", () => spectatorClient.EndPlay(streamingUser.Id, SpectatedUserState.Failed)); + AddUntilStep("state is failed", () => spectatorClient.WatchedUserStates[streamingUser.Id].State == SpectatedUserState.Failed); start(); sendFrames(); waitForPlayer(); - AddUntilStep("state is playing", () => spectatorClient.WatchingUserStates[streamingUser.Id].State == SpectatingUserState.Playing); + AddUntilStep("state is playing", () => spectatorClient.WatchedUserStates[streamingUser.Id].State == SpectatedUserState.Playing); } private OsuFramedReplayInputHandler replayHandler => @@ -313,7 +313,7 @@ namespace osu.Game.Tests.Visual.Gameplay private void start(int? beatmapId = null) => AddStep("start play", () => spectatorClient.StartPlay(streamingUser.Id, beatmapId ?? importedBeatmapId)); - private void finish(SpectatingUserState state = SpectatingUserState.Quit) => AddStep("end play", () => spectatorClient.EndPlay(streamingUser.Id, state)); + private void finish(SpectatedUserState state = SpectatedUserState.Quit) => AddStep("end play", () => spectatorClient.EndPlay(streamingUser.Id, state)); private void checkPaused(bool state) => AddUntilStep($"game is {(state ? "paused" : "playing")}", () => player.ChildrenOfType().First().IsPaused.Value == state); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorHost.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorHost.cs index 6d6b0bf89e..034519fbf8 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorHost.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorHost.cs @@ -37,8 +37,8 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestClientSendsCorrectRuleset() { - AddUntilStep("spectator client sending frames", () => spectatorClient.WatchingUserStates.ContainsKey(dummy_user_id)); - AddAssert("spectator client sent correct ruleset", () => spectatorClient.WatchingUserStates[dummy_user_id].RulesetID == Ruleset.Value.OnlineID); + AddUntilStep("spectator client sending frames", () => spectatorClient.WatchedUserStates.ContainsKey(dummy_user_id)); + AddAssert("spectator client sent correct ruleset", () => spectatorClient.WatchedUserStates[dummy_user_id].RulesetID == Ruleset.Value.OnlineID); } public override void TearDownSteps() diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs index 55450b36e2..1322fbc96e 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs @@ -115,7 +115,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public void RandomlyUpdateState() { - foreach ((int userId, _) in WatchingUserStates) + foreach ((int userId, _) in WatchedUserStates) { if (RNG.NextBool()) continue; diff --git a/osu.Game/Online/Spectator/SpectatingUserState.cs b/osu.Game/Online/Spectator/SpectatedUserState.cs similarity index 90% rename from osu.Game/Online/Spectator/SpectatingUserState.cs rename to osu.Game/Online/Spectator/SpectatedUserState.cs index e00c2e1947..0f0a3068b8 100644 --- a/osu.Game/Online/Spectator/SpectatingUserState.cs +++ b/osu.Game/Online/Spectator/SpectatedUserState.cs @@ -3,7 +3,7 @@ namespace osu.Game.Online.Spectator { - public enum SpectatingUserState + public enum SpectatedUserState { /// /// The spectated user is not yet playing. @@ -26,7 +26,7 @@ namespace osu.Game.Online.Spectator Passed, /// - /// The spectator user has failed gameplay. + /// The spectated user has failed gameplay. /// Failed, diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index ed6e355ddf..a54ea0d9ee 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -38,7 +38,7 @@ namespace osu.Game.Online.Spectator /// /// The states of all users currently being watched. /// - public IBindableDictionary WatchingUserStates => watchingUserStates; + public IBindableDictionary WatchedUserStates => watchedUserStates; /// /// A global list of all players currently playing. @@ -48,9 +48,9 @@ namespace osu.Game.Online.Spectator /// /// All users currently being watched. /// - private readonly List watchingUsers = new List(); + private readonly List watchedUsers = new List(); - private readonly BindableDictionary watchingUserStates = new BindableDictionary(); + private readonly BindableDictionary watchedUserStates = new BindableDictionary(); private readonly BindableList playingUsers = new BindableList(); private readonly SpectatorState currentState = new SpectatorState(); @@ -85,8 +85,8 @@ namespace osu.Game.Online.Spectator if (connected.NewValue) { // get all the users that were previously being watched - int[] users = watchingUsers.ToArray(); - watchingUsers.Clear(); + int[] users = watchedUsers.ToArray(); + watchedUsers.Clear(); // resubscribe to watched users. foreach (int userId in users) @@ -99,7 +99,7 @@ namespace osu.Game.Online.Spectator else { playingUsers.Clear(); - watchingUserStates.Clear(); + watchedUserStates.Clear(); } }), true); } @@ -111,8 +111,8 @@ namespace osu.Game.Online.Spectator if (!playingUsers.Contains(userId)) playingUsers.Add(userId); - if (watchingUsers.Contains(userId)) - watchingUserStates[userId] = state; + if (watchedUsers.Contains(userId)) + watchedUserStates[userId] = state; OnUserBeganPlaying?.Invoke(userId, state); }); @@ -126,8 +126,8 @@ namespace osu.Game.Online.Spectator { playingUsers.Remove(userId); - if (watchingUsers.Contains(userId)) - watchingUserStates[userId] = state; + if (watchedUsers.Contains(userId)) + watchedUserStates[userId] = state; OnUserFinishedPlaying?.Invoke(userId, state); }); @@ -159,7 +159,7 @@ namespace osu.Game.Online.Spectator currentState.BeatmapID = score.ScoreInfo.BeatmapInfo.OnlineID; currentState.RulesetID = score.ScoreInfo.RulesetID; currentState.Mods = score.ScoreInfo.Mods.Select(m => new APIMod(m)).ToArray(); - currentState.State = SpectatingUserState.Playing; + currentState.State = SpectatedUserState.Playing; currentBeatmap = state.Beatmap; currentScore = score; @@ -186,11 +186,11 @@ namespace osu.Game.Online.Spectator currentBeatmap = null; if (state.HasPassed) - currentState.State = SpectatingUserState.Passed; + currentState.State = SpectatedUserState.Passed; else if (state.HasFailed) - currentState.State = SpectatingUserState.Failed; + currentState.State = SpectatedUserState.Failed; else - currentState.State = SpectatingUserState.Quit; + currentState.State = SpectatedUserState.Quit; EndPlayingInternal(currentState); }); @@ -200,10 +200,10 @@ namespace osu.Game.Online.Spectator { Debug.Assert(ThreadSafety.IsUpdateThread); - if (watchingUsers.Contains(userId)) + if (watchedUsers.Contains(userId)) return; - watchingUsers.Add(userId); + watchedUsers.Add(userId); WatchUserInternal(userId); } @@ -214,8 +214,8 @@ namespace osu.Game.Online.Spectator // Todo: This should not be a thing, but requires framework changes. Schedule(() => { - watchingUsers.Remove(userId); - watchingUserStates.Remove(userId); + watchedUsers.Remove(userId); + watchedUserStates.Remove(userId); StopWatchingUserInternal(userId); }); } diff --git a/osu.Game/Online/Spectator/SpectatorState.cs b/osu.Game/Online/Spectator/SpectatorState.cs index fc62f16bba..77686d12da 100644 --- a/osu.Game/Online/Spectator/SpectatorState.cs +++ b/osu.Game/Online/Spectator/SpectatorState.cs @@ -25,7 +25,7 @@ namespace osu.Game.Online.Spectator public IEnumerable Mods { get; set; } = Enumerable.Empty(); [Key(3)] - public SpectatingUserState State { get; set; } + public SpectatedUserState State { get; set; } public bool Equals(SpectatorState other) { diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index 55df8301da..571b6b4324 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -218,7 +218,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { // Allowed passed/failed users to complete their remaining replay frames. // The failed state isn't really possible in multiplayer (yet?) but is added here just for safety in case it starts being used. - if (state.State == SpectatingUserState.Passed || state.State == SpectatingUserState.Failed) + if (state.State == SpectatedUserState.Passed || state.State == SpectatedUserState.Failed) return; RemoveUser(userId); diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs index 394afb3a4c..9d0f8abddc 100644 --- a/osu.Game/Screens/Spectate/SpectatorScreen.cs +++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs @@ -77,7 +77,7 @@ namespace osu.Game.Screens.Spectate userMap[u.Id] = u; } - userStates.BindTo(spectatorClient.WatchingUserStates); + userStates.BindTo(spectatorClient.WatchedUserStates); userStates.BindCollectionChanged(onUserStatesChanged, true); realmSubscription = realm.RegisterForNotifications( @@ -134,13 +134,13 @@ namespace osu.Game.Screens.Spectate switch (newState.State) { - case SpectatingUserState.Passed: + case SpectatedUserState.Passed: // Make sure that gameplay completes to the end. if (gameplayStates.TryGetValue(userId, out var gameplayState)) gameplayState.Score.Replay.HasReceivedAllFrames = true; break; - case SpectatingUserState.Playing: + case SpectatedUserState.Playing: Schedule(() => OnUserStateChanged(userId, newState)); updateGameplayState(userId); break; diff --git a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs index 6862cda88c..1322a99ea7 100644 --- a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs +++ b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs @@ -59,7 +59,7 @@ namespace osu.Game.Tests.Visual.Spectator /// /// The user to end play for. /// The spectator state to end play with. - public void EndPlay(int userId, SpectatingUserState state = SpectatingUserState.Quit) + public void EndPlay(int userId, SpectatedUserState state = SpectatedUserState.Quit) { if (!userBeatmapDictionary.ContainsKey(userId)) return; @@ -144,7 +144,7 @@ namespace osu.Game.Tests.Visual.Spectator { BeatmapID = userBeatmapDictionary[userId], RulesetID = 0, - State = SpectatingUserState.Playing + State = SpectatedUserState.Playing }); } } From 18251c9285a559ac3f4070cdc8538211175e18ab Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 9 Feb 2022 12:20:07 +0900 Subject: [PATCH 984/996] Clean up SpectatorScreen based on suggestions --- .../Multiplayer/Spectate/MultiSpectatorScreen.cs | 2 +- osu.Game/Screens/Play/SoloSpectator.cs | 2 +- osu.Game/Screens/Spectate/SpectatorScreen.cs | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index 571b6b4324..e5eeeb3448 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -207,7 +207,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate } } - protected override void OnUserStateChanged(int userId, SpectatorState spectatorState) + protected override void OnNewPlayingUserState(int userId, SpectatorState spectatorState) { } diff --git a/osu.Game/Screens/Play/SoloSpectator.cs b/osu.Game/Screens/Play/SoloSpectator.cs index a710db6d24..a0b07fcbd9 100644 --- a/osu.Game/Screens/Play/SoloSpectator.cs +++ b/osu.Game/Screens/Play/SoloSpectator.cs @@ -166,7 +166,7 @@ namespace osu.Game.Screens.Play automaticDownload.Current.BindValueChanged(_ => checkForAutomaticDownload()); } - protected override void OnUserStateChanged(int userId, SpectatorState spectatorState) + protected override void OnNewPlayingUserState(int userId, SpectatorState spectatorState) { clearDisplay(); showBeatmapPanel(spectatorState); diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs index 9d0f8abddc..9eb374f0f7 100644 --- a/osu.Game/Screens/Spectate/SpectatorScreen.cs +++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs @@ -103,7 +103,7 @@ namespace osu.Game.Screens.Spectate continue; if (beatmapSet.Beatmaps.Any(b => b.OnlineID == userState.BeatmapID)) - updateGameplayState(userId); + startGameplay(userId); } } @@ -141,8 +141,8 @@ namespace osu.Game.Screens.Spectate break; case SpectatedUserState.Playing: - Schedule(() => OnUserStateChanged(userId, newState)); - updateGameplayState(userId); + Schedule(() => OnNewPlayingUserState(userId, newState)); + startGameplay(userId); break; } } @@ -161,7 +161,7 @@ namespace osu.Game.Screens.Spectate Schedule(() => EndGameplay(userId, state)); } - private void updateGameplayState(int userId) + private void startGameplay(int userId) { Debug.Assert(userMap.ContainsKey(userId)); @@ -195,11 +195,11 @@ namespace osu.Game.Screens.Spectate } /// - /// Invoked when a spectated user's state has changed. + /// Invoked when a spectated user's state has changed to a new state indicating the player is currently playing. /// /// The user whose state has changed. /// The new state. - protected abstract void OnUserStateChanged(int userId, [NotNull] SpectatorState spectatorState); + protected abstract void OnNewPlayingUserState(int userId, [NotNull] SpectatorState spectatorState); /// /// Starts gameplay for a user. From 1b8ada087d97ffdf8b62697a678ddaf8a8c4c654 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Wed, 9 Feb 2022 19:25:51 +0800 Subject: [PATCH 985/996] Set `NoDefaultExcludes` to true This allows files such as .editorconfig and .gitignore to be included in the nupkg --- Templates/osu.Game.Templates.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/Templates/osu.Game.Templates.csproj b/Templates/osu.Game.Templates.csproj index 31a24a301f..4624d3d771 100644 --- a/Templates/osu.Game.Templates.csproj +++ b/Templates/osu.Game.Templates.csproj @@ -15,6 +15,7 @@ true false content + true From f1c6fdb2afce097de6a636cfff97aef2b337e70f Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Wed, 9 Feb 2022 19:53:34 +0800 Subject: [PATCH 986/996] Update `.editorconfig`, `.gitignore` and DotSettings Basically just copied from root directory --- .../Rulesets/ruleset-empty/.editorconfig | 20 +-- Templates/Rulesets/ruleset-empty/.gitignore | 128 +++++++++++++----- ...ame.Rulesets.EmptyFreeform.sln.DotSettings | 83 +++++++++++- .../Rulesets/ruleset-example/.editorconfig | 20 +-- Templates/Rulesets/ruleset-example/.gitignore | 128 +++++++++++++----- ...osu.Game.Rulesets.Pippidon.sln.DotSettings | 83 +++++++++++- .../ruleset-scrolling-empty/.editorconfig | 20 +-- .../ruleset-scrolling-empty/.gitignore | 128 +++++++++++++----- ...me.Rulesets.EmptyScrolling.sln.DotSettings | 83 +++++++++++- .../ruleset-scrolling-example/.editorconfig | 20 +-- .../ruleset-scrolling-example/.gitignore | 128 +++++++++++++----- ...osu.Game.Rulesets.Pippidon.sln.DotSettings | 83 +++++++++++- 12 files changed, 696 insertions(+), 228 deletions(-) diff --git a/Templates/Rulesets/ruleset-empty/.editorconfig b/Templates/Rulesets/ruleset-empty/.editorconfig index f3badda9b3..b01749195f 100644 --- a/Templates/Rulesets/ruleset-empty/.editorconfig +++ b/Templates/Rulesets/ruleset-empty/.editorconfig @@ -10,14 +10,6 @@ trim_trailing_whitespace = true #Roslyn naming styles -#PascalCase for public and protected members -dotnet_naming_style.pascalcase.capitalization = pascal_case -dotnet_naming_symbols.public_members.applicable_accessibilities = public,internal,protected,protected_internal,private_protected -dotnet_naming_symbols.public_members.applicable_kinds = property,method,field,event -dotnet_naming_rule.public_members_pascalcase.severity = error -dotnet_naming_rule.public_members_pascalcase.symbols = public_members -dotnet_naming_rule.public_members_pascalcase.style = pascalcase - #camelCase for private members dotnet_naming_style.camelcase.capitalization = camel_case @@ -121,7 +113,7 @@ dotnet_style_qualification_for_event = false:warning dotnet_style_predefined_type_for_locals_parameters_members = true:warning dotnet_style_predefined_type_for_member_access = true:warning csharp_style_var_when_type_is_apparent = true:none -csharp_style_var_for_built_in_types = true:none +csharp_style_var_for_built_in_types = false:warning csharp_style_var_elsewhere = true:silent #Style - modifiers @@ -165,7 +157,7 @@ csharp_style_unused_value_assignment_preference = discard_variable:warning #Style - variable declaration csharp_style_inlined_variable_declaration = true:warning -csharp_style_deconstructed_variable_declaration = true:warning +csharp_style_deconstructed_variable_declaration = false:silent #Style - other C# 7.x features dotnet_style_prefer_inferred_tuple_names = true:warning @@ -176,8 +168,8 @@ dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent #Style - C# 8 features csharp_prefer_static_local_function = true:warning csharp_prefer_simple_using_statement = true:silent -csharp_style_prefer_index_operator = true:warning -csharp_style_prefer_range_operator = true:warning +csharp_style_prefer_index_operator = false:silent +csharp_style_prefer_range_operator = false:silent csharp_style_prefer_switch_expression = false:none #Supressing roslyn built-in analyzers @@ -197,4 +189,6 @@ dotnet_diagnostic.IDE0069.severity = none dotnet_diagnostic.CA2225.severity = none # Banned APIs -dotnet_diagnostic.RS0030.severity = error \ No newline at end of file +dotnet_diagnostic.RS0030.severity = error + +dotnet_diagnostic.OLOC001.license_header = // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.\n// See the LICENCE file in the repository root for full licence text. diff --git a/Templates/Rulesets/ruleset-empty/.gitignore b/Templates/Rulesets/ruleset-empty/.gitignore index 940794e60f..5b19270ab9 100644 --- a/Templates/Rulesets/ruleset-empty/.gitignore +++ b/Templates/Rulesets/ruleset-empty/.gitignore @@ -1,7 +1,5 @@ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. -## -## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore # User-specific files *.suo @@ -17,8 +15,6 @@ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ -x64/ -x86/ bld/ [Bb]in/ [Oo]bj/ @@ -42,11 +38,10 @@ TestResult.xml [Rr]eleasePS/ dlldata.c -# .NET Core +# DNX project.lock.json project.fragment.lock.json artifacts/ -**/Properties/launchSettings.json *_i.c *_p.c @@ -113,10 +108,6 @@ _TeamCity* # DotCover is a Code Coverage Tool *.dotCover -# Visual Studio code coverage results -*.coverage -*.coveragexml - # NCrunch _NCrunch_* .*crunch*.local.xml @@ -166,7 +157,7 @@ PublishScripts/ !**/packages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/packages/repositories.config -# NuGet v3's project.json files produces more ignorable files +# NuGet v3's project.json files produces more ignoreable files *.nuget.props *.nuget.targets @@ -196,10 +187,11 @@ ClientBin/ *~ *.dbmdl *.dbproj.schemaview -*.jfm *.pfx *.publishsettings +node_modules/ orleans.codegen.cs +Resource.designer.cs # Since there are multiple workflows, uncomment next line to ignore bower_components # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) @@ -219,7 +211,6 @@ UpgradeLog*.htm # SQL Server files *.mdf *.ldf -*.ndf # Business Intelligence projects *.rdl.data @@ -234,10 +225,6 @@ FakesAssemblies/ # Node.js Tools for Visual Studio .ntvs_analysis.dat -node_modules/ - -# Typescript v1 declaration files -typings/ # Visual Studio 6 build log *.plg @@ -245,9 +232,6 @@ typings/ # Visual Studio 6 workspace options file *.opt -# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) -*.vbw - # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts @@ -263,26 +247,96 @@ paket-files/ # FAKE - F# Make .fake/ -# JetBrains Rider -.idea/ -*.sln.iml - -# CodeRush -.cr/ - # Python Tools for Visual Studio (PTVS) __pycache__/ *.pyc -# Cake - Uncomment if you are using it -# tools/** -# !tools/packages.config +# Cake # +/tools/** +/build/tools/** +/build/temp/** -# Telerik's JustMock configuration file -*.jmconfig +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 -# BizTalk build output -*.btp.cs -*.btm.cs -*.odx.cs -*.xsd.cs +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +.idea/modules.xml +.idea/*.iml +.idea/modules +*.iml +*.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +# fastlane +fastlane/report.xml + +# inspectcode +inspectcodereport.xml +inspectcode + +# BenchmarkDotNet +/BenchmarkDotNet.Artifacts + +*.GeneratedMSBuildEditorConfig.editorconfig + +# Fody (pulled in by Realm) - schema file +FodyWeavers.xsd +**/FodyWeavers.xml diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.sln.DotSettings b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.sln.DotSettings index aa8f8739c1..2ff0f4d30b 100644 --- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.sln.DotSettings +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.sln.DotSettings @@ -18,9 +18,10 @@ WARNING HINT DO_NOT_SHOW - HINT - WARNING - WARNING + WARNING + WARNING + HINT + HINT WARNING WARNING WARNING @@ -73,6 +74,7 @@ HINT WARNING HINT + DO_NOT_SHOW WARNING DO_NOT_SHOW WARNING @@ -105,8 +107,9 @@ HINT HINT WARNING + DO_NOT_SHOW + DO_NOT_SHOW WARNING - DO_NOT_SHOW WARNING WARNING WARNING @@ -120,6 +123,7 @@ WARNING WARNING HINT + HINT WARNING HINT HINT @@ -129,7 +133,7 @@ HINT WARNING WARNING - HINT + WARNING WARNING WARNING WARNING @@ -204,8 +208,10 @@ HINT WARNING WARNING - DO_NOT_SHOW + SUGGESTION DO_NOT_SHOW + + True DO_NOT_SHOW WARNING WARNING @@ -226,6 +232,7 @@ HINT DO_NOT_SHOW WARNING + WARNING WARNING WARNING WARNING @@ -298,15 +305,21 @@ True 200 CHOP_IF_LONG + UseExplicitType + UseVarWhenEvident + UseVarWhenEvident False False AABB API BPM + EF + FPS GC GL GLSL HID + HSV HTML HUD ID @@ -909,26 +922,82 @@ private void load() { $END$ }; + True True True + True True True True True + True + True + True + True + True + True + True + True True + True + True + True True + True + True + True True True + True + True + True + True + True True True + True + True True True + True + True + True + True + True + True + True + True True True + True + True + True + True + True + True + True + True True + True + True + True True True True + True + True + True + True + True + True True True - True + True + True + True + True + True + True + True + True + True diff --git a/Templates/Rulesets/ruleset-example/.editorconfig b/Templates/Rulesets/ruleset-example/.editorconfig index f3badda9b3..b01749195f 100644 --- a/Templates/Rulesets/ruleset-example/.editorconfig +++ b/Templates/Rulesets/ruleset-example/.editorconfig @@ -10,14 +10,6 @@ trim_trailing_whitespace = true #Roslyn naming styles -#PascalCase for public and protected members -dotnet_naming_style.pascalcase.capitalization = pascal_case -dotnet_naming_symbols.public_members.applicable_accessibilities = public,internal,protected,protected_internal,private_protected -dotnet_naming_symbols.public_members.applicable_kinds = property,method,field,event -dotnet_naming_rule.public_members_pascalcase.severity = error -dotnet_naming_rule.public_members_pascalcase.symbols = public_members -dotnet_naming_rule.public_members_pascalcase.style = pascalcase - #camelCase for private members dotnet_naming_style.camelcase.capitalization = camel_case @@ -121,7 +113,7 @@ dotnet_style_qualification_for_event = false:warning dotnet_style_predefined_type_for_locals_parameters_members = true:warning dotnet_style_predefined_type_for_member_access = true:warning csharp_style_var_when_type_is_apparent = true:none -csharp_style_var_for_built_in_types = true:none +csharp_style_var_for_built_in_types = false:warning csharp_style_var_elsewhere = true:silent #Style - modifiers @@ -165,7 +157,7 @@ csharp_style_unused_value_assignment_preference = discard_variable:warning #Style - variable declaration csharp_style_inlined_variable_declaration = true:warning -csharp_style_deconstructed_variable_declaration = true:warning +csharp_style_deconstructed_variable_declaration = false:silent #Style - other C# 7.x features dotnet_style_prefer_inferred_tuple_names = true:warning @@ -176,8 +168,8 @@ dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent #Style - C# 8 features csharp_prefer_static_local_function = true:warning csharp_prefer_simple_using_statement = true:silent -csharp_style_prefer_index_operator = true:warning -csharp_style_prefer_range_operator = true:warning +csharp_style_prefer_index_operator = false:silent +csharp_style_prefer_range_operator = false:silent csharp_style_prefer_switch_expression = false:none #Supressing roslyn built-in analyzers @@ -197,4 +189,6 @@ dotnet_diagnostic.IDE0069.severity = none dotnet_diagnostic.CA2225.severity = none # Banned APIs -dotnet_diagnostic.RS0030.severity = error \ No newline at end of file +dotnet_diagnostic.RS0030.severity = error + +dotnet_diagnostic.OLOC001.license_header = // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.\n// See the LICENCE file in the repository root for full licence text. diff --git a/Templates/Rulesets/ruleset-example/.gitignore b/Templates/Rulesets/ruleset-example/.gitignore index 940794e60f..5b19270ab9 100644 --- a/Templates/Rulesets/ruleset-example/.gitignore +++ b/Templates/Rulesets/ruleset-example/.gitignore @@ -1,7 +1,5 @@ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. -## -## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore # User-specific files *.suo @@ -17,8 +15,6 @@ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ -x64/ -x86/ bld/ [Bb]in/ [Oo]bj/ @@ -42,11 +38,10 @@ TestResult.xml [Rr]eleasePS/ dlldata.c -# .NET Core +# DNX project.lock.json project.fragment.lock.json artifacts/ -**/Properties/launchSettings.json *_i.c *_p.c @@ -113,10 +108,6 @@ _TeamCity* # DotCover is a Code Coverage Tool *.dotCover -# Visual Studio code coverage results -*.coverage -*.coveragexml - # NCrunch _NCrunch_* .*crunch*.local.xml @@ -166,7 +157,7 @@ PublishScripts/ !**/packages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/packages/repositories.config -# NuGet v3's project.json files produces more ignorable files +# NuGet v3's project.json files produces more ignoreable files *.nuget.props *.nuget.targets @@ -196,10 +187,11 @@ ClientBin/ *~ *.dbmdl *.dbproj.schemaview -*.jfm *.pfx *.publishsettings +node_modules/ orleans.codegen.cs +Resource.designer.cs # Since there are multiple workflows, uncomment next line to ignore bower_components # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) @@ -219,7 +211,6 @@ UpgradeLog*.htm # SQL Server files *.mdf *.ldf -*.ndf # Business Intelligence projects *.rdl.data @@ -234,10 +225,6 @@ FakesAssemblies/ # Node.js Tools for Visual Studio .ntvs_analysis.dat -node_modules/ - -# Typescript v1 declaration files -typings/ # Visual Studio 6 build log *.plg @@ -245,9 +232,6 @@ typings/ # Visual Studio 6 workspace options file *.opt -# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) -*.vbw - # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts @@ -263,26 +247,96 @@ paket-files/ # FAKE - F# Make .fake/ -# JetBrains Rider -.idea/ -*.sln.iml - -# CodeRush -.cr/ - # Python Tools for Visual Studio (PTVS) __pycache__/ *.pyc -# Cake - Uncomment if you are using it -# tools/** -# !tools/packages.config +# Cake # +/tools/** +/build/tools/** +/build/temp/** -# Telerik's JustMock configuration file -*.jmconfig +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 -# BizTalk build output -*.btp.cs -*.btm.cs -*.odx.cs -*.xsd.cs +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +.idea/modules.xml +.idea/*.iml +.idea/modules +*.iml +*.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +# fastlane +fastlane/report.xml + +# inspectcode +inspectcodereport.xml +inspectcode + +# BenchmarkDotNet +/BenchmarkDotNet.Artifacts + +*.GeneratedMSBuildEditorConfig.editorconfig + +# Fody (pulled in by Realm) - schema file +FodyWeavers.xsd +**/FodyWeavers.xml diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.sln.DotSettings b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.sln.DotSettings index aa8f8739c1..2ff0f4d30b 100644 --- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.sln.DotSettings +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.sln.DotSettings @@ -18,9 +18,10 @@ WARNING HINT DO_NOT_SHOW - HINT - WARNING - WARNING + WARNING + WARNING + HINT + HINT WARNING WARNING WARNING @@ -73,6 +74,7 @@ HINT WARNING HINT + DO_NOT_SHOW WARNING DO_NOT_SHOW WARNING @@ -105,8 +107,9 @@ HINT HINT WARNING + DO_NOT_SHOW + DO_NOT_SHOW WARNING - DO_NOT_SHOW WARNING WARNING WARNING @@ -120,6 +123,7 @@ WARNING WARNING HINT + HINT WARNING HINT HINT @@ -129,7 +133,7 @@ HINT WARNING WARNING - HINT + WARNING WARNING WARNING WARNING @@ -204,8 +208,10 @@ HINT WARNING WARNING - DO_NOT_SHOW + SUGGESTION DO_NOT_SHOW + + True DO_NOT_SHOW WARNING WARNING @@ -226,6 +232,7 @@ HINT DO_NOT_SHOW WARNING + WARNING WARNING WARNING WARNING @@ -298,15 +305,21 @@ True 200 CHOP_IF_LONG + UseExplicitType + UseVarWhenEvident + UseVarWhenEvident False False AABB API BPM + EF + FPS GC GL GLSL HID + HSV HTML HUD ID @@ -909,26 +922,82 @@ private void load() { $END$ }; + True True True + True True True True True + True + True + True + True + True + True + True + True True + True + True + True True + True + True + True True True + True + True + True + True + True True True + True + True True True + True + True + True + True + True + True + True + True True True + True + True + True + True + True + True + True + True True + True + True + True True True True + True + True + True + True + True + True True True - True + True + True + True + True + True + True + True + True + True diff --git a/Templates/Rulesets/ruleset-scrolling-empty/.editorconfig b/Templates/Rulesets/ruleset-scrolling-empty/.editorconfig index f3badda9b3..b01749195f 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/.editorconfig +++ b/Templates/Rulesets/ruleset-scrolling-empty/.editorconfig @@ -10,14 +10,6 @@ trim_trailing_whitespace = true #Roslyn naming styles -#PascalCase for public and protected members -dotnet_naming_style.pascalcase.capitalization = pascal_case -dotnet_naming_symbols.public_members.applicable_accessibilities = public,internal,protected,protected_internal,private_protected -dotnet_naming_symbols.public_members.applicable_kinds = property,method,field,event -dotnet_naming_rule.public_members_pascalcase.severity = error -dotnet_naming_rule.public_members_pascalcase.symbols = public_members -dotnet_naming_rule.public_members_pascalcase.style = pascalcase - #camelCase for private members dotnet_naming_style.camelcase.capitalization = camel_case @@ -121,7 +113,7 @@ dotnet_style_qualification_for_event = false:warning dotnet_style_predefined_type_for_locals_parameters_members = true:warning dotnet_style_predefined_type_for_member_access = true:warning csharp_style_var_when_type_is_apparent = true:none -csharp_style_var_for_built_in_types = true:none +csharp_style_var_for_built_in_types = false:warning csharp_style_var_elsewhere = true:silent #Style - modifiers @@ -165,7 +157,7 @@ csharp_style_unused_value_assignment_preference = discard_variable:warning #Style - variable declaration csharp_style_inlined_variable_declaration = true:warning -csharp_style_deconstructed_variable_declaration = true:warning +csharp_style_deconstructed_variable_declaration = false:silent #Style - other C# 7.x features dotnet_style_prefer_inferred_tuple_names = true:warning @@ -176,8 +168,8 @@ dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent #Style - C# 8 features csharp_prefer_static_local_function = true:warning csharp_prefer_simple_using_statement = true:silent -csharp_style_prefer_index_operator = true:warning -csharp_style_prefer_range_operator = true:warning +csharp_style_prefer_index_operator = false:silent +csharp_style_prefer_range_operator = false:silent csharp_style_prefer_switch_expression = false:none #Supressing roslyn built-in analyzers @@ -197,4 +189,6 @@ dotnet_diagnostic.IDE0069.severity = none dotnet_diagnostic.CA2225.severity = none # Banned APIs -dotnet_diagnostic.RS0030.severity = error \ No newline at end of file +dotnet_diagnostic.RS0030.severity = error + +dotnet_diagnostic.OLOC001.license_header = // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.\n// See the LICENCE file in the repository root for full licence text. diff --git a/Templates/Rulesets/ruleset-scrolling-empty/.gitignore b/Templates/Rulesets/ruleset-scrolling-empty/.gitignore index 940794e60f..5b19270ab9 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/.gitignore +++ b/Templates/Rulesets/ruleset-scrolling-empty/.gitignore @@ -1,7 +1,5 @@ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. -## -## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore # User-specific files *.suo @@ -17,8 +15,6 @@ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ -x64/ -x86/ bld/ [Bb]in/ [Oo]bj/ @@ -42,11 +38,10 @@ TestResult.xml [Rr]eleasePS/ dlldata.c -# .NET Core +# DNX project.lock.json project.fragment.lock.json artifacts/ -**/Properties/launchSettings.json *_i.c *_p.c @@ -113,10 +108,6 @@ _TeamCity* # DotCover is a Code Coverage Tool *.dotCover -# Visual Studio code coverage results -*.coverage -*.coveragexml - # NCrunch _NCrunch_* .*crunch*.local.xml @@ -166,7 +157,7 @@ PublishScripts/ !**/packages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/packages/repositories.config -# NuGet v3's project.json files produces more ignorable files +# NuGet v3's project.json files produces more ignoreable files *.nuget.props *.nuget.targets @@ -196,10 +187,11 @@ ClientBin/ *~ *.dbmdl *.dbproj.schemaview -*.jfm *.pfx *.publishsettings +node_modules/ orleans.codegen.cs +Resource.designer.cs # Since there are multiple workflows, uncomment next line to ignore bower_components # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) @@ -219,7 +211,6 @@ UpgradeLog*.htm # SQL Server files *.mdf *.ldf -*.ndf # Business Intelligence projects *.rdl.data @@ -234,10 +225,6 @@ FakesAssemblies/ # Node.js Tools for Visual Studio .ntvs_analysis.dat -node_modules/ - -# Typescript v1 declaration files -typings/ # Visual Studio 6 build log *.plg @@ -245,9 +232,6 @@ typings/ # Visual Studio 6 workspace options file *.opt -# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) -*.vbw - # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts @@ -263,26 +247,96 @@ paket-files/ # FAKE - F# Make .fake/ -# JetBrains Rider -.idea/ -*.sln.iml - -# CodeRush -.cr/ - # Python Tools for Visual Studio (PTVS) __pycache__/ *.pyc -# Cake - Uncomment if you are using it -# tools/** -# !tools/packages.config +# Cake # +/tools/** +/build/tools/** +/build/temp/** -# Telerik's JustMock configuration file -*.jmconfig +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 -# BizTalk build output -*.btp.cs -*.btm.cs -*.odx.cs -*.xsd.cs +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +.idea/modules.xml +.idea/*.iml +.idea/modules +*.iml +*.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +# fastlane +fastlane/report.xml + +# inspectcode +inspectcodereport.xml +inspectcode + +# BenchmarkDotNet +/BenchmarkDotNet.Artifacts + +*.GeneratedMSBuildEditorConfig.editorconfig + +# Fody (pulled in by Realm) - schema file +FodyWeavers.xsd +**/FodyWeavers.xml diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.sln.DotSettings b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.sln.DotSettings index aa8f8739c1..2ff0f4d30b 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.sln.DotSettings +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.sln.DotSettings @@ -18,9 +18,10 @@ WARNING HINT DO_NOT_SHOW - HINT - WARNING - WARNING + WARNING + WARNING + HINT + HINT WARNING WARNING WARNING @@ -73,6 +74,7 @@ HINT WARNING HINT + DO_NOT_SHOW WARNING DO_NOT_SHOW WARNING @@ -105,8 +107,9 @@ HINT HINT WARNING + DO_NOT_SHOW + DO_NOT_SHOW WARNING - DO_NOT_SHOW WARNING WARNING WARNING @@ -120,6 +123,7 @@ WARNING WARNING HINT + HINT WARNING HINT HINT @@ -129,7 +133,7 @@ HINT WARNING WARNING - HINT + WARNING WARNING WARNING WARNING @@ -204,8 +208,10 @@ HINT WARNING WARNING - DO_NOT_SHOW + SUGGESTION DO_NOT_SHOW + + True DO_NOT_SHOW WARNING WARNING @@ -226,6 +232,7 @@ HINT DO_NOT_SHOW WARNING + WARNING WARNING WARNING WARNING @@ -298,15 +305,21 @@ True 200 CHOP_IF_LONG + UseExplicitType + UseVarWhenEvident + UseVarWhenEvident False False AABB API BPM + EF + FPS GC GL GLSL HID + HSV HTML HUD ID @@ -909,26 +922,82 @@ private void load() { $END$ }; + True True True + True True True True True + True + True + True + True + True + True + True + True True + True + True + True True + True + True + True True True + True + True + True + True + True True True + True + True True True + True + True + True + True + True + True + True + True True True + True + True + True + True + True + True + True + True True + True + True + True True True True + True + True + True + True + True + True True True - True + True + True + True + True + True + True + True + True + True diff --git a/Templates/Rulesets/ruleset-scrolling-example/.editorconfig b/Templates/Rulesets/ruleset-scrolling-example/.editorconfig index f3badda9b3..b01749195f 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/.editorconfig +++ b/Templates/Rulesets/ruleset-scrolling-example/.editorconfig @@ -10,14 +10,6 @@ trim_trailing_whitespace = true #Roslyn naming styles -#PascalCase for public and protected members -dotnet_naming_style.pascalcase.capitalization = pascal_case -dotnet_naming_symbols.public_members.applicable_accessibilities = public,internal,protected,protected_internal,private_protected -dotnet_naming_symbols.public_members.applicable_kinds = property,method,field,event -dotnet_naming_rule.public_members_pascalcase.severity = error -dotnet_naming_rule.public_members_pascalcase.symbols = public_members -dotnet_naming_rule.public_members_pascalcase.style = pascalcase - #camelCase for private members dotnet_naming_style.camelcase.capitalization = camel_case @@ -121,7 +113,7 @@ dotnet_style_qualification_for_event = false:warning dotnet_style_predefined_type_for_locals_parameters_members = true:warning dotnet_style_predefined_type_for_member_access = true:warning csharp_style_var_when_type_is_apparent = true:none -csharp_style_var_for_built_in_types = true:none +csharp_style_var_for_built_in_types = false:warning csharp_style_var_elsewhere = true:silent #Style - modifiers @@ -165,7 +157,7 @@ csharp_style_unused_value_assignment_preference = discard_variable:warning #Style - variable declaration csharp_style_inlined_variable_declaration = true:warning -csharp_style_deconstructed_variable_declaration = true:warning +csharp_style_deconstructed_variable_declaration = false:silent #Style - other C# 7.x features dotnet_style_prefer_inferred_tuple_names = true:warning @@ -176,8 +168,8 @@ dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent #Style - C# 8 features csharp_prefer_static_local_function = true:warning csharp_prefer_simple_using_statement = true:silent -csharp_style_prefer_index_operator = true:warning -csharp_style_prefer_range_operator = true:warning +csharp_style_prefer_index_operator = false:silent +csharp_style_prefer_range_operator = false:silent csharp_style_prefer_switch_expression = false:none #Supressing roslyn built-in analyzers @@ -197,4 +189,6 @@ dotnet_diagnostic.IDE0069.severity = none dotnet_diagnostic.CA2225.severity = none # Banned APIs -dotnet_diagnostic.RS0030.severity = error \ No newline at end of file +dotnet_diagnostic.RS0030.severity = error + +dotnet_diagnostic.OLOC001.license_header = // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.\n// See the LICENCE file in the repository root for full licence text. diff --git a/Templates/Rulesets/ruleset-scrolling-example/.gitignore b/Templates/Rulesets/ruleset-scrolling-example/.gitignore index 940794e60f..5b19270ab9 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/.gitignore +++ b/Templates/Rulesets/ruleset-scrolling-example/.gitignore @@ -1,7 +1,5 @@ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. -## -## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore # User-specific files *.suo @@ -17,8 +15,6 @@ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ -x64/ -x86/ bld/ [Bb]in/ [Oo]bj/ @@ -42,11 +38,10 @@ TestResult.xml [Rr]eleasePS/ dlldata.c -# .NET Core +# DNX project.lock.json project.fragment.lock.json artifacts/ -**/Properties/launchSettings.json *_i.c *_p.c @@ -113,10 +108,6 @@ _TeamCity* # DotCover is a Code Coverage Tool *.dotCover -# Visual Studio code coverage results -*.coverage -*.coveragexml - # NCrunch _NCrunch_* .*crunch*.local.xml @@ -166,7 +157,7 @@ PublishScripts/ !**/packages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/packages/repositories.config -# NuGet v3's project.json files produces more ignorable files +# NuGet v3's project.json files produces more ignoreable files *.nuget.props *.nuget.targets @@ -196,10 +187,11 @@ ClientBin/ *~ *.dbmdl *.dbproj.schemaview -*.jfm *.pfx *.publishsettings +node_modules/ orleans.codegen.cs +Resource.designer.cs # Since there are multiple workflows, uncomment next line to ignore bower_components # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) @@ -219,7 +211,6 @@ UpgradeLog*.htm # SQL Server files *.mdf *.ldf -*.ndf # Business Intelligence projects *.rdl.data @@ -234,10 +225,6 @@ FakesAssemblies/ # Node.js Tools for Visual Studio .ntvs_analysis.dat -node_modules/ - -# Typescript v1 declaration files -typings/ # Visual Studio 6 build log *.plg @@ -245,9 +232,6 @@ typings/ # Visual Studio 6 workspace options file *.opt -# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) -*.vbw - # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts @@ -263,26 +247,96 @@ paket-files/ # FAKE - F# Make .fake/ -# JetBrains Rider -.idea/ -*.sln.iml - -# CodeRush -.cr/ - # Python Tools for Visual Studio (PTVS) __pycache__/ *.pyc -# Cake - Uncomment if you are using it -# tools/** -# !tools/packages.config +# Cake # +/tools/** +/build/tools/** +/build/temp/** -# Telerik's JustMock configuration file -*.jmconfig +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 -# BizTalk build output -*.btp.cs -*.btm.cs -*.odx.cs -*.xsd.cs +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +.idea/modules.xml +.idea/*.iml +.idea/modules +*.iml +*.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +# fastlane +fastlane/report.xml + +# inspectcode +inspectcodereport.xml +inspectcode + +# BenchmarkDotNet +/BenchmarkDotNet.Artifacts + +*.GeneratedMSBuildEditorConfig.editorconfig + +# Fody (pulled in by Realm) - schema file +FodyWeavers.xsd +**/FodyWeavers.xml diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.sln.DotSettings b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.sln.DotSettings index aa8f8739c1..2ff0f4d30b 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.sln.DotSettings +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.sln.DotSettings @@ -18,9 +18,10 @@ WARNING HINT DO_NOT_SHOW - HINT - WARNING - WARNING + WARNING + WARNING + HINT + HINT WARNING WARNING WARNING @@ -73,6 +74,7 @@ HINT WARNING HINT + DO_NOT_SHOW WARNING DO_NOT_SHOW WARNING @@ -105,8 +107,9 @@ HINT HINT WARNING + DO_NOT_SHOW + DO_NOT_SHOW WARNING - DO_NOT_SHOW WARNING WARNING WARNING @@ -120,6 +123,7 @@ WARNING WARNING HINT + HINT WARNING HINT HINT @@ -129,7 +133,7 @@ HINT WARNING WARNING - HINT + WARNING WARNING WARNING WARNING @@ -204,8 +208,10 @@ HINT WARNING WARNING - DO_NOT_SHOW + SUGGESTION DO_NOT_SHOW + + True DO_NOT_SHOW WARNING WARNING @@ -226,6 +232,7 @@ HINT DO_NOT_SHOW WARNING + WARNING WARNING WARNING WARNING @@ -298,15 +305,21 @@ True 200 CHOP_IF_LONG + UseExplicitType + UseVarWhenEvident + UseVarWhenEvident False False AABB API BPM + EF + FPS GC GL GLSL HID + HSV HTML HUD ID @@ -909,26 +922,82 @@ private void load() { $END$ }; + True True True + True True True True True + True + True + True + True + True + True + True + True True + True + True + True True + True + True + True True True + True + True + True + True + True True True + True + True True True + True + True + True + True + True + True + True + True True True + True + True + True + True + True + True + True + True True + True + True + True True True True + True + True + True + True + True + True True True - True + True + True + True + True + True + True + True + True + True From d06d584867e57ee6cb03cc566c1114ada0f47473 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Wed, 9 Feb 2022 19:56:32 +0800 Subject: [PATCH 987/996] Change assembly titles So that they match the source name specified in template.json and can get replaced --- .../osu.Game.Rulesets.EmptyFreeform.csproj | 2 +- .../osu.Game.Rulesets.Pippidon.csproj | 2 +- .../osu.Game.Rulesets.EmptyScrolling.csproj | 2 +- .../osu.Game.Rulesets.Pippidon.csproj | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/osu.Game.Rulesets.EmptyFreeform.csproj b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/osu.Game.Rulesets.EmptyFreeform.csproj index cfe2bd1cb2..092a013614 100644 --- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/osu.Game.Rulesets.EmptyFreeform.csproj +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/osu.Game.Rulesets.EmptyFreeform.csproj @@ -1,7 +1,7 @@  netstandard2.1 - osu.Game.Rulesets.Sample + osu.Game.Rulesets.EmptyFreeform Library AnyCPU osu.Game.Rulesets.EmptyFreeform diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj index 61b859f45b..a3607343c9 100644 --- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj @@ -1,7 +1,7 @@  netstandard2.1 - osu.Game.Rulesets.Sample + osu.Game.Rulesets.Pippidon Library AnyCPU osu.Game.Rulesets.Pippidon diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/osu.Game.Rulesets.EmptyScrolling.csproj b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/osu.Game.Rulesets.EmptyScrolling.csproj index 9dce3c9a0a..2ea52429ab 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/osu.Game.Rulesets.EmptyScrolling.csproj +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/osu.Game.Rulesets.EmptyScrolling.csproj @@ -1,7 +1,7 @@  netstandard2.1 - osu.Game.Rulesets.Sample + osu.Game.Rulesets.EmptyScrolling Library AnyCPU osu.Game.Rulesets.EmptyScrolling diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj index 61b859f45b..a3607343c9 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj @@ -1,7 +1,7 @@  netstandard2.1 - osu.Game.Rulesets.Sample + osu.Game.Rulesets.Pippidon Library AnyCPU osu.Game.Rulesets.Pippidon From 036d17d9fd215423f34ace116203af936ef7428e Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Wed, 9 Feb 2022 20:48:14 +0800 Subject: [PATCH 988/996] Remove licence headers --- Templates/Rulesets/ruleset-empty/.editorconfig | 2 -- .../osu.Game.Rulesets.EmptyFreeform.sln.DotSettings | 3 --- Templates/Rulesets/ruleset-example/.editorconfig | 2 -- .../ruleset-example/osu.Game.Rulesets.Pippidon.sln.DotSettings | 3 --- Templates/Rulesets/ruleset-scrolling-empty/.editorconfig | 2 -- .../osu.Game.Rulesets.EmptyScrolling.sln.DotSettings | 3 --- Templates/Rulesets/ruleset-scrolling-example/.editorconfig | 2 -- .../osu.Game.Rulesets.Pippidon.sln.DotSettings | 3 --- 8 files changed, 20 deletions(-) diff --git a/Templates/Rulesets/ruleset-empty/.editorconfig b/Templates/Rulesets/ruleset-empty/.editorconfig index b01749195f..9c7537de4b 100644 --- a/Templates/Rulesets/ruleset-empty/.editorconfig +++ b/Templates/Rulesets/ruleset-empty/.editorconfig @@ -190,5 +190,3 @@ dotnet_diagnostic.CA2225.severity = none # Banned APIs dotnet_diagnostic.RS0030.severity = error - -dotnet_diagnostic.OLOC001.license_header = // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.\n// See the LICENCE file in the repository root for full licence text. diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.sln.DotSettings b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.sln.DotSettings index 2ff0f4d30b..9752e08599 100644 --- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.sln.DotSettings +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.sln.DotSettings @@ -730,9 +730,6 @@ </Group> </TypePattern> </Patterns> - Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. -See the LICENCE file in the repository root for full licence text. - <Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /> <Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> diff --git a/Templates/Rulesets/ruleset-example/.editorconfig b/Templates/Rulesets/ruleset-example/.editorconfig index b01749195f..9c7537de4b 100644 --- a/Templates/Rulesets/ruleset-example/.editorconfig +++ b/Templates/Rulesets/ruleset-example/.editorconfig @@ -190,5 +190,3 @@ dotnet_diagnostic.CA2225.severity = none # Banned APIs dotnet_diagnostic.RS0030.severity = error - -dotnet_diagnostic.OLOC001.license_header = // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.\n// See the LICENCE file in the repository root for full licence text. diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.sln.DotSettings b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.sln.DotSettings index 2ff0f4d30b..9752e08599 100644 --- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.sln.DotSettings +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.sln.DotSettings @@ -730,9 +730,6 @@ </Group> </TypePattern> </Patterns> - Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. -See the LICENCE file in the repository root for full licence text. - <Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /> <Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> diff --git a/Templates/Rulesets/ruleset-scrolling-empty/.editorconfig b/Templates/Rulesets/ruleset-scrolling-empty/.editorconfig index b01749195f..9c7537de4b 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/.editorconfig +++ b/Templates/Rulesets/ruleset-scrolling-empty/.editorconfig @@ -190,5 +190,3 @@ dotnet_diagnostic.CA2225.severity = none # Banned APIs dotnet_diagnostic.RS0030.severity = error - -dotnet_diagnostic.OLOC001.license_header = // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.\n// See the LICENCE file in the repository root for full licence text. diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.sln.DotSettings b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.sln.DotSettings index 2ff0f4d30b..9752e08599 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.sln.DotSettings +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.sln.DotSettings @@ -730,9 +730,6 @@ </Group> </TypePattern> </Patterns> - Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. -See the LICENCE file in the repository root for full licence text. - <Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /> <Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> diff --git a/Templates/Rulesets/ruleset-scrolling-example/.editorconfig b/Templates/Rulesets/ruleset-scrolling-example/.editorconfig index b01749195f..9c7537de4b 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/.editorconfig +++ b/Templates/Rulesets/ruleset-scrolling-example/.editorconfig @@ -190,5 +190,3 @@ dotnet_diagnostic.CA2225.severity = none # Banned APIs dotnet_diagnostic.RS0030.severity = error - -dotnet_diagnostic.OLOC001.license_header = // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.\n// See the LICENCE file in the repository root for full licence text. diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.sln.DotSettings b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.sln.DotSettings index 2ff0f4d30b..9752e08599 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.sln.DotSettings +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.sln.DotSettings @@ -730,9 +730,6 @@ </Group> </TypePattern> </Patterns> - Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. -See the LICENCE file in the repository root for full licence text. - <Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /> <Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> From f47748591a2deb2604f9556bfbe4bdad754ba96a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 10 Feb 2022 12:11:26 +0900 Subject: [PATCH 989/996] Update fastlane to latest release This pulls in the fix from souyuz to allow us to bring things up-to-date again (see https://github.com/voydz/souyuz/pull/36#event-6033249116). Have tested builds locally to work as expected. --- Gemfile.lock | 71 ++++++++++++++++++------------------ fastlane/README.md | 91 +++++++++++++++++++++++++++++++--------------- 2 files changed, 96 insertions(+), 66 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 86c8baabe6..1010027af9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -8,17 +8,17 @@ GEM artifactory (3.0.15) atomos (0.1.3) aws-eventstream (1.2.0) - aws-partitions (1.551.0) - aws-sdk-core (3.125.5) + aws-partitions (1.553.0) + aws-sdk-core (3.126.0) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.525.0) aws-sigv4 (~> 1.1) jmespath (~> 1.0) - aws-sdk-kms (1.53.0) - aws-sdk-core (~> 3, >= 3.125.0) + aws-sdk-kms (1.54.0) + aws-sdk-core (~> 3, >= 3.126.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.111.3) - aws-sdk-core (~> 3, >= 3.125.0) + aws-sdk-s3 (1.112.0) + aws-sdk-core (~> 3, >= 3.126.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.4) aws-sigv4 (1.4.0) @@ -27,8 +27,8 @@ GEM claide (1.1.0) colored (1.2) colored2 (3.1.2) - commander-fastlane (4.4.6) - highline (~> 1.7.2) + commander (4.6.0) + highline (~> 2.0.0) declarative (0.0.20) digest-crc (0.6.4) rake (>= 12.0.0, < 14.0.0) @@ -36,7 +36,7 @@ GEM unf (>= 0.0.5, < 1.0.0) dotenv (2.7.6) emoji_regex (3.2.3) - excon (0.90.0) + excon (0.91.0) faraday (1.9.3) faraday-em_http (~> 1.0) faraday-em_synchrony (~> 1.0) @@ -66,15 +66,15 @@ GEM faraday_middleware (1.2.0) faraday (~> 1.0) fastimage (2.2.6) - fastlane (2.181.0) + fastlane (2.204.2) CFPropertyList (>= 2.3, < 4.0.0) - addressable (>= 2.3, < 3.0.0) + addressable (>= 2.8, < 3.0.0) artifactory (~> 3.0) aws-sdk-s3 (~> 1.0) babosa (>= 1.0.3, < 2.0.0) bundler (>= 1.12.0, < 3.0.0) colored - commander-fastlane (>= 4.4.6, < 5.0.0) + commander (~> 4.6) dotenv (>= 2.1.1, < 3.0.0) emoji_regex (>= 0.1, < 4.0) excon (>= 0.71.0, < 1.0.0) @@ -83,19 +83,20 @@ GEM faraday_middleware (~> 1.0) fastimage (>= 2.1.0, < 3.0.0) gh_inspector (>= 1.1.2, < 2.0.0) - google-api-client (>= 0.37.0, < 0.39.0) - google-cloud-storage (>= 1.15.0, < 2.0.0) - highline (>= 1.7.2, < 2.0.0) + google-apis-androidpublisher_v3 (~> 0.3) + google-apis-playcustomapp_v1 (~> 0.1) + google-cloud-storage (~> 1.31) + highline (~> 2.0) json (< 3.0.0) jwt (>= 2.1.0, < 3) mini_magick (>= 4.9.4, < 5.0.0) multipart-post (~> 2.0.0) naturally (~> 2.2) + optparse (~> 0.1.1) plist (>= 3.1.0, < 4.0.0) rubyzip (>= 2.0.0, < 3.0.0) security (= 0.1.3) simctl (~> 1.6.3) - slack-notifier (>= 2.0.0, < 3.0.0) terminal-notifier (>= 2.0.0, < 3.0.0) terminal-table (>= 1.4.5, < 2.0.0) tty-screen (>= 0.6.3, < 1.0.0) @@ -105,18 +106,12 @@ GEM xcpretty (~> 0.3.0) xcpretty-travis-formatter (>= 0.0.3) fastlane-plugin-clean_testflight_testers (0.3.0) - fastlane-plugin-souyuz (0.9.1) - souyuz (= 0.9.1) + fastlane-plugin-souyuz (0.11.1) + souyuz (= 0.11.1) fastlane-plugin-xamarin (0.6.3) gh_inspector (1.1.3) - google-api-client (0.38.0) - addressable (~> 2.5, >= 2.5.1) - googleauth (~> 0.9) - httpclient (>= 2.8.1, < 3.0) - mini_mime (~> 1.0) - representable (~> 3.0) - retriable (>= 2.0, < 4.0) - signet (~> 0.12) + google-apis-androidpublisher_v3 (0.16.0) + google-apis-core (>= 0.4, < 2.a) google-apis-core (0.4.2) addressable (~> 2.5, >= 2.5.1) googleauth (>= 0.16.2, < 2.a) @@ -128,6 +123,8 @@ GEM webrick google-apis-iamcredentials_v1 (0.10.0) google-apis-core (>= 0.4, < 2.a) + google-apis-playcustomapp_v1 (0.7.0) + google-apis-core (>= 0.4, < 2.a) google-apis-storage_v1 (0.11.0) google-apis-core (>= 0.4, < 2.a) google-cloud-core (1.6.0) @@ -144,14 +141,14 @@ GEM google-cloud-core (~> 1.6) googleauth (>= 0.16.2, < 2.a) mini_mime (~> 1.0) - googleauth (0.17.1) + googleauth (1.1.0) faraday (>= 0.17.3, < 2.0) jwt (>= 1.4, < 3.0) memoist (~> 0.16) multi_json (~> 1.11) os (>= 0.9, < 2.0) - signet (~> 0.15) - highline (1.7.10) + signet (>= 0.16, < 2.a) + highline (2.0.3) http-cookie (1.0.4) domain_name (~> 0.5) httpclient (2.8.3) @@ -161,16 +158,19 @@ GEM memoist (0.16.2) mini_magick (4.11.0) mini_mime (1.1.2) - mini_portile2 (2.4.0) + mini_portile2 (2.7.1) multi_json (1.15.0) multipart-post (2.0.0) nanaimo (0.3.0) naturally (2.2.1) - nokogiri (1.10.10) - mini_portile2 (~> 2.4.0) + nokogiri (1.13.1) + mini_portile2 (~> 2.7.0) + racc (~> 1.4) + optparse (0.1.1) os (1.1.4) plist (3.6.0) public_suffix (4.0.6) + racc (1.6.0) rake (13.0.6) representable (3.1.1) declarative (< 0.1.0) @@ -190,10 +190,9 @@ GEM simctl (1.6.8) CFPropertyList naturally - slack-notifier (2.4.0) - souyuz (0.9.1) - fastlane (>= 1.103.0) - highline (~> 1.7) + souyuz (0.11.1) + fastlane (>= 2.182.0) + highline (~> 2.0) nokogiri (~> 1.7) terminal-notifier (2.0.0) terminal-table (1.8.0) diff --git a/fastlane/README.md b/fastlane/README.md index 8273fdaa5d..9d5e11f7cb 100644 --- a/fastlane/README.md +++ b/fastlane/README.md @@ -1,78 +1,109 @@ fastlane documentation -================ +---- + # Installation Make sure you have the latest version of the Xcode command line tools installed: -``` +```sh xcode-select --install ``` -Install _fastlane_ using -``` -[sudo] gem install fastlane -NV -``` -or alternatively using `brew install fastlane` +For _fastlane_ installation instructions, see [Installing _fastlane_](https://docs.fastlane.tools/#installing-fastlane) # Available Actions + ## Android + ### android beta + +```sh +[bundle exec] fastlane android beta ``` -fastlane android beta -``` + Deploy to play store + ### android build_github + +```sh +[bundle exec] fastlane android build_github ``` -fastlane android build_github -``` + Deploy to github release + ### android build + +```sh +[bundle exec] fastlane android build ``` -fastlane android build -``` + Compile the project + ### android update_version + +```sh +[bundle exec] fastlane android update_version ``` -fastlane android update_version -``` + ---- + ## iOS + ### ios beta + +```sh +[bundle exec] fastlane ios beta ``` -fastlane ios beta -``` + Deploy to testflight + ### ios build + +```sh +[bundle exec] fastlane ios build ``` -fastlane ios build -``` + Compile the project + ### ios provision + +```sh +[bundle exec] fastlane ios provision ``` -fastlane ios provision -``` + Install provisioning profiles using match + ### ios update_version + +```sh +[bundle exec] fastlane ios update_version ``` -fastlane ios update_version -``` + + ### ios testflight_prune_dry -``` -fastlane ios testflight_prune_dry + +```sh +[bundle exec] fastlane ios testflight_prune_dry ``` + + ### ios testflight_prune + +```sh +[bundle exec] fastlane ios testflight_prune ``` -fastlane ios testflight_prune -``` + ---- -This README.md is auto-generated and will be re-generated every time [fastlane](https://fastlane.tools) is run. -More information about fastlane can be found on [fastlane.tools](https://fastlane.tools). -The documentation of fastlane can be found on [docs.fastlane.tools](https://docs.fastlane.tools). +This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run. + +More information about _fastlane_ can be found on [fastlane.tools](https://fastlane.tools). + +The documentation of _fastlane_ can be found on [docs.fastlane.tools](https://docs.fastlane.tools). From a3896a8ebdc87b95c9d97a1f5112c65bafe761b9 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 10 Feb 2022 14:18:29 +0900 Subject: [PATCH 990/996] Remove allowance of null dependency --- osu.Game/Rulesets/UI/ReplayRecorder.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/UI/ReplayRecorder.cs b/osu.Game/Rulesets/UI/ReplayRecorder.cs index 277040b2a6..0a94b55221 100644 --- a/osu.Game/Rulesets/UI/ReplayRecorder.cs +++ b/osu.Game/Rulesets/UI/ReplayRecorder.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.UI public int RecordFrameRate = 60; - [Resolved(canBeNull: true)] + [Resolved] private SpectatorClient spectatorClient { get; set; } [Resolved] @@ -48,8 +48,7 @@ namespace osu.Game.Rulesets.UI base.LoadComplete(); inputManager = GetContainingInputManager(); - - spectatorClient?.BeginPlaying(gameplayState, target); + spectatorClient.BeginPlaying(gameplayState, target); } protected override void Dispose(bool isDisposing) From f7fb7825cc322c3ac08a0c310928591ed96e3142 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 10 Feb 2022 14:21:33 +0900 Subject: [PATCH 991/996] Simplify disposal --- osu.Game/Rulesets/UI/ReplayRecorder.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/UI/ReplayRecorder.cs b/osu.Game/Rulesets/UI/ReplayRecorder.cs index 0a94b55221..6843beef7b 100644 --- a/osu.Game/Rulesets/UI/ReplayRecorder.cs +++ b/osu.Game/Rulesets/UI/ReplayRecorder.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -54,9 +55,7 @@ namespace osu.Game.Rulesets.UI protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - - if (spectatorClient != null && gameplayState != null) - spectatorClient.EndPlaying(gameplayState); + spectatorClient?.EndPlaying(gameplayState); } protected override void Update() From ebd105422fd6c2903bced01cb820d7e0d542789a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 10 Feb 2022 14:22:08 +0900 Subject: [PATCH 992/996] Remove unused using --- osu.Game/Rulesets/UI/ReplayRecorder.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/ReplayRecorder.cs b/osu.Game/Rulesets/UI/ReplayRecorder.cs index 6843beef7b..dcd8f12028 100644 --- a/osu.Game/Rulesets/UI/ReplayRecorder.cs +++ b/osu.Game/Rulesets/UI/ReplayRecorder.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; From 88bb9d4237c62d37678d8e0e110438ea247b7ae5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 10 Feb 2022 15:50:03 +0900 Subject: [PATCH 993/996] Fix migration errors not outputting the call stack to logs --- .../Settings/Sections/Maintenance/MigrationRunScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationRunScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationRunScreen.cs index b0b61554eb..adb347e7b8 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationRunScreen.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationRunScreen.cs @@ -88,7 +88,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance .ContinueWith(t => { if (t.IsFaulted) - Logger.Log($"Error during migration: {t.Exception?.Message}", level: LogLevel.Error); + Logger.Error(t.Exception, $"Error during migration: {t.Exception?.Message}"); Schedule(this.Exit); }); From 44f2d8a4481d686aea94d6d480cfc0e17ec7351b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 10 Feb 2022 18:48:37 +0900 Subject: [PATCH 994/996] Allow game folder migration to fail gracefully when cleanup cannot completely succeed --- osu.Game.Tournament/IO/TournamentStorage.cs | 4 ++- osu.Game/IO/MigratableStorage.cs | 30 +++++++++++----- osu.Game/IO/OsuStorage.cs | 7 ++-- osu.Game/OsuGameBase.cs | 9 ++--- .../Maintenance/MigrationRunScreen.cs | 36 ++++++++++++++++--- 5 files changed, 66 insertions(+), 20 deletions(-) diff --git a/osu.Game.Tournament/IO/TournamentStorage.cs b/osu.Game.Tournament/IO/TournamentStorage.cs index 347d368a04..b4859d0c91 100644 --- a/osu.Game.Tournament/IO/TournamentStorage.cs +++ b/osu.Game.Tournament/IO/TournamentStorage.cs @@ -70,7 +70,7 @@ namespace osu.Game.Tournament.IO public IEnumerable ListTournaments() => AllTournaments.GetDirectories(string.Empty); - public override void Migrate(Storage newStorage) + public override bool Migrate(Storage newStorage) { // this migration only happens once on moving to the per-tournament storage system. // listed files are those known at that point in time. @@ -94,6 +94,8 @@ namespace osu.Game.Tournament.IO ChangeTargetStorage(newStorage); storageConfig.SetValue(StorageConfig.CurrentTournament, default_tournament); storageConfig.Save(); + + return true; } private void moveFileIfExists(string file, DirectoryInfo destination) diff --git a/osu.Game/IO/MigratableStorage.cs b/osu.Game/IO/MigratableStorage.cs index 1b76725b04..e478144294 100644 --- a/osu.Game/IO/MigratableStorage.cs +++ b/osu.Game/IO/MigratableStorage.cs @@ -33,7 +33,8 @@ namespace osu.Game.IO /// A general purpose migration method to move the storage to a different location. /// The target storage of the migration. /// - public virtual void Migrate(Storage newStorage) + /// Whether cleanup could complete. + public virtual bool Migrate(Storage newStorage) { var source = new DirectoryInfo(GetFullPath(".")); var destination = new DirectoryInfo(newStorage.GetFullPath(".")); @@ -57,17 +58,20 @@ namespace osu.Game.IO CopyRecursive(source, destination); ChangeTargetStorage(newStorage); - DeleteRecursive(source); + + return DeleteRecursive(source); } - protected void DeleteRecursive(DirectoryInfo target, bool topLevelExcludes = true) + protected bool DeleteRecursive(DirectoryInfo target, bool topLevelExcludes = true) { + bool allFilesDeleted = true; + foreach (System.IO.FileInfo fi in target.GetFiles()) { if (topLevelExcludes && IgnoreFiles.Contains(fi.Name)) continue; - AttemptOperation(() => fi.Delete()); + allFilesDeleted &= AttemptOperation(() => fi.Delete(), throwOnFailure: false); } foreach (DirectoryInfo dir in target.GetDirectories()) @@ -75,11 +79,13 @@ namespace osu.Game.IO if (topLevelExcludes && IgnoreDirectories.Contains(dir.Name)) continue; - AttemptOperation(() => dir.Delete(true)); + allFilesDeleted &= AttemptOperation(() => dir.Delete(true), throwOnFailure: false); } if (target.GetFiles().Length == 0 && target.GetDirectories().Length == 0) - AttemptOperation(target.Delete); + allFilesDeleted &= AttemptOperation(target.Delete, throwOnFailure: false); + + return allFilesDeleted; } protected void CopyRecursive(DirectoryInfo source, DirectoryInfo destination, bool topLevelExcludes = true) @@ -110,19 +116,25 @@ namespace osu.Game.IO /// /// The action to perform. /// The number of attempts (250ms wait between each). - protected static void AttemptOperation(Action action, int attempts = 10) + /// Whether to throw an exception on failure. If false, will silently fail. + protected static bool AttemptOperation(Action action, int attempts = 10, bool throwOnFailure = true) { while (true) { try { action(); - return; + return true; } catch (Exception) { if (attempts-- == 0) - throw; + { + if (throwOnFailure) + throw; + + return false; + } } Thread.Sleep(250); diff --git a/osu.Game/IO/OsuStorage.cs b/osu.Game/IO/OsuStorage.cs index 802c71e363..6e7cb545e3 100644 --- a/osu.Game/IO/OsuStorage.cs +++ b/osu.Game/IO/OsuStorage.cs @@ -113,11 +113,14 @@ namespace osu.Game.IO } } - public override void Migrate(Storage newStorage) + public override bool Migrate(Storage newStorage) { - base.Migrate(newStorage); + bool cleanupSucceeded = base.Migrate(newStorage); + storageConfig.SetValue(StorageConfig.FullPath, newStorage.GetFullPath(".")); storageConfig.Save(); + + return cleanupSucceeded; } } diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 5b2eb5607a..0b2644d5ba 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -413,7 +413,7 @@ namespace osu.Game Scheduler.AddDelayed(GracefullyExit, 2000); } - public void Migrate(string path) + public bool Migrate(string path) { Logger.Log($@"Migrating osu! data from ""{Storage.GetFullPath(string.Empty)}"" to ""{path}""..."); @@ -432,14 +432,15 @@ namespace osu.Game readyToRun.Wait(); - (Storage as OsuStorage)?.Migrate(Host.GetStorage(path)); + bool? cleanupSucceded = (Storage as OsuStorage)?.Migrate(Host.GetStorage(path)); + + Logger.Log(@"Migration complete!"); + return cleanupSucceded != false; } finally { realmBlocker?.Dispose(); } - - Logger.Log(@"Migration complete!"); } protected override UserInputManager CreateUserInputManager() => new OsuUserInputManager(); diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationRunScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationRunScreen.cs index adb347e7b8..fb7ff0dbd1 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationRunScreen.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationRunScreen.cs @@ -4,13 +4,16 @@ using System.IO; using System.Threading.Tasks; using osu.Framework.Allocation; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Logging; +using osu.Framework.Platform; using osu.Framework.Screens; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays.Notifications; using osu.Game.Screens; using osuTK; @@ -23,6 +26,15 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance [Resolved(canBeNull: true)] private OsuGame game { get; set; } + [Resolved] + private NotificationOverlay notifications { get; set; } + + [Resolved] + private Storage storage { get; set; } + + [Resolved] + private GameHost host { get; set; } + public override bool AllowBackButton => false; public override bool AllowExternalScreenChange => false; @@ -84,17 +96,33 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance Beatmap.Value = Beatmap.Default; + var originalStorage = new NativeStorage(storage.GetFullPath(string.Empty), host); + migrationTask = Task.Run(PerformMigration) - .ContinueWith(t => + .ContinueWith(task => { - if (t.IsFaulted) - Logger.Error(t.Exception, $"Error during migration: {t.Exception?.Message}"); + if (task.IsFaulted) + { + Logger.Error(task.Exception, $"Error during migration: {task.Exception?.Message}"); + } + else if (!task.GetResultSafely()) + { + notifications.Post(new SimpleNotification + { + Text = "Some files couldn't be cleaned up during migration. Clicking this notification will open the folder so you can manually clean things up.", + Activated = () => + { + originalStorage.PresentExternally(); + return true; + } + }); + } Schedule(this.Exit); }); } - protected virtual void PerformMigration() => game?.Migrate(destination.FullName); + protected virtual bool PerformMigration() => game?.Migrate(destination.FullName) != false; public override void OnEntering(IScreen last) { From 19cb8cb03a100c9baa80c23b7b307ff300130df2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 10 Feb 2022 19:21:17 +0900 Subject: [PATCH 995/996] Update tests --- .../Settings/TestSceneMigrationScreens.cs | 51 ++++++++++++++++--- 1 file changed, 44 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneMigrationScreens.cs b/osu.Game.Tests/Visual/Settings/TestSceneMigrationScreens.cs index 2883e54385..a68090504d 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneMigrationScreens.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneMigrationScreens.cs @@ -3,32 +3,69 @@ using System.IO; using System.Threading; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Framework.Screens; +using osu.Game.Overlays; using osu.Game.Overlays.Settings.Sections.Maintenance; namespace osu.Game.Tests.Visual.Settings { public class TestSceneMigrationScreens : ScreenTestScene { + [Cached] + private readonly NotificationOverlay notifications; + public TestSceneMigrationScreens() { - AddStep("Push screen", () => Stack.Push(new TestMigrationSelectScreen())); + Children = new Drawable[] + { + notifications = new NotificationOverlay + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + } + }; + } + + [Test] + public void TestDeleteSuccess() + { + AddStep("Push screen", () => Stack.Push(new TestMigrationSelectScreen(true))); + } + + [Test] + public void TestDeleteFails() + { + AddStep("Push screen", () => Stack.Push(new TestMigrationSelectScreen(false))); } private class TestMigrationSelectScreen : MigrationSelectScreen { - protected override void BeginMigration(DirectoryInfo target) => this.Push(new TestMigrationRunScreen()); + private readonly bool deleteSuccess; + + public TestMigrationSelectScreen(bool deleteSuccess) + { + this.deleteSuccess = deleteSuccess; + } + + protected override void BeginMigration(DirectoryInfo target) => this.Push(new TestMigrationRunScreen(deleteSuccess)); private class TestMigrationRunScreen : MigrationRunScreen { - protected override void PerformMigration() - { - Thread.Sleep(3000); - } + private readonly bool success; - public TestMigrationRunScreen() + public TestMigrationRunScreen(bool success) : base(null) { + this.success = success; + } + + protected override bool PerformMigration() + { + Thread.Sleep(3000); + return success; } } } From 2939bc4644b2b5079a5af5ffa578288676cf0ca2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Feb 2022 01:49:52 +0900 Subject: [PATCH 996/996] Update resources --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 71525a7acb..147f576c55 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -51,7 +51,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index c8f634284b..dd10807ec2 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -37,7 +37,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 5978f6d685..6fbc468586 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - +