From 39d37c4779b82c1069a1cc89b4d0342ceb34b686 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 12 Nov 2020 15:24:45 +0900 Subject: [PATCH 01/62] Add support for nested hitobject pooling --- .../Objects/Drawables/DrawableHitObject.cs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 244cf831c3..2dba83f2be 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -19,6 +19,7 @@ using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; using osu.Game.Configuration; +using osu.Game.Rulesets.UI; using osuTK.Graphics; namespace osu.Game.Rulesets.Objects.Drawables @@ -126,6 +127,9 @@ namespace osu.Game.Rulesets.Objects.Drawables [CanBeNull] private HitObjectLifetimeEntry lifetimeEntry; + [Resolved(CanBeNull = true)] + private DrawableRuleset drawableRuleset { get; set; } + /// /// Creates a new . /// @@ -195,7 +199,9 @@ namespace osu.Game.Rulesets.Objects.Drawables foreach (var h in HitObject.NestedHitObjects) { - var drawableNested = CreateNestedHitObject(h) ?? throw new InvalidOperationException($"{nameof(CreateNestedHitObject)} returned null for {h.GetType().ReadableName()}."); + var drawableNested = drawableRuleset?.GetPooledDrawableRepresentation(h) + ?? CreateNestedHitObject(h) + ?? throw new InvalidOperationException($"{nameof(CreateNestedHitObject)} returned null for {h.GetType().ReadableName()}."); drawableNested.OnNewResult += onNewResult; drawableNested.OnRevertResult += onRevertResult; @@ -203,6 +209,8 @@ namespace osu.Game.Rulesets.Objects.Drawables nestedHitObjects.Value.Add(drawableNested); AddNestedHitObject(drawableNested); + + drawableNested.OnParentReceived(this); } StartTimeBindable.BindTo(HitObject.StartTimeBindable); @@ -291,6 +299,14 @@ namespace osu.Game.Rulesets.Objects.Drawables { } + /// + /// Invoked when this receives a new parenting . + /// + /// The parenting . + protected virtual void OnParentReceived(DrawableHitObject parent) + { + } + /// /// Invoked by the base to populate samples, once on initial load and potentially again on any change to the samples collection. /// From bf72961959c081ca9254b4183cf19e85a89c845c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 11 Nov 2020 00:22:06 +0900 Subject: [PATCH 02/62] Add top-level osu! hitobject pooling --- .../Objects/Drawables/DrawableHitCircle.cs | 5 ++ .../Objects/Drawables/DrawableSlider.cs | 5 ++ .../Objects/Drawables/DrawableSpinner.cs | 5 ++ .../UI/DrawableOsuRuleset.cs | 48 ++++++++++------- osu.Game.Rulesets.Osu/UI/OsuDrawablePool.cs | 33 ++++++++++++ osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 54 ++++++++++--------- 6 files changed, 108 insertions(+), 42 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/UI/OsuDrawablePool.cs diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index 77d24db084..2e63160d36 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -31,6 +31,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private Container scaleContainer; private InputManager inputManager; + public DrawableHitCircle() + : this(null) + { + } + public DrawableHitCircle([CanBeNull] HitCircle h = null) : base(h) { diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 3f91a31066..05e4587307 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -41,6 +41,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private Container tickContainer; private Container repeatContainer; + public DrawableSlider() + : this(null) + { + } + public DrawableSlider([CanBeNull] Slider s = null) : base(s) { diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index eb125969b0..f04a914fe3 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -33,6 +33,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private Bindable isSpinning; private bool spinnerFrequencyModulate; + public DrawableSpinner() + : this(null) + { + } + public DrawableSpinner([CanBeNull] Spinner s = null) : base(s) { diff --git a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs index b2299398e1..1c119e2e95 100644 --- a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs @@ -4,12 +4,14 @@ using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Pooling; using osu.Framework.Input; using osu.Game.Beatmaps; using osu.Game.Input.Handlers; using osu.Game.Replays; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Configuration; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; @@ -24,11 +26,28 @@ namespace osu.Game.Rulesets.Osu.UI { protected new OsuRulesetConfigManager Config => (OsuRulesetConfigManager)base.Config; + public new OsuPlayfield Playfield => (OsuPlayfield)base.Playfield; + public DrawableOsuRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) : base(ruleset, beatmap, mods) { } + protected override bool PoolHitObjects => true; + + [BackgroundDependencyLoader] + private void load() + { + RegisterPool(10, 100); + RegisterPool(10, 100); + RegisterPool(2, 20); + } + + protected override DrawablePool CreatePool(int initialSize, int? maximumSize = null) + => new OsuDrawablePool(Playfield.CheckHittable, Playfield.OnHitObjectLoaded, initialSize, maximumSize); + + protected override HitObjectLifetimeEntry CreateLifetimeEntry(OsuHitObject hitObject) => new OsuHitObjectLifetimeEntry(hitObject); + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; // always show the gameplay cursor protected override Playfield CreatePlayfield() => new OsuPlayfield(); @@ -39,23 +58,6 @@ namespace osu.Game.Rulesets.Osu.UI protected override ResumeOverlay CreateResumeOverlay() => new OsuResumeOverlay(); - public override DrawableHitObject CreateDrawableRepresentation(OsuHitObject h) - { - switch (h) - { - case HitCircle circle: - return new DrawableHitCircle(circle); - - case Slider slider: - return new DrawableSlider(slider); - - case Spinner spinner: - return new DrawableSpinner(spinner); - } - - return null; - } - protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new OsuFramedReplayInputHandler(replay); protected override ReplayRecorder CreateReplayRecorder(Replay replay) => new OsuReplayRecorder(replay); @@ -70,5 +72,15 @@ namespace osu.Game.Rulesets.Osu.UI return 0; } } + + private class OsuHitObjectLifetimeEntry : HitObjectLifetimeEntry + { + public OsuHitObjectLifetimeEntry(HitObject hitObject) + : base(hitObject) + { + } + + protected override double InitialLifetimeOffset => ((OsuHitObject)HitObject).TimePreempt; + } } } diff --git a/osu.Game.Rulesets.Osu/UI/OsuDrawablePool.cs b/osu.Game.Rulesets.Osu/UI/OsuDrawablePool.cs new file mode 100644 index 0000000000..148146f25a --- /dev/null +++ b/osu.Game.Rulesets.Osu/UI/OsuDrawablePool.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 System; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Pooling; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables; + +namespace osu.Game.Rulesets.Osu.UI +{ + public class OsuDrawablePool : DrawablePool + where T : DrawableHitObject, new() + { + private readonly Func checkHittable; + private readonly Action onLoaded; + + public OsuDrawablePool(Func checkHittable, Action onLoaded, int initialSize, int? maximumSize = null) + : base(initialSize, maximumSize) + { + this.checkHittable = checkHittable; + this.onLoaded = onLoaded; + } + + protected override T CreateNewDrawable() => base.CreateNewDrawable().With(o => + { + var osuObject = (DrawableOsuHitObject)(object)o; + + osuObject.CheckHittable = checkHittable; + osuObject.OnLoadComplete += onLoaded; + }); + } +} diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 321eeeab65..5d59a6ff38 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -13,6 +13,7 @@ using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Configuration; +using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables.Connections; using osu.Game.Rulesets.Osu.Scoring; @@ -26,6 +27,8 @@ namespace osu.Game.Rulesets.Osu.UI { public class OsuPlayfield : Playfield { + public readonly Func CheckHittable; + private readonly PlayfieldBorder playfieldBorder; private readonly ProxyContainer approachCircles; private readonly ProxyContainer spinnerProxies; @@ -78,6 +81,7 @@ namespace osu.Game.Rulesets.Osu.UI }; hitPolicy = new OrderedHitPolicy(HitObjectContainer); + CheckHittable = hitPolicy.IsHittable; var hitWindows = new OsuHitWindows(); @@ -85,6 +89,8 @@ namespace osu.Game.Rulesets.Osu.UI poolDictionary.Add(result, new DrawableJudgementPool(result)); AddRangeInternal(poolDictionary.Values); + + NewResult += onNewResult; } [BackgroundDependencyLoader(true)] @@ -93,37 +99,37 @@ namespace osu.Game.Rulesets.Osu.UI config?.BindWith(OsuRulesetSetting.PlayfieldBorderStyle, playfieldBorder.PlayfieldBorderStyle); } - public override void Add(DrawableHitObject h) + protected override void OnHitObjectAdded(HitObject hitObject) { - DrawableOsuHitObject osuHitObject = (DrawableOsuHitObject)h; - - h.OnNewResult += onNewResult; - h.OnLoadComplete += d => - { - if (d is DrawableSpinner) - spinnerProxies.Add(d.CreateProxy()); - - if (d is IDrawableHitObjectWithProxiedApproach c) - approachCircles.Add(c.ProxiedLayer.CreateProxy()); - }; - - base.Add(h); - - osuHitObject.CheckHittable = hitPolicy.IsHittable; - - followPoints.AddFollowPoints(osuHitObject.HitObject); + base.OnHitObjectAdded(hitObject); + followPoints.AddFollowPoints((OsuHitObject)hitObject); } - public override bool Remove(DrawableHitObject h) + protected override void OnHitObjectRemoved(HitObject hitObject) { - DrawableOsuHitObject osuHitObject = (DrawableOsuHitObject)h; + base.OnHitObjectRemoved(hitObject); + followPoints.RemoveFollowPoints((OsuHitObject)hitObject); + } - bool result = base.Remove(h); + public void OnHitObjectLoaded(Drawable drawable) + { + switch (drawable) + { + case DrawableSliderHead _: + case DrawableSliderTail _: + case DrawableSliderTick _: + case DrawableSliderRepeat _: + case DrawableSpinnerTick _: + break; - if (result) - followPoints.RemoveFollowPoints(osuHitObject.HitObject); + case DrawableSpinner _: + spinnerProxies.Add(drawable.CreateProxy()); + break; - return result; + case IDrawableHitObjectWithProxiedApproach approach: + approachCircles.Add(approach.ProxiedLayer.CreateProxy()); + break; + } } private void onNewResult(DrawableHitObject judgedObject, JudgementResult result) From 1ea526b5ef72ac020ecc3fd3d589cbd23c2e9ff3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 12 Nov 2020 14:19:26 +0900 Subject: [PATCH 03/62] Adjust pooling implementation with branch changes --- osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs index 1c119e2e95..86c7305439 100644 --- a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs @@ -12,6 +12,7 @@ using osu.Game.Input.Handlers; using osu.Game.Replays; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Configuration; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; @@ -33,17 +34,21 @@ namespace osu.Game.Rulesets.Osu.UI { } - protected override bool PoolHitObjects => true; - [BackgroundDependencyLoader] private void load() { - RegisterPool(10, 100); - RegisterPool(10, 100); - RegisterPool(2, 20); + registerPool(10, 100); + registerPool(10, 100); + registerPool(2, 20); } - protected override DrawablePool CreatePool(int initialSize, int? maximumSize = null) + private void registerPool(int initialSize, int? maximumSize = null) + where TObject : HitObject + where TDrawable : DrawableHitObject, new() + => RegisterPool(CreatePool(initialSize, maximumSize)); + + protected virtual DrawablePool CreatePool(int initialSize, int? maximumSize = null) + where TDrawable : DrawableHitObject, new() => new OsuDrawablePool(Playfield.CheckHittable, Playfield.OnHitObjectLoaded, initialSize, maximumSize); protected override HitObjectLifetimeEntry CreateLifetimeEntry(OsuHitObject hitObject) => new OsuHitObjectLifetimeEntry(hitObject); From 3f78d81386db5deb57d42f55dcd841ead8d68a4c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 12 Nov 2020 15:59:48 +0900 Subject: [PATCH 04/62] Add nested osu! hitobject pooling --- .../Objects/Drawables/DrawableOsuHitObject.cs | 2 +- .../Objects/Drawables/DrawableSlider.cs | 18 +++----- .../Objects/Drawables/DrawableSliderHead.cs | 44 +++++++++++++++---- .../Objects/Drawables/DrawableSliderRepeat.cs | 31 +++++++++---- .../Objects/Drawables/DrawableSliderTail.cs | 12 +++-- .../Objects/Drawables/DrawableSliderTick.cs | 14 +++++- .../Objects/Drawables/DrawableSpinner.cs | 2 +- .../Drawables/DrawableSpinnerBonusTick.cs | 5 +++ .../Objects/Drawables/DrawableSpinnerTick.cs | 5 +++ .../UI/DrawableOsuRuleset.cs | 8 ++++ 10 files changed, 106 insertions(+), 35 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index d17bf93fa0..bcaf73d34f 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -83,7 +83,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private OsuInputManager osuActionInputManager; internal OsuInputManager OsuActionInputManager => osuActionInputManager ??= GetContainingInputManager() as OsuInputManager; - protected virtual void Shake(double maximumLength) => shakeContainer.Shake(maximumLength); + public virtual void Shake(double maximumLength) => shakeContainer.Shake(maximumLength); protected override void UpdateInitialTransforms() { diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 05e4587307..5bbdc5ee7b 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -164,10 +164,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { base.ClearNestedHitObjects(); - headContainer.Clear(); - tailContainer.Clear(); - repeatContainer.Clear(); - tickContainer.Clear(); + headContainer.Clear(false); + tailContainer.Clear(false); + repeatContainer.Clear(false); + tickContainer.Clear(false); } protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject) @@ -178,17 +178,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables return new DrawableSliderTail(tail); case SliderHeadCircle head: - return new DrawableSliderHead(HitObject, head) - { - OnShake = Shake, - CheckHittable = (d, t) => CheckHittable?.Invoke(d, t) ?? true - }; + return new DrawableSliderHead(head); case SliderTick tick: - return new DrawableSliderTick(tick) { Position = tick.Position - HitObject.Position }; + return new DrawableSliderTick(tick); case SliderRepeat repeat: - return new DrawableSliderRepeat(repeat, this) { Position = repeat.Position - HitObject.Position }; + return new DrawableSliderRepeat(repeat); } return base.CreateNestedHitObject(hitObject); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs index 49ed9f12e3..fd0f35d20d 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs @@ -4,6 +4,8 @@ using System; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; namespace osu.Game.Rulesets.Osu.Objects.Drawables @@ -14,21 +16,43 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected override OsuSkinComponents CirclePieceComponent => OsuSkinComponents.SliderHeadHitCircle; - private readonly Slider slider; + private DrawableSlider drawableSlider; - public DrawableSliderHead(Slider slider, SliderHeadCircle h) + private Slider slider => drawableSlider?.HitObject; + + public DrawableSliderHead() + { + } + + public DrawableSliderHead(SliderHeadCircle h) : base(h) { - this.slider = slider; } [BackgroundDependencyLoader] private void load() { - pathVersion.BindTo(slider.Path.Version); - PositionBindable.BindValueChanged(_ => updatePosition()); - pathVersion.BindValueChanged(_ => updatePosition(), true); + pathVersion.BindValueChanged(_ => updatePosition()); + } + + protected override void OnFree(HitObject hitObject) + { + base.OnFree(hitObject); + + pathVersion.UnbindFrom(drawableSlider.PathVersion); + } + + protected override void OnParentReceived(DrawableHitObject parent) + { + base.OnParentReceived(parent); + + drawableSlider = (DrawableSlider)parent; + + pathVersion.BindTo(drawableSlider.PathVersion); + + OnShake = drawableSlider.Shake; + CheckHittable = (d, t) => drawableSlider.CheckHittable?.Invoke(d, t) ?? true; } protected override void Update() @@ -44,8 +68,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public Action OnShake; - protected override void Shake(double maximumLength) => OnShake?.Invoke(maximumLength); + public override void Shake(double maximumLength) => OnShake?.Invoke(maximumLength); - private void updatePosition() => Position = HitObject.Position - slider.Position; + private void updatePosition() + { + if (slider != null) + Position = HitObject.Position - slider.Position; + } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs index 9c382bd0a7..0735d48ae1 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs @@ -16,8 +16,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { public class DrawableSliderRepeat : DrawableOsuHitObject, ITrackSnaking { - private readonly SliderRepeat sliderRepeat; - private readonly DrawableSlider drawableSlider; + public new SliderRepeat HitObject => (SliderRepeat)base.HitObject; private double animDuration; @@ -27,11 +26,16 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public override bool DisplayResult => false; - public DrawableSliderRepeat(SliderRepeat sliderRepeat, DrawableSlider drawableSlider) + private DrawableSlider drawableSlider; + + public DrawableSliderRepeat() + : base(null) + { + } + + public DrawableSliderRepeat(SliderRepeat sliderRepeat) : base(sliderRepeat) { - this.sliderRepeat = sliderRepeat; - this.drawableSlider = drawableSlider; } [BackgroundDependencyLoader] @@ -53,18 +57,27 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } }; - ScaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue), true); + ScaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue)); + } + + protected override void OnParentReceived(DrawableHitObject parent) + { + base.OnParentReceived(parent); + + drawableSlider = (DrawableSlider)parent; + + Position = HitObject.Position - drawableSlider.Position; } protected override void CheckForResult(bool userTriggered, double timeOffset) { - if (sliderRepeat.StartTime <= Time.Current) + if (HitObject.StartTime <= Time.Current) ApplyResult(r => r.Type = drawableSlider.Tracking.Value ? r.Judgement.MaxResult : r.Judgement.MinResult); } protected override void UpdateInitialTransforms() { - animDuration = Math.Min(300, sliderRepeat.SpanDuration); + animDuration = Math.Min(300, HitObject.SpanDuration); this.Animate( d => d.FadeIn(animDuration), @@ -100,7 +113,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables // When the repeat is hit, the arrow should fade out on spot rather than following the slider if (IsHit) return; - bool isRepeatAtEnd = sliderRepeat.RepeatIndex % 2 == 0; + bool isRepeatAtEnd = HitObject.RepeatIndex % 2 == 0; List curve = ((PlaySliderBody)drawableSlider.Body.Drawable).CurrentCurve; Position = isRepeatAtEnd ? end : start; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs index 3be5983c57..eff72168ee 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { public class DrawableSliderTail : DrawableOsuHitObject, IRequireTracking, ITrackSnaking { - private readonly SliderTailCircle tailCircle; + public new SliderTailCircle HitObject => (SliderTailCircle)base.HitObject; /// /// The judgement text is provided by the . @@ -25,10 +25,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private SkinnableDrawable circlePiece; private Container scaleContainer; + public DrawableSliderTail() + : base(null) + { + } + public DrawableSliderTail(SliderTailCircle tailCircle) : base(tailCircle) { - this.tailCircle = tailCircle; } [BackgroundDependencyLoader] @@ -52,7 +56,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables }, }; - ScaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue), true); + ScaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue)); } protected override void UpdateInitialTransforms() @@ -92,6 +96,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } public void UpdateSnakingPosition(Vector2 start, Vector2 end) => - Position = tailCircle.RepeatIndex % 2 == 0 ? end : start; + Position = HitObject.RepeatIndex % 2 == 0 ? end : start; } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs index 2af51ea486..faccf5d4d1 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs @@ -24,6 +24,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private SkinnableDrawable scaleContainer; + public DrawableSliderTick() + : base(null) + { + } + public DrawableSliderTick(SliderTick sliderTick) : base(sliderTick) { @@ -54,7 +59,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Origin = Anchor.Centre, }; - ScaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue), true); + ScaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue)); + } + + protected override void OnParentReceived(DrawableHitObject parent) + { + base.OnParentReceived(parent); + + Position = HitObject.Position - ((DrawableSlider)parent).HitObject.Position; } protected override void CheckForResult(bool userTriggered, double timeOffset) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index f04a914fe3..6b33517c33 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -160,7 +160,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected override void ClearNestedHitObjects() { base.ClearNestedHitObjects(); - ticks.Clear(); + ticks.Clear(false); } protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerBonusTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerBonusTick.cs index 2e1c07c4c6..ffeb14b0a8 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerBonusTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerBonusTick.cs @@ -5,6 +5,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { public class DrawableSpinnerBonusTick : DrawableSpinnerTick { + public DrawableSpinnerBonusTick() + : base(null) + { + } + public DrawableSpinnerBonusTick(SpinnerBonusTick spinnerTick) : base(spinnerTick) { diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs index e9cede1398..fc9a7c00e6 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs @@ -7,6 +7,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { public override bool DisplayResult => false; + public DrawableSpinnerTick() + : base(null) + { + } + public DrawableSpinnerTick(SpinnerTick spinnerTick) : base(spinnerTick) { diff --git a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs index 86c7305439..c89f138bcd 100644 --- a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs @@ -38,8 +38,16 @@ namespace osu.Game.Rulesets.Osu.UI private void load() { registerPool(10, 100); + registerPool(10, 100); + registerPool(10, 100); + registerPool(10, 100); + registerPool(10, 100); + registerPool(5, 50); + registerPool(2, 20); + registerPool(10, 100); + registerPool(10, 100); } private void registerPool(int initialSize, int? maximumSize = null) From 3f0a1271966dd84047f1ddbbbae18b7098d637be Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 12 Nov 2020 18:51:58 +0900 Subject: [PATCH 05/62] Fix slider/spinner samples not being disposed --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs | 6 ++++-- .../Objects/Drawables/DrawableSpinner.cs | 8 +++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 5bbdc5ee7b..04fc755da5 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -40,6 +40,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private Container tailContainer; private Container tickContainer; private Container repeatContainer; + private Container samplesContainer; public DrawableSlider() : this(null) @@ -68,6 +69,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Alpha = 0 }, headContainer = new Container { RelativeSizeAxes = Axes.Both }, + samplesContainer = new Container { RelativeSizeAxes = Axes.Both } }; PositionBindable.BindValueChanged(_ => Position = HitObject.StackedPosition); @@ -105,7 +107,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { base.LoadSamples(); - slidingSample?.Expire(); + samplesContainer.Clear(); slidingSample = null; var firstSample = HitObject.Samples.FirstOrDefault(); @@ -115,7 +117,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables var clone = HitObject.SampleControlPoint.ApplyTo(firstSample); clone.Name = "sliderslide"; - AddInternal(slidingSample = new PausableSkinnableSound(clone) + samplesContainer.Add(slidingSample = new PausableSkinnableSound(clone) { Looping = true }); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 6b33517c33..824b8806e5 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -29,6 +29,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private Container ticks; private SpinnerBonusDisplay bonusDisplay; + private Container samplesContainer; private Bindable isSpinning; private bool spinnerFrequencyModulate; @@ -75,7 +76,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Anchor = Anchor.Centre, Origin = Anchor.Centre, Y = -120, - } + }, + samplesContainer = new Container { RelativeSizeAxes = Axes.Both } }; PositionBindable.BindValueChanged(pos => Position = pos.NewValue); @@ -97,7 +99,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { base.LoadSamples(); - spinningSample?.Expire(); + samplesContainer.Clear(); spinningSample = null; var firstSample = HitObject.Samples.FirstOrDefault(); @@ -107,7 +109,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables var clone = HitObject.SampleControlPoint.ApplyTo(firstSample); clone.Name = "spinnerspin"; - AddInternal(spinningSample = new PausableSkinnableSound(clone) + samplesContainer.Add(spinningSample = new PausableSkinnableSound(clone) { Volume = { Value = 0 }, Looping = true, From beb6bbd2a14509f39bd5235bb8a7fb7583e51271 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 13 Nov 2020 14:58:32 +0900 Subject: [PATCH 06/62] Implement now abstract method --- osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs index c89f138bcd..1d16c47818 100644 --- a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs @@ -59,6 +59,8 @@ namespace osu.Game.Rulesets.Osu.UI where TDrawable : DrawableHitObject, new() => new OsuDrawablePool(Playfield.CheckHittable, Playfield.OnHitObjectLoaded, initialSize, maximumSize); + public override DrawableHitObject CreateDrawableRepresentation(OsuHitObject h) => null; + protected override HitObjectLifetimeEntry CreateLifetimeEntry(OsuHitObject hitObject) => new OsuHitObjectLifetimeEntry(hitObject); public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; // always show the gameplay cursor From f7f70d41dfb73719b99aadcaa592684621031687 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 13 Nov 2020 17:28:14 +0900 Subject: [PATCH 07/62] Add osu! editor pooling support --- .../Edit/DrawableOsuEditRuleset.cs | 51 +-------------- .../UI/OsuEditDrawablePool.cs | 63 +++++++++++++++++++ 2 files changed, 66 insertions(+), 48 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/UI/OsuEditDrawablePool.cs diff --git a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs index 746ff4ac19..05396ebc8b 100644 --- a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs +++ b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs @@ -2,13 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using System.Linq; -using osu.Framework.Graphics; +using osu.Framework.Graphics.Pooling; using osu.Game.Beatmaps; 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; @@ -17,54 +13,13 @@ namespace osu.Game.Rulesets.Osu.Edit { public class DrawableOsuEditRuleset : DrawableOsuRuleset { - /// - /// Hit objects are intentionally made to fade out at a constant slower rate than in gameplay. - /// This allows a mapper to gain better historical context and use recent hitobjects as reference / snap points. - /// - private const double editor_hit_object_fade_out_extension = 700; - public DrawableOsuEditRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods) : base(ruleset, beatmap, mods) { } - public override DrawableHitObject CreateDrawableRepresentation(OsuHitObject h) - => base.CreateDrawableRepresentation(h)?.With(d => d.ApplyCustomUpdateState += updateState); - - private void updateState(DrawableHitObject hitObject, ArmedState state) - { - if (state == ArmedState.Idle) - return; - - // adjust the visuals of certain object types to make them stay on screen for longer than usual. - switch (hitObject) - { - default: - // there are quite a few drawable hit types we don't want to extent (spinners, ticks etc.) - return; - - case DrawableSlider _: - // no specifics to sliders but let them fade slower below. - break; - - case DrawableHitCircle circle: // also handles slider heads - circle.ApproachCircle - .FadeOutFromOne(editor_hit_object_fade_out_extension) - .Expire(); - break; - } - - // Get the existing fade out transform - var existing = hitObject.Transforms.LastOrDefault(t => t.TargetMember == nameof(Alpha)); - - if (existing == null) - return; - - hitObject.RemoveTransform(existing); - - using (hitObject.BeginAbsoluteSequence(existing.StartTime)) - hitObject.FadeOut(editor_hit_object_fade_out_extension).Expire(); - } + protected override DrawablePool CreatePool(int initialSize, int? maximumSize = null) + => new OsuEditDrawablePool(Playfield.CheckHittable, Playfield.OnHitObjectLoaded, initialSize, maximumSize); protected override Playfield CreatePlayfield() => new OsuPlayfieldNoCursor(); diff --git a/osu.Game.Rulesets.Osu/UI/OsuEditDrawablePool.cs b/osu.Game.Rulesets.Osu/UI/OsuEditDrawablePool.cs new file mode 100644 index 0000000000..822ff076f5 --- /dev/null +++ b/osu.Game.Rulesets.Osu/UI/OsuEditDrawablePool.cs @@ -0,0 +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.Graphics; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables; + +namespace osu.Game.Rulesets.Osu.UI +{ + public class OsuEditDrawablePool : OsuDrawablePool + where T : DrawableHitObject, new() + { + /// + /// Hit objects are intentionally made to fade out at a constant slower rate than in gameplay. + /// This allows a mapper to gain better historical context and use recent hitobjects as reference / snap points. + /// + private const double editor_hit_object_fade_out_extension = 700; + + public OsuEditDrawablePool(Func checkHittable, Action onLoaded, int initialSize, int? maximumSize = null) + : base(checkHittable, onLoaded, initialSize, maximumSize) + { + } + + protected override T CreateNewDrawable() => base.CreateNewDrawable().With(d => d.ApplyCustomUpdateState += updateState); + + private void updateState(DrawableHitObject hitObject, ArmedState state) + { + if (state == ArmedState.Idle) + return; + + // adjust the visuals of certain object types to make them stay on screen for longer than usual. + switch (hitObject) + { + default: + // there are quite a few drawable hit types we don't want to extent (spinners, ticks etc.) + return; + + case DrawableSlider _: + // no specifics to sliders but let them fade slower below. + break; + + case DrawableHitCircle circle: // also handles slider heads + circle.ApproachCircle + .FadeOutFromOne(editor_hit_object_fade_out_extension) + .Expire(); + break; + } + + // Get the existing fade out transform + var existing = hitObject.Transforms.LastOrDefault(t => t.TargetMember == nameof(Alpha)); + + if (existing == null) + return; + + hitObject.RemoveTransform(existing); + + using (hitObject.BeginAbsoluteSequence(existing.StartTime)) + hitObject.FadeOut(editor_hit_object_fade_out_extension).Expire(); + } + } +} From 832d52a05678b723aed22832bfc31cb5a77e6c8a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 13 Nov 2020 18:19:07 +0900 Subject: [PATCH 08/62] Fix hitobject sample tests --- osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs | 2 -- osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs | 11 +++++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs b/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs index 6b95931b21..64eaafbe75 100644 --- a/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs @@ -3,7 +3,6 @@ using NUnit.Framework; using osu.Framework.IO.Stores; -using osu.Framework.Testing; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Tests.Beatmaps; @@ -12,7 +11,6 @@ using static osu.Game.Skinning.LegacySkinConfiguration; namespace osu.Game.Tests.Gameplay { - [HeadlessTest] public class TestSceneHitObjectSamples : HitObjectSampleTest { protected override Ruleset CreatePlayerRuleset() => new OsuRuleset(); diff --git a/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs b/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs index 1e43e5d148..e3557222d5 100644 --- a/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs +++ b/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs @@ -9,11 +9,14 @@ using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.IO.Stores; +using osu.Framework.Testing; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Formats; using osu.Game.IO; using osu.Game.Rulesets; +using osu.Game.Rulesets.Objects; +using osu.Game.Screens.Ranking; using osu.Game.Skinning; using osu.Game.Storyboards; using osu.Game.Tests.Visual; @@ -21,6 +24,7 @@ using osu.Game.Users; namespace osu.Game.Tests.Beatmaps { + [HeadlessTest] public abstract class HitObjectSampleTest : PlayerTestScene { protected abstract IResourceStore Resources { get; } @@ -44,7 +48,9 @@ namespace osu.Game.Tests.Beatmaps private readonly TestResourceStore beatmapSkinResourceStore = new TestResourceStore(); private SkinSourceDependencyContainer dependencies; private IBeatmap currentTestBeatmap; + protected sealed override bool HasCustomSteps => true; + protected override bool Autoplay => true; protected sealed override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => new DependencyContainer(dependencies = new SkinSourceDependencyContainer(base.CreateChildDependencies(parent))); @@ -54,6 +60,8 @@ namespace osu.Game.Tests.Beatmaps protected sealed override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) => new TestWorkingBeatmap(beatmapInfo, beatmapSkinResourceStore, beatmap, storyboard, Clock, Audio); + protected override TestPlayer CreatePlayer(Ruleset ruleset) => new TestPlayer(false); + protected void CreateTestWithBeatmap(string filename) { CreateTest(() => @@ -73,6 +81,9 @@ namespace osu.Game.Tests.Beatmaps currentTestBeatmap.BeatmapInfo.Ruleset = rulesetStore.GetRuleset(currentTestBeatmap.BeatmapInfo.RulesetID); }); }); + + AddStep("seek to completion", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.Objects.Last().GetEndTime())); + AddUntilStep("results displayed", () => Stack.CurrentScreen is ResultsScreen); } protected void SetupSkins(string beatmapFile, string userFile) From feabca860bbbdc603fa391c7f9d19822789d26cc Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 13 Nov 2020 18:35:33 +0900 Subject: [PATCH 09/62] Fix sample playback test --- .../Visual/Gameplay/TestSceneGameplaySamplePlayback.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs index b86cb69eb4..7c6a213fe2 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs @@ -23,11 +23,13 @@ namespace osu.Game.Tests.Visual.Gameplay DrawableSample[] samples = null; ISamplePlaybackDisabler sampleDisabler = null; - AddStep("get variables", () => + AddUntilStep("get variables", () => { sampleDisabler = Player; - slider = Player.ChildrenOfType().OrderBy(s => s.HitObject.StartTime).First(); - samples = slider.ChildrenOfType().ToArray(); + slider = Player.ChildrenOfType().OrderBy(s => s.HitObject.StartTime).FirstOrDefault(); + samples = slider?.ChildrenOfType().ToArray(); + + return slider != null; }); AddUntilStep("wait for slider sliding then seek", () => From 1e05fd48e239e1f8622935122bb2bfb82e2af467 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 13 Nov 2020 21:43:53 +0900 Subject: [PATCH 10/62] Fix hidden mod crash --- osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs index 84a335750a..7c1dd46c02 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs @@ -28,10 +28,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override void ApplyToDrawableHitObjects(IEnumerable drawables) { foreach (var d in drawables) - { d.HitObjectApplied += applyFadeInAdjustment; - applyFadeInAdjustment(d); - } base.ApplyToDrawableHitObjects(drawables); } From 9792d1fc738ffeaad759d3a6826af94de612f763 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 13 Nov 2020 23:03:44 +0900 Subject: [PATCH 11/62] Fix slider tests --- .../Mods/TestSceneOsuModSpunOut.cs | 8 ++++++-- .../TestSceneSliderSnaking.cs | 17 ++++++++-------- osu.Game/Rulesets/UI/DrawableRuleset.cs | 20 ++++++++++++++++--- 3 files changed, 32 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs index d8064d36ea..7b909d2907 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Mod = new OsuModSpunOut(), Autoplay = false, Beatmap = singleSpinnerBeatmap, - PassCondition = () => Player.ChildrenOfType().Single().Progress >= 1 + PassCondition = () => Player.ChildrenOfType().SingleOrDefault()?.Progress >= 1 }); [TestCase(null)] @@ -45,7 +45,11 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Mods = mods, Autoplay = false, Beatmap = singleSpinnerBeatmap, - PassCondition = () => Precision.AlmostEquals(Player.ChildrenOfType().Single().SpinsPerMinute, 286, 1) + PassCondition = () => + { + var counter = Player.ChildrenOfType().SingleOrDefault(); + return counter != null && Precision.AlmostEquals(counter.SpinsPerMinute, 286, 1); + } }); } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index 3d100e4b1c..b71400b71d 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Osu.Tests config.BindWith(OsuRulesetSetting.SnakingOutSliders, snakingOut); } - private DrawableSlider slider; + private DrawableSlider drawableSlider; [SetUpSteps] public override void SetUpSteps() @@ -68,7 +68,8 @@ namespace osu.Game.Rulesets.Osu.Tests AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning); double startTime = hitObjects[sliderIndex].StartTime; - retrieveDrawableSlider(sliderIndex); + addSeekStep(startTime); + retrieveDrawableSlider((Slider)hitObjects[sliderIndex]); setSnaking(true); ensureSnakingIn(startTime + fade_in_modifier); @@ -93,7 +94,8 @@ namespace osu.Game.Rulesets.Osu.Tests AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning); double startTime = hitObjects[sliderIndex].StartTime; - retrieveDrawableSlider(sliderIndex); + addSeekStep(startTime); + retrieveDrawableSlider((Slider)hitObjects[sliderIndex]); setSnaking(false); ensureNoSnakingIn(startTime + fade_in_modifier); @@ -127,9 +129,8 @@ namespace osu.Game.Rulesets.Osu.Tests checkPositionChange(16600, sliderRepeat, positionDecreased); } - private void retrieveDrawableSlider(int index) => - AddStep($"retrieve {(index + 1).ToOrdinalWords()} slider", () => - slider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.ElementAt(index)); + private void retrieveDrawableSlider(Slider slider) => AddUntilStep($"retrieve slider @ {slider.StartTime}", () => + (drawableSlider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.SingleOrDefault(d => d.HitObject == slider)) != null); private void ensureSnakingIn(double startTime) => checkPositionChange(startTime, sliderEnd, positionIncreased); private void ensureNoSnakingIn(double startTime) => checkPositionChange(startTime, sliderEnd, positionRemainsSame); @@ -150,13 +151,13 @@ namespace osu.Game.Rulesets.Osu.Tests private double timeAtRepeat(double startTime, int repeatIndex) => startTime + 100 + duration_of_span * repeatIndex; private Func positionAtRepeat(int repeatIndex) => repeatIndex % 2 == 0 ? (Func)sliderStart : sliderEnd; - private List sliderCurve => ((PlaySliderBody)slider.Body.Drawable).CurrentCurve; + private List sliderCurve => ((PlaySliderBody)drawableSlider.Body.Drawable).CurrentCurve; private Vector2 sliderStart() => sliderCurve.First(); private Vector2 sliderEnd() => sliderCurve.Last(); private Vector2 sliderRepeat() { - var drawable = Player.DrawableRuleset.Playfield.AllHitObjects.ElementAt(1); + var drawable = Player.DrawableRuleset.Playfield.AllHitObjects.SingleOrDefault(d => d.HitObject == hitObjects[1]); var repeat = drawable.ChildrenOfType>().First().Children.First(); return repeat.Position; } diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index c912348604..e3c81d0f57 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -593,10 +593,24 @@ namespace osu.Game.Rulesets.UI [CanBeNull] public DrawableHitObject GetPooledDrawableRepresentation([NotNull] HitObject hitObject) { - if (!pools.TryGetValue(hitObject.GetType(), out var pool)) - return null; + var lookupType = hitObject.GetType(); - return (DrawableHitObject)pool.Get(d => + IDrawablePool pool; + + // Tests may add derived hitobject instances for which pools don't exist. Try to find any applicable pool and dynamically assign the type if the pool exists. + if (!pools.TryGetValue(lookupType, out pool)) + { + foreach (var (t, p) in pools) + { + if (!t.IsInstanceOfType(hitObject)) + continue; + + pools[lookupType] = pool = p; + break; + } + } + + return (DrawableHitObject)pool?.Get(d => { var dho = (DrawableHitObject)d; From 36f1833f6eecbbacbb69d85c01a0d53d07f23241 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Sat, 14 Nov 2020 00:41:18 +0900 Subject: [PATCH 12/62] Move hitobject pooling to Playfield --- .../Gameplay/TestScenePoolingRuleset.cs | 19 +-- .../Objects/Drawables/DrawableHitObject.cs | 4 +- osu.Game/Rulesets/UI/DrawableRuleset.cs | 69 +---------- osu.Game/Rulesets/UI/HitObjectContainer.cs | 4 +- osu.Game/Rulesets/UI/HitObjectPoolProvider.cs | 111 ++++++++++++++++++ osu.Game/Rulesets/UI/Playfield.cs | 3 +- 6 files changed, 130 insertions(+), 80 deletions(-) create mode 100644 osu.Game/Rulesets/UI/HitObjectPoolProvider.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs index 242eaf7b7d..2e1e667d0d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs @@ -134,19 +134,13 @@ namespace osu.Game.Tests.Visual.Gameplay { } - [BackgroundDependencyLoader] - private void load() - { - RegisterPool(PoolSize); - } - protected override HitObjectLifetimeEntry CreateLifetimeEntry(TestHitObject hitObject) => new TestHitObjectLifetimeEntry(hitObject); public override DrawableHitObject CreateDrawableRepresentation(TestHitObject h) => null; protected override PassThroughInputManager CreateInputManager() => new PassThroughInputManager(); - protected override Playfield CreatePlayfield() => new TestPlayfield(); + protected override Playfield CreatePlayfield() => new TestPlayfield(PoolSize); private class TestHitObjectLifetimeEntry : HitObjectLifetimeEntry { @@ -161,11 +155,20 @@ namespace osu.Game.Tests.Visual.Gameplay private class TestPlayfield : Playfield { - public TestPlayfield() + private readonly int poolSize; + + public TestPlayfield(int poolSize) { + this.poolSize = poolSize; AddInternal(HitObjectContainer); } + [BackgroundDependencyLoader] + private void load() + { + RegisterPool(poolSize); + } + protected override GameplayCursorContainer CreateCursor() => null; } diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 3e3936b45a..c22257e544 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -137,7 +137,7 @@ namespace osu.Game.Rulesets.Objects.Drawables private HitObjectLifetimeEntry lifetimeEntry; [Resolved(CanBeNull = true)] - private DrawableRuleset drawableRuleset { get; set; } + private HitObjectPoolProvider poolProvider { get; set; } private Container samplesContainer; @@ -212,7 +212,7 @@ namespace osu.Game.Rulesets.Objects.Drawables foreach (var h in HitObject.NestedHitObjects) { - var drawableNested = drawableRuleset?.GetPooledDrawableRepresentation(h) + var drawableNested = poolProvider?.GetPooledDrawableRepresentation(h) ?? CreateNestedHitObject(h) ?? throw new InvalidOperationException($"{nameof(CreateNestedHitObject)} returned null for {h.GetType().ReadableName()}."); diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index c912348604..5022b571fd 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -15,10 +15,8 @@ using System.Linq; using System.Threading; using JetBrains.Annotations; using osu.Framework.Bindables; -using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Graphics.Cursor; -using osu.Framework.Graphics.Pooling; using osu.Framework.Input; using osu.Framework.Input.Events; using osu.Game.Configuration; @@ -327,9 +325,8 @@ namespace osu.Game.Rulesets.UI /// Creates a to represent a . /// /// - /// If this method returns null, then this will assume the requested type is being pooled, - /// and will instead attempt to retrieve the s at the point they should become alive via pools registered through - /// or . + /// If this method returns null, then this will assume the requested type is being pooled inside the , + /// and will instead attempt to retrieve the s at the point they should become alive via pools registered in the . /// /// The to represent. /// The representing . @@ -550,68 +547,8 @@ namespace osu.Game.Rulesets.UI /// public abstract void CancelResume(); - private readonly Dictionary pools = new Dictionary(); private readonly Dictionary lifetimeEntries = new Dictionary(); - /// - /// Registers a default pool with this which is to be used whenever - /// representations are requested for the given type (via ). - /// - /// The number of s to be initially stored in the pool. - /// - /// The maximum number of s that can be stored in the pool. - /// If this limit is exceeded, every subsequent will be created anew instead of being retrieved from the pool, - /// until some of the existing s are returned to the pool. - /// - /// The type. - /// The receiver for s. - protected void RegisterPool(int initialSize, int? maximumSize = null) - where TObject : HitObject - where TDrawable : DrawableHitObject, new() - => RegisterPool(new DrawablePool(initialSize, maximumSize)); - - /// - /// Registers a custom pool with this which is to be used whenever - /// representations are requested for the given type (via ). - /// - /// The to register. - /// The type. - /// The receiver for s. - protected void RegisterPool([NotNull] DrawablePool pool) - where TObject : HitObject - where TDrawable : DrawableHitObject, new() - { - pools[typeof(TObject)] = pool; - AddInternal(pool); - } - - /// - /// Attempts to retrieve the poolable representation of a . - /// - /// The to retrieve the representation of. - /// The representing , or null if no poolable representation exists. - [CanBeNull] - public DrawableHitObject GetPooledDrawableRepresentation([NotNull] HitObject hitObject) - { - if (!pools.TryGetValue(hitObject.GetType(), out var pool)) - return null; - - return (DrawableHitObject)pool.Get(d => - { - var dho = (DrawableHitObject)d; - - // If this is the first time this DHO is being used (not loaded), then apply the DHO mods. - // This is done before Apply() so that the state is updated once when the hitobject is applied. - if (!dho.IsLoaded) - { - foreach (var m in Mods.OfType()) - m.ApplyToDrawableHitObjects(dho.Yield()); - } - - dho.Apply(hitObject, GetLifetimeEntry(hitObject)); - }); - } - /// /// Creates the for a given . /// @@ -629,7 +566,7 @@ namespace osu.Game.Rulesets.UI /// The to retrieve or create the for. /// The for . [NotNull] - protected HitObjectLifetimeEntry GetLifetimeEntry([NotNull] HitObject hitObject) + internal HitObjectLifetimeEntry GetLifetimeEntry([NotNull] HitObject hitObject) { if (lifetimeEntries.TryGetValue(hitObject, out var entry)) return entry; diff --git a/osu.Game/Rulesets/UI/HitObjectContainer.cs b/osu.Game/Rulesets/UI/HitObjectContainer.cs index 25fb7ab9f3..8de9f41482 100644 --- a/osu.Game/Rulesets/UI/HitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/HitObjectContainer.cs @@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.UI private readonly LifetimeEntryManager lifetimeManager = new LifetimeEntryManager(); [Resolved(CanBeNull = true)] - private DrawableRuleset drawableRuleset { get; set; } + private HitObjectPoolProvider poolProvider { get; set; } public HitObjectContainer() { @@ -105,7 +105,7 @@ namespace osu.Game.Rulesets.UI { Debug.Assert(!drawableMap.ContainsKey(entry)); - var drawable = drawableRuleset.GetPooledDrawableRepresentation(entry.HitObject); + var drawable = poolProvider.GetPooledDrawableRepresentation(entry.HitObject); if (drawable == null) throw new InvalidOperationException($"A drawable representation could not be retrieved for hitobject type: {entry.HitObject.GetType().ReadableName()}."); diff --git a/osu.Game/Rulesets/UI/HitObjectPoolProvider.cs b/osu.Game/Rulesets/UI/HitObjectPoolProvider.cs new file mode 100644 index 0000000000..ee3aee59b4 --- /dev/null +++ b/osu.Game/Rulesets/UI/HitObjectPoolProvider.cs @@ -0,0 +1,111 @@ +// 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 JetBrains.Annotations; +using osu.Framework.Allocation; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Pooling; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; + +namespace osu.Game.Rulesets.UI +{ + /// + /// A that pools s and allows children to retrieve them via . + /// + [Cached(typeof(HitObjectPoolProvider))] + public class HitObjectPoolProvider : CompositeDrawable + { + [Resolved] + private DrawableRuleset drawableRuleset { get; set; } + + [Resolved] + private IReadOnlyList mods { get; set; } + + [Resolved(CanBeNull = true)] + private HitObjectPoolProvider parentProvider { get; set; } + + private readonly Dictionary pools = new Dictionary(); + + /// + /// Registers a default pool with this which is to be used whenever + /// representations are requested for the given type (via ). + /// + /// The number of s to be initially stored in the pool. + /// + /// The maximum number of s that can be stored in the pool. + /// If this limit is exceeded, every subsequent will be created anew instead of being retrieved from the pool, + /// until some of the existing s are returned to the pool. + /// + /// The type. + /// The receiver for s. + protected void RegisterPool(int initialSize, int? maximumSize = null) + where TObject : HitObject + where TDrawable : DrawableHitObject, new() + => RegisterPool(new DrawablePool(initialSize, maximumSize)); + + /// + /// Registers a custom pool with this which is to be used whenever + /// representations are requested for the given type (via ). + /// + /// The to register. + /// The type. + /// The receiver for s. + protected void RegisterPool([NotNull] DrawablePool pool) + where TObject : HitObject + where TDrawable : DrawableHitObject, new() + { + pools[typeof(TObject)] = pool; + AddInternal(pool); + } + + /// + /// Attempts to retrieve the poolable representation of a . + /// + /// The to retrieve the representation of. + /// The representing , or null if no poolable representation exists. + [CanBeNull] + public DrawableHitObject GetPooledDrawableRepresentation([NotNull] HitObject hitObject) + { + var lookupType = hitObject.GetType(); + + IDrawablePool pool; + + // Tests may add derived hitobject instances for which pools don't exist. Try to find any applicable pool and dynamically assign the type if the pool exists. + if (!pools.TryGetValue(lookupType, out pool)) + { + foreach (var (t, p) in pools) + { + if (!t.IsInstanceOfType(hitObject)) + continue; + + pools[lookupType] = pool = p; + break; + } + } + + if (pool == null) + return parentProvider?.GetPooledDrawableRepresentation(hitObject); + + return (DrawableHitObject)pool.Get(d => + { + var dho = (DrawableHitObject)d; + + // If this is the first time this DHO is being used (not loaded), then apply the DHO mods. + // This is done before Apply() so that the state is updated once when the hitobject is applied. + if (!dho.IsLoaded) + { + foreach (var m in mods.OfType()) + m.ApplyToDrawableHitObjects(dho.Yield()); + } + + dho.Apply(hitObject, drawableRuleset.GetLifetimeEntry(hitObject)); + }); + } + } +} diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index 6747145d50..1d0196d173 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -9,7 +9,6 @@ using osu.Game.Rulesets.Objects.Drawables; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; @@ -17,7 +16,7 @@ using osuTK; namespace osu.Game.Rulesets.UI { - public abstract class Playfield : CompositeDrawable + public abstract class Playfield : HitObjectPoolProvider { /// /// Invoked when a is judged. From c71b237c4f8d73ce957a23ae4a2c56478725307a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Sat, 14 Nov 2020 00:54:57 +0900 Subject: [PATCH 13/62] Merge all pooling support into Playfield --- .../Gameplay/TestScenePoolingRuleset.cs | 24 +-- .../Objects/Drawables/DrawableHitObject.cs | 4 +- osu.Game/Rulesets/UI/DrawableRuleset.cs | 55 +----- osu.Game/Rulesets/UI/HitObjectContainer.cs | 4 +- osu.Game/Rulesets/UI/HitObjectPoolProvider.cs | 111 ------------ .../Rulesets/UI/IPooledHitObjectProvider.cs | 20 +++ osu.Game/Rulesets/UI/Playfield.cs | 167 ++++++++++++++---- 7 files changed, 172 insertions(+), 213 deletions(-) delete mode 100644 osu.Game/Rulesets/UI/HitObjectPoolProvider.cs create mode 100644 osu.Game/Rulesets/UI/IPooledHitObjectProvider.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs index 2e1e667d0d..d009d805f0 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs @@ -134,23 +134,11 @@ namespace osu.Game.Tests.Visual.Gameplay { } - protected override HitObjectLifetimeEntry CreateLifetimeEntry(TestHitObject hitObject) => new TestHitObjectLifetimeEntry(hitObject); - public override DrawableHitObject CreateDrawableRepresentation(TestHitObject h) => null; protected override PassThroughInputManager CreateInputManager() => new PassThroughInputManager(); protected override Playfield CreatePlayfield() => new TestPlayfield(PoolSize); - - private class TestHitObjectLifetimeEntry : HitObjectLifetimeEntry - { - public TestHitObjectLifetimeEntry(HitObject hitObject) - : base(hitObject) - { - } - - protected override double InitialLifetimeOffset => 0; - } } private class TestPlayfield : Playfield @@ -169,9 +157,21 @@ namespace osu.Game.Tests.Visual.Gameplay RegisterPool(poolSize); } + protected override HitObjectLifetimeEntry CreateLifetimeEntry(HitObject hitObject) => new TestHitObjectLifetimeEntry(hitObject); + protected override GameplayCursorContainer CreateCursor() => null; } + private class TestHitObjectLifetimeEntry : HitObjectLifetimeEntry + { + public TestHitObjectLifetimeEntry(HitObject hitObject) + : base(hitObject) + { + } + + protected override double InitialLifetimeOffset => 0; + } + private class TestBeatmapConverter : BeatmapConverter { public TestBeatmapConverter(IBeatmap beatmap, Ruleset ruleset) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index c22257e544..b400c532c5 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -137,7 +137,7 @@ namespace osu.Game.Rulesets.Objects.Drawables private HitObjectLifetimeEntry lifetimeEntry; [Resolved(CanBeNull = true)] - private HitObjectPoolProvider poolProvider { get; set; } + private IPooledHitObjectProvider pooledObjectProvider { get; set; } private Container samplesContainer; @@ -212,7 +212,7 @@ namespace osu.Game.Rulesets.Objects.Drawables foreach (var h in HitObject.NestedHitObjects) { - var drawableNested = poolProvider?.GetPooledDrawableRepresentation(h) + var drawableNested = pooledObjectProvider?.GetPooledDrawableRepresentation(h) ?? CreateNestedHitObject(h) ?? throw new InvalidOperationException($"{nameof(CreateNestedHitObject)} returned null for {h.GetType().ReadableName()}."); diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 5022b571fd..c1a601eaae 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -15,7 +15,6 @@ using System.Linq; using System.Threading; using JetBrains.Annotations; using osu.Framework.Bindables; -using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Graphics.Cursor; using osu.Framework.Input; using osu.Framework.Input.Events; @@ -246,7 +245,7 @@ namespace osu.Game.Rulesets.UI if (drawableRepresentation != null) Playfield.Add(drawableRepresentation); else - Playfield.Add(GetLifetimeEntry(hitObject)); + Playfield.Add(hitObject); } /// @@ -258,15 +257,10 @@ namespace osu.Game.Rulesets.UI /// The to remove. public bool RemoveHitObject(TObject hitObject) { - var entry = GetLifetimeEntry(hitObject); - - // May have been newly-created by the above call - remove it anyway. - RemoveLifetimeEntry(hitObject); - - if (Playfield.Remove(entry)) + if (Playfield.Remove(hitObject)) return true; - // If the entry was not removed from the playfield, assume the hitobject is not being pooled and attempt a direct removal. + // If the entry was not removed from the playfield, assume the hitobject is not being pooled and attempt a direct drawable removal. var drawableObject = Playfield.AllHitObjects.SingleOrDefault(d => d.HitObject == hitObject); if (drawableObject != null) return Playfield.Remove(drawableObject); @@ -274,16 +268,6 @@ namespace osu.Game.Rulesets.UI return false; } - protected sealed override HitObjectLifetimeEntry CreateLifetimeEntry(HitObject hitObject) - { - if (!(hitObject is TObject tHitObject)) - throw new InvalidOperationException($"Unexpected hitobject type: {hitObject.GetType().ReadableName()}"); - - return CreateLifetimeEntry(tHitObject); - } - - protected virtual HitObjectLifetimeEntry CreateLifetimeEntry(TObject hitObject) => new HitObjectLifetimeEntry(hitObject); - public override void SetRecordTarget(Replay recordingReplay) { if (!(KeyBindingInputManager is IHasRecordingHandler recordingInputManager)) @@ -546,39 +530,6 @@ namespace osu.Game.Rulesets.UI /// Invoked when the user requests to pause while the resume overlay is active. /// public abstract void CancelResume(); - - private readonly Dictionary lifetimeEntries = new Dictionary(); - - /// - /// Creates the for a given . - /// - /// - /// This may be overridden to provide custom lifetime control (e.g. via . - /// - /// The to create the entry for. - /// The . - [NotNull] - protected abstract HitObjectLifetimeEntry CreateLifetimeEntry([NotNull] HitObject hitObject); - - /// - /// Retrieves or creates the for a given . - /// - /// The to retrieve or create the for. - /// The for . - [NotNull] - internal HitObjectLifetimeEntry GetLifetimeEntry([NotNull] HitObject hitObject) - { - if (lifetimeEntries.TryGetValue(hitObject, out var entry)) - return entry; - - return lifetimeEntries[hitObject] = CreateLifetimeEntry(hitObject); - } - - /// - /// Removes the for a . - /// - /// The to remove the for. - internal void RemoveLifetimeEntry([NotNull] HitObject hitObject) => lifetimeEntries.Remove(hitObject); } public class BeatmapInvalidForRulesetException : ArgumentException diff --git a/osu.Game/Rulesets/UI/HitObjectContainer.cs b/osu.Game/Rulesets/UI/HitObjectContainer.cs index 8de9f41482..1dc029506f 100644 --- a/osu.Game/Rulesets/UI/HitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/HitObjectContainer.cs @@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.UI private readonly LifetimeEntryManager lifetimeManager = new LifetimeEntryManager(); [Resolved(CanBeNull = true)] - private HitObjectPoolProvider poolProvider { get; set; } + private IPooledHitObjectProvider pooledObjectProvider { get; set; } public HitObjectContainer() { @@ -105,7 +105,7 @@ namespace osu.Game.Rulesets.UI { Debug.Assert(!drawableMap.ContainsKey(entry)); - var drawable = poolProvider.GetPooledDrawableRepresentation(entry.HitObject); + var drawable = pooledObjectProvider.GetPooledDrawableRepresentation(entry.HitObject); if (drawable == null) throw new InvalidOperationException($"A drawable representation could not be retrieved for hitobject type: {entry.HitObject.GetType().ReadableName()}."); diff --git a/osu.Game/Rulesets/UI/HitObjectPoolProvider.cs b/osu.Game/Rulesets/UI/HitObjectPoolProvider.cs deleted file mode 100644 index ee3aee59b4..0000000000 --- a/osu.Game/Rulesets/UI/HitObjectPoolProvider.cs +++ /dev/null @@ -1,111 +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 JetBrains.Annotations; -using osu.Framework.Allocation; -using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Pooling; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Drawables; - -namespace osu.Game.Rulesets.UI -{ - /// - /// A that pools s and allows children to retrieve them via . - /// - [Cached(typeof(HitObjectPoolProvider))] - public class HitObjectPoolProvider : CompositeDrawable - { - [Resolved] - private DrawableRuleset drawableRuleset { get; set; } - - [Resolved] - private IReadOnlyList mods { get; set; } - - [Resolved(CanBeNull = true)] - private HitObjectPoolProvider parentProvider { get; set; } - - private readonly Dictionary pools = new Dictionary(); - - /// - /// Registers a default pool with this which is to be used whenever - /// representations are requested for the given type (via ). - /// - /// The number of s to be initially stored in the pool. - /// - /// The maximum number of s that can be stored in the pool. - /// If this limit is exceeded, every subsequent will be created anew instead of being retrieved from the pool, - /// until some of the existing s are returned to the pool. - /// - /// The type. - /// The receiver for s. - protected void RegisterPool(int initialSize, int? maximumSize = null) - where TObject : HitObject - where TDrawable : DrawableHitObject, new() - => RegisterPool(new DrawablePool(initialSize, maximumSize)); - - /// - /// Registers a custom pool with this which is to be used whenever - /// representations are requested for the given type (via ). - /// - /// The to register. - /// The type. - /// The receiver for s. - protected void RegisterPool([NotNull] DrawablePool pool) - where TObject : HitObject - where TDrawable : DrawableHitObject, new() - { - pools[typeof(TObject)] = pool; - AddInternal(pool); - } - - /// - /// Attempts to retrieve the poolable representation of a . - /// - /// The to retrieve the representation of. - /// The representing , or null if no poolable representation exists. - [CanBeNull] - public DrawableHitObject GetPooledDrawableRepresentation([NotNull] HitObject hitObject) - { - var lookupType = hitObject.GetType(); - - IDrawablePool pool; - - // Tests may add derived hitobject instances for which pools don't exist. Try to find any applicable pool and dynamically assign the type if the pool exists. - if (!pools.TryGetValue(lookupType, out pool)) - { - foreach (var (t, p) in pools) - { - if (!t.IsInstanceOfType(hitObject)) - continue; - - pools[lookupType] = pool = p; - break; - } - } - - if (pool == null) - return parentProvider?.GetPooledDrawableRepresentation(hitObject); - - return (DrawableHitObject)pool.Get(d => - { - var dho = (DrawableHitObject)d; - - // If this is the first time this DHO is being used (not loaded), then apply the DHO mods. - // This is done before Apply() so that the state is updated once when the hitobject is applied. - if (!dho.IsLoaded) - { - foreach (var m in mods.OfType()) - m.ApplyToDrawableHitObjects(dho.Yield()); - } - - dho.Apply(hitObject, drawableRuleset.GetLifetimeEntry(hitObject)); - }); - } - } -} diff --git a/osu.Game/Rulesets/UI/IPooledHitObjectProvider.cs b/osu.Game/Rulesets/UI/IPooledHitObjectProvider.cs new file mode 100644 index 0000000000..d8240d892f --- /dev/null +++ b/osu.Game/Rulesets/UI/IPooledHitObjectProvider.cs @@ -0,0 +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 JetBrains.Annotations; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; + +namespace osu.Game.Rulesets.UI +{ + internal interface IPooledHitObjectProvider + { + /// + /// Attempts to retrieve the poolable representation of a . + /// + /// The to retrieve the representation of. + /// The representing , or null if no poolable representation exists. + [CanBeNull] + public DrawableHitObject GetPooledDrawableRepresentation([NotNull] HitObject hitObject); + } +} diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index 1d0196d173..80e33e0ec5 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -4,11 +4,14 @@ using System; using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; using osu.Framework.Graphics; using osu.Game.Rulesets.Objects.Drawables; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Pooling; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; @@ -16,7 +19,8 @@ using osuTK; namespace osu.Game.Rulesets.UI { - public abstract class Playfield : HitObjectPoolProvider + [Cached(typeof(IPooledHitObjectProvider))] + public abstract class Playfield : CompositeDrawable, IPooledHitObjectProvider { /// /// Invoked when a is judged. @@ -137,39 +141,6 @@ namespace osu.Game.Rulesets.UI return false; } - /// - /// Adds a for a pooled to this . - /// - /// The controlling the lifetime of the . - public virtual void Add(HitObjectLifetimeEntry entry) - { - HitObjectContainer.Add(entry); - lifetimeEntryMap[entry.HitObject] = entry; - OnHitObjectAdded(entry.HitObject); - } - - /// - /// Removes a for a pooled from this . - /// - /// The controlling the lifetime of the . - /// Whether the was successfully removed. - public virtual bool Remove(HitObjectLifetimeEntry entry) - { - if (HitObjectContainer.Remove(entry)) - { - lifetimeEntryMap.Remove(entry.HitObject); - OnHitObjectRemoved(entry.HitObject); - return true; - } - - bool removedFromNested = false; - - if (nestedPlayfields.IsValueCreated) - removedFromNested = nestedPlayfields.Value.Any(p => p.Remove(entry)); - - return removedFromNested; - } - /// /// Invoked when a is added to this . /// @@ -245,6 +216,134 @@ namespace osu.Game.Rulesets.UI /// protected virtual HitObjectContainer CreateHitObjectContainer() => new HitObjectContainer(); + #region Pooling support + + [Resolved(CanBeNull = true)] + private IPooledHitObjectProvider parentPooledObjectProvider { get; set; } + + private readonly Dictionary pools = new Dictionary(); + + /// + /// Adds a for a pooled to this . + /// + /// + public virtual void Add(HitObject hitObject) + { + var entry = CreateLifetimeEntry(hitObject); + lifetimeEntryMap[entry.HitObject] = entry; + + HitObjectContainer.Add(entry); + OnHitObjectAdded(entry.HitObject); + } + + /// + /// Removes a for a pooled from this . + /// + /// + /// Whether the was successfully removed. + public virtual bool Remove(HitObject hitObject) + { + if (lifetimeEntryMap.Remove(hitObject, out var entry)) + { + HitObjectContainer.Remove(entry); + OnHitObjectRemoved(hitObject); + return true; + } + + bool removedFromNested = false; + + if (nestedPlayfields.IsValueCreated) + removedFromNested = nestedPlayfields.Value.Any(p => p.Remove(hitObject)); + + return removedFromNested; + } + + /// + /// Creates the for a given . + /// + /// + /// This may be overridden to provide custom lifetime control (e.g. via . + /// + /// The to create the entry for. + /// The . + [NotNull] + protected virtual HitObjectLifetimeEntry CreateLifetimeEntry([NotNull] HitObject hitObject) => new HitObjectLifetimeEntry(hitObject); + + /// + /// Registers a default pool with this which is to be used whenever + /// representations are requested for the given type. + /// + /// The number of s to be initially stored in the pool. + /// + /// The maximum number of s that can be stored in the pool. + /// If this limit is exceeded, every subsequent will be created anew instead of being retrieved from the pool, + /// until some of the existing s are returned to the pool. + /// + /// The type. + /// The receiver for s. + protected void RegisterPool(int initialSize, int? maximumSize = null) + where TObject : HitObject + where TDrawable : DrawableHitObject, new() + => RegisterPool(new DrawablePool(initialSize, maximumSize)); + + /// + /// Registers a custom pool with this which is to be used whenever + /// representations are requested for the given type. + /// + /// The to register. + /// The type. + /// The receiver for s. + protected void RegisterPool([NotNull] DrawablePool pool) + where TObject : HitObject + where TDrawable : DrawableHitObject, new() + { + pools[typeof(TObject)] = pool; + AddInternal(pool); + } + + DrawableHitObject IPooledHitObjectProvider.GetPooledDrawableRepresentation(HitObject hitObject) + { + var lookupType = hitObject.GetType(); + + IDrawablePool pool; + + // Tests may add derived hitobject instances for which pools don't exist. Try to find any applicable pool and dynamically assign the type if the pool exists. + if (!pools.TryGetValue(lookupType, out pool)) + { + foreach (var (t, p) in pools) + { + if (!t.IsInstanceOfType(hitObject)) + continue; + + pools[lookupType] = pool = p; + break; + } + } + + if (pool == null) + return parentPooledObjectProvider?.GetPooledDrawableRepresentation(hitObject); + + return (DrawableHitObject)pool.Get(d => + { + var dho = (DrawableHitObject)d; + + // If this is the first time this DHO is being used (not loaded), then apply the DHO mods. + // This is done before Apply() so that the state is updated once when the hitobject is applied. + if (!dho.IsLoaded) + { + foreach (var m in mods.OfType()) + m.ApplyToDrawableHitObjects(dho.Yield()); + } + + if (!lifetimeEntryMap.TryGetValue(hitObject, out var entry)) + lifetimeEntryMap[hitObject] = entry = CreateLifetimeEntry(hitObject); + + dho.Apply(hitObject, entry); + }); + } + + #endregion + #region Editor logic /// From 5b60f32c7f96bc1b17fff30bf875c7e8f6acaf6c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Sat, 14 Nov 2020 01:03:23 +0900 Subject: [PATCH 14/62] Move implementation into OsuPlayfield --- .../Edit/DrawableOsuEditRuleset.cs | 10 ++--- .../UI/DrawableOsuRuleset.cs | 41 ------------------- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 33 +++++++++++++++ 3 files changed, 38 insertions(+), 46 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs index 05396ebc8b..8af6fd65ce 100644 --- a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs +++ b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs @@ -18,16 +18,16 @@ namespace osu.Game.Rulesets.Osu.Edit { } - protected override DrawablePool CreatePool(int initialSize, int? maximumSize = null) - => new OsuEditDrawablePool(Playfield.CheckHittable, Playfield.OnHitObjectLoaded, initialSize, maximumSize); - - protected override Playfield CreatePlayfield() => new OsuPlayfieldNoCursor(); + protected override Playfield CreatePlayfield() => new OsuEditPlayfield(); public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new OsuPlayfieldAdjustmentContainer { Size = Vector2.One }; - private class OsuPlayfieldNoCursor : OsuPlayfield + private class OsuEditPlayfield : OsuPlayfield { protected override GameplayCursorContainer CreateCursor() => null; + + protected override DrawablePool CreatePool(int initialSize, int? maximumSize = null) + => new OsuEditDrawablePool(CheckHittable, OnHitObjectLoaded, initialSize, maximumSize); } } } diff --git a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs index 1d16c47818..69179137a6 100644 --- a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs @@ -4,18 +4,14 @@ using System; using System.Collections.Generic; using System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Graphics.Pooling; using osu.Framework.Input; using osu.Game.Beatmaps; using osu.Game.Input.Handlers; using osu.Game.Replays; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Configuration; using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Replays; using osu.Game.Rulesets.UI; using osu.Game.Screens.Play; @@ -34,35 +30,8 @@ namespace osu.Game.Rulesets.Osu.UI { } - [BackgroundDependencyLoader] - private void load() - { - registerPool(10, 100); - - registerPool(10, 100); - registerPool(10, 100); - registerPool(10, 100); - registerPool(10, 100); - registerPool(5, 50); - - registerPool(2, 20); - registerPool(10, 100); - registerPool(10, 100); - } - - private void registerPool(int initialSize, int? maximumSize = null) - where TObject : HitObject - where TDrawable : DrawableHitObject, new() - => RegisterPool(CreatePool(initialSize, maximumSize)); - - protected virtual DrawablePool CreatePool(int initialSize, int? maximumSize = null) - where TDrawable : DrawableHitObject, new() - => new OsuDrawablePool(Playfield.CheckHittable, Playfield.OnHitObjectLoaded, initialSize, maximumSize); - public override DrawableHitObject CreateDrawableRepresentation(OsuHitObject h) => null; - protected override HitObjectLifetimeEntry CreateLifetimeEntry(OsuHitObject hitObject) => new OsuHitObjectLifetimeEntry(hitObject); - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; // always show the gameplay cursor protected override Playfield CreatePlayfield() => new OsuPlayfield(); @@ -87,15 +56,5 @@ namespace osu.Game.Rulesets.Osu.UI return 0; } } - - private class OsuHitObjectLifetimeEntry : HitObjectLifetimeEntry - { - public OsuHitObjectLifetimeEntry(HitObject hitObject) - : base(hitObject) - { - } - - protected override double InitialLifetimeOffset => ((OsuHitObject)HitObject).TimePreempt; - } } } diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 5d59a6ff38..e8ff6c410f 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -97,8 +97,31 @@ namespace osu.Game.Rulesets.Osu.UI private void load(OsuRulesetConfigManager config) { config?.BindWith(OsuRulesetSetting.PlayfieldBorderStyle, playfieldBorder.PlayfieldBorderStyle); + + registerPool(10, 100); + + registerPool(10, 100); + registerPool(10, 100); + registerPool(10, 100); + registerPool(10, 100); + registerPool(5, 50); + + registerPool(2, 20); + registerPool(10, 100); + registerPool(10, 100); } + private void registerPool(int initialSize, int? maximumSize = null) + where TObject : HitObject + where TDrawable : DrawableHitObject, new() + => RegisterPool(CreatePool(initialSize, maximumSize)); + + protected virtual DrawablePool CreatePool(int initialSize, int? maximumSize = null) + where TDrawable : DrawableHitObject, new() + => new OsuDrawablePool(CheckHittable, OnHitObjectLoaded, initialSize, maximumSize); + + protected override HitObjectLifetimeEntry CreateLifetimeEntry(HitObject hitObject) => new OsuHitObjectLifetimeEntry(hitObject); + protected override void OnHitObjectAdded(HitObject hitObject) { base.OnHitObjectAdded(hitObject); @@ -172,5 +195,15 @@ namespace osu.Game.Rulesets.Osu.UI return judgement; } } + + private class OsuHitObjectLifetimeEntry : HitObjectLifetimeEntry + { + public OsuHitObjectLifetimeEntry(HitObject hitObject) + : base(hitObject) + { + } + + protected override double InitialLifetimeOffset => ((OsuHitObject)HitObject).TimePreempt; + } } } From 21b015d63afffa9b0acdb0027f37d0b228614698 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Sat, 14 Nov 2020 01:06:38 +0900 Subject: [PATCH 15/62] Remove explicit public --- osu.Game/Rulesets/UI/IPooledHitObjectProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/IPooledHitObjectProvider.cs b/osu.Game/Rulesets/UI/IPooledHitObjectProvider.cs index d8240d892f..315926dfc6 100644 --- a/osu.Game/Rulesets/UI/IPooledHitObjectProvider.cs +++ b/osu.Game/Rulesets/UI/IPooledHitObjectProvider.cs @@ -15,6 +15,6 @@ namespace osu.Game.Rulesets.UI /// The to retrieve the representation of. /// The representing , or null if no poolable representation exists. [CanBeNull] - public DrawableHitObject GetPooledDrawableRepresentation([NotNull] HitObject hitObject); + DrawableHitObject GetPooledDrawableRepresentation([NotNull] HitObject hitObject); } } From 706d7890b4bf93816e8d8b2ebe4d75d15898a7c9 Mon Sep 17 00:00:00 2001 From: Joehu Date: Thu, 5 Nov 2020 06:40:20 -0800 Subject: [PATCH 16/62] Make tournaments strings more consistent --- osu.Game.Tournament/Screens/SetupScreen.cs | 8 ++++---- osu.Game.Tournament/TournamentSceneManager.cs | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tournament/Screens/SetupScreen.cs b/osu.Game.Tournament/Screens/SetupScreen.cs index 98bc292901..e78d3a9e83 100644 --- a/osu.Game.Tournament/Screens/SetupScreen.cs +++ b/osu.Game.Tournament/Screens/SetupScreen.cs @@ -83,8 +83,8 @@ namespace osu.Game.Tournament.Screens }, new ActionableInfo { - Label = "Current User", - ButtonText = "Change Login", + Label = "Current user", + ButtonText = "Change sign-in", Action = () => { api.Logout(); @@ -102,12 +102,12 @@ namespace osu.Game.Tournament.Screens }, Value = api?.LocalUser.Value.Username, Failing = api?.IsLoggedIn != true, - Description = "In order to access the API and display metadata, a login is required." + Description = "In order to access the API and display metadata, signing in is required." }, new LabelledDropdown { Label = "Ruleset", - Description = "Decides what stats are displayed and which ranks are retrieved for players", + Description = "Decides what stats are displayed and which ranks are retrieved for players.", Items = rulesets.AvailableRulesets, Current = LadderInfo.Ruleset, }, diff --git a/osu.Game.Tournament/TournamentSceneManager.cs b/osu.Game.Tournament/TournamentSceneManager.cs index 2c539cdd43..870ea466cc 100644 --- a/osu.Game.Tournament/TournamentSceneManager.cs +++ b/osu.Game.Tournament/TournamentSceneManager.cs @@ -127,10 +127,10 @@ namespace osu.Game.Tournament new ScreenButton(typeof(ScheduleScreen)) { Text = "Schedule", RequestSelection = SetScreen }, new ScreenButton(typeof(LadderScreen)) { Text = "Bracket", RequestSelection = SetScreen }, new Separator(), - new ScreenButton(typeof(TeamIntroScreen)) { Text = "TeamIntro", RequestSelection = SetScreen }, + new ScreenButton(typeof(TeamIntroScreen)) { Text = "Team Intro", RequestSelection = SetScreen }, new ScreenButton(typeof(SeedingScreen)) { Text = "Seeding", RequestSelection = SetScreen }, new Separator(), - new ScreenButton(typeof(MapPoolScreen)) { Text = "MapPool", RequestSelection = SetScreen }, + new ScreenButton(typeof(MapPoolScreen)) { Text = "Map Pool", RequestSelection = SetScreen }, new ScreenButton(typeof(GameplayScreen)) { Text = "Gameplay", RequestSelection = SetScreen }, new Separator(), new ScreenButton(typeof(TeamWinScreen)) { Text = "Win", RequestSelection = SetScreen }, From 90f37ff4ab651281059a5f8bdf2bc41cf6c69a1a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 14 Nov 2020 18:04:59 +0900 Subject: [PATCH 17/62] Rejig namespaces --- osu.Game.Rulesets.Osu/{UI => Edit}/OsuEditDrawablePool.cs | 2 +- .../{UI => Objects/Drawables}/OsuDrawablePool.cs | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) rename osu.Game.Rulesets.Osu/{UI => Edit}/OsuEditDrawablePool.cs (98%) rename osu.Game.Rulesets.Osu/{UI => Objects/Drawables}/OsuDrawablePool.cs (93%) diff --git a/osu.Game.Rulesets.Osu/UI/OsuEditDrawablePool.cs b/osu.Game.Rulesets.Osu/Edit/OsuEditDrawablePool.cs similarity index 98% rename from osu.Game.Rulesets.Osu/UI/OsuEditDrawablePool.cs rename to osu.Game.Rulesets.Osu/Edit/OsuEditDrawablePool.cs index 822ff076f5..946181d8a4 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuEditDrawablePool.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuEditDrawablePool.cs @@ -7,7 +7,7 @@ using osu.Framework.Graphics; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables; -namespace osu.Game.Rulesets.Osu.UI +namespace osu.Game.Rulesets.Osu.Edit { public class OsuEditDrawablePool : OsuDrawablePool where T : DrawableHitObject, new() diff --git a/osu.Game.Rulesets.Osu/UI/OsuDrawablePool.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/OsuDrawablePool.cs similarity index 93% rename from osu.Game.Rulesets.Osu/UI/OsuDrawablePool.cs rename to osu.Game.Rulesets.Osu/Objects/Drawables/OsuDrawablePool.cs index 148146f25a..1d9330b962 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuDrawablePool.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/OsuDrawablePool.cs @@ -5,9 +5,8 @@ using System; using osu.Framework.Graphics; using osu.Framework.Graphics.Pooling; using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Osu.Objects.Drawables; -namespace osu.Game.Rulesets.Osu.UI +namespace osu.Game.Rulesets.Osu.Objects.Drawables { public class OsuDrawablePool : DrawablePool where T : DrawableHitObject, new() From 7ac4d2c4be80ffe933bd76ff6c1333e6b3d498fc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 14 Nov 2020 18:05:30 +0900 Subject: [PATCH 18/62] Move "drawable" to first prefix --- .../Edit/{OsuEditDrawablePool.cs => DrawableOsuEditPool.cs} | 4 ++-- osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs | 2 +- .../Drawables/{OsuDrawablePool.cs => DrawableOsuPool.cs} | 4 ++-- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) rename osu.Game.Rulesets.Osu/Edit/{OsuEditDrawablePool.cs => DrawableOsuEditPool.cs} (95%) rename osu.Game.Rulesets.Osu/Objects/Drawables/{OsuDrawablePool.cs => DrawableOsuPool.cs} (89%) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuEditDrawablePool.cs b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditPool.cs similarity index 95% rename from osu.Game.Rulesets.Osu/Edit/OsuEditDrawablePool.cs rename to osu.Game.Rulesets.Osu/Edit/DrawableOsuEditPool.cs index 946181d8a4..569031752e 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuEditDrawablePool.cs +++ b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditPool.cs @@ -9,7 +9,7 @@ using osu.Game.Rulesets.Osu.Objects.Drawables; namespace osu.Game.Rulesets.Osu.Edit { - public class OsuEditDrawablePool : OsuDrawablePool + public class DrawableOsuEditPool : DrawableOsuPool where T : DrawableHitObject, new() { /// @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Osu.Edit /// private const double editor_hit_object_fade_out_extension = 700; - public OsuEditDrawablePool(Func checkHittable, Action onLoaded, int initialSize, int? maximumSize = null) + public DrawableOsuEditPool(Func checkHittable, Action onLoaded, int initialSize, int? maximumSize = null) : base(checkHittable, onLoaded, initialSize, maximumSize) { } diff --git a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs index 8af6fd65ce..547dff88b5 100644 --- a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs +++ b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Edit protected override GameplayCursorContainer CreateCursor() => null; protected override DrawablePool CreatePool(int initialSize, int? maximumSize = null) - => new OsuEditDrawablePool(CheckHittable, OnHitObjectLoaded, initialSize, maximumSize); + => new DrawableOsuEditPool(CheckHittable, OnHitObjectLoaded, initialSize, maximumSize); } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/OsuDrawablePool.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuPool.cs similarity index 89% rename from osu.Game.Rulesets.Osu/Objects/Drawables/OsuDrawablePool.cs rename to osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuPool.cs index 1d9330b962..1b5fd50022 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/OsuDrawablePool.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuPool.cs @@ -8,13 +8,13 @@ using osu.Game.Rulesets.Objects.Drawables; namespace osu.Game.Rulesets.Osu.Objects.Drawables { - public class OsuDrawablePool : DrawablePool + public class DrawableOsuPool : DrawablePool where T : DrawableHitObject, new() { private readonly Func checkHittable; private readonly Action onLoaded; - public OsuDrawablePool(Func checkHittable, Action onLoaded, int initialSize, int? maximumSize = null) + public DrawableOsuPool(Func checkHittable, Action onLoaded, int initialSize, int? maximumSize = null) : base(initialSize, maximumSize) { this.checkHittable = checkHittable; diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index e8ff6c410f..c816502d61 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -118,7 +118,7 @@ namespace osu.Game.Rulesets.Osu.UI protected virtual DrawablePool CreatePool(int initialSize, int? maximumSize = null) where TDrawable : DrawableHitObject, new() - => new OsuDrawablePool(CheckHittable, OnHitObjectLoaded, initialSize, maximumSize); + => new DrawableOsuPool(CheckHittable, OnHitObjectLoaded, initialSize, maximumSize); protected override HitObjectLifetimeEntry CreateLifetimeEntry(HitObject hitObject) => new OsuHitObjectLifetimeEntry(hitObject); From deea75b2e9f53f92eff40109adef3e162d76b68c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 14 Nov 2020 18:05:51 +0900 Subject: [PATCH 19/62] Fix typo in comment --- osu.Game.Rulesets.Osu/Edit/DrawableOsuEditPool.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditPool.cs b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditPool.cs index 569031752e..776aacd143 100644 --- a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditPool.cs +++ b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditPool.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Edit switch (hitObject) { default: - // there are quite a few drawable hit types we don't want to extent (spinners, ticks etc.) + // there are quite a few drawable hit types we don't want to extend (spinners, ticks etc.) return; case DrawableSlider _: From 4d2bc790fd96b68234b39e623d6147e09a444ffb Mon Sep 17 00:00:00 2001 From: kamp Date: Sat, 14 Nov 2020 13:20:16 +0100 Subject: [PATCH 20/62] Fix crash on shift+right-click deleting objects --- osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 3229719d5a..c9043ccef3 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -338,7 +338,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// Whether a selection was performed. private bool beginClickSelection(MouseButtonEvent e) { - foreach (SelectionBlueprint blueprint in SelectionBlueprints.AliveChildren) + foreach (SelectionBlueprint blueprint in SelectionBlueprints.AliveChildren.ToList()) { if (!blueprint.IsHovered) continue; From 610ed99ae3eba0f9f28cc7ca13b9cd56d94f2cf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 14 Nov 2020 14:48:48 +0100 Subject: [PATCH 21/62] Add null checks to unguarded resolved-as-null fields --- .../Screens/Ladder/Components/DrawableTournamentMatch.cs | 4 ++-- osu.Game/Online/DownloadTrackingComposite.cs | 7 +++++-- osu.Game/Online/Leaderboards/Leaderboard.cs | 6 ++++-- osu.Game/Overlays/AccountCreation/ScreenWarning.cs | 4 ++-- .../Overlays/Settings/Sections/General/LoginSettings.cs | 2 +- osu.Game/PerformFromMenuRunner.cs | 2 +- osu.Game/Rulesets/UI/HitObjectContainer.cs | 2 +- .../Screens/Edit/Compose/Components/BlueprintContainer.cs | 3 +++ .../Components/Timeline/TimelineBlueprintContainer.cs | 2 +- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 2 +- osu.Game/Screens/Multi/Lounge/Components/FilterControl.cs | 3 +++ 11 files changed, 24 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentMatch.cs b/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentMatch.cs index 655beb4bdd..f2065e7e88 100644 --- a/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentMatch.cs +++ b/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentMatch.cs @@ -144,9 +144,9 @@ namespace osu.Game.Tournament.Screens.Ladder.Components if (selected) { selectionBox.Show(); - if (editor) + if (editor && editorInfo != null) editorInfo.Selected.Value = Match; - else + else if (ladderInfo != null) ladderInfo.CurrentMatch.Value = Match; } else diff --git a/osu.Game/Online/DownloadTrackingComposite.cs b/osu.Game/Online/DownloadTrackingComposite.cs index 5d9cf612bb..bed95344c6 100644 --- a/osu.Game/Online/DownloadTrackingComposite.cs +++ b/osu.Game/Online/DownloadTrackingComposite.cs @@ -46,12 +46,15 @@ namespace osu.Game.Online { if (modelInfo.NewValue == null) attachDownload(null); - else if (manager.IsAvailableLocally(modelInfo.NewValue)) + else if (manager?.IsAvailableLocally(modelInfo.NewValue) == true) State.Value = DownloadState.LocallyAvailable; else - attachDownload(manager.GetExistingDownload(modelInfo.NewValue)); + attachDownload(manager?.GetExistingDownload(modelInfo.NewValue)); }, true); + if (manager == null) + return; + managerDownloadBegan = manager.DownloadBegan.GetBoundCopy(); managerDownloadBegan.BindValueChanged(downloadBegan); managerDownloadFailed = manager.DownloadFailed.GetBoundCopy(); diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 3a5c2e181f..d18f189a70 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -248,7 +248,9 @@ namespace osu.Game.Online.Leaderboards [BackgroundDependencyLoader] private void load() { - apiState.BindTo(api.State); + if (api != null) + apiState.BindTo(api.State); + apiState.BindValueChanged(onlineStateChanged, true); } @@ -303,7 +305,7 @@ namespace osu.Game.Online.Leaderboards PlaceholderState = PlaceholderState.NetworkFailure; }); - api.Queue(getScoresRequest); + api?.Queue(getScoresRequest); }); } diff --git a/osu.Game/Overlays/AccountCreation/ScreenWarning.cs b/osu.Game/Overlays/AccountCreation/ScreenWarning.cs index 5375476c9e..b2096968fe 100644 --- a/osu.Game/Overlays/AccountCreation/ScreenWarning.cs +++ b/osu.Game/Overlays/AccountCreation/ScreenWarning.cs @@ -30,7 +30,7 @@ namespace osu.Game.Overlays.AccountCreation public override void OnEntering(IScreen last) { - if (string.IsNullOrEmpty(api.ProvidedUsername)) + if (string.IsNullOrEmpty(api?.ProvidedUsername)) { this.FadeOut(); this.Push(new ScreenEntry()); @@ -43,7 +43,7 @@ namespace osu.Game.Overlays.AccountCreation [BackgroundDependencyLoader(true)] private void load(OsuColour colours, OsuGame game, TextureStore textures) { - if (string.IsNullOrEmpty(api.ProvidedUsername)) + if (string.IsNullOrEmpty(api?.ProvidedUsername)) return; InternalChildren = new Drawable[] diff --git a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs index 873272bf12..8f757f7a36 100644 --- a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs @@ -217,7 +217,7 @@ namespace osu.Game.Overlays.Settings.Sections.General private void performLogin() { if (!string.IsNullOrEmpty(username.Text) && !string.IsNullOrEmpty(password.Text)) - api.Login(username.Text, password.Text); + api?.Login(username.Text, password.Text); else shakeSignIn.Shake(); } diff --git a/osu.Game/PerformFromMenuRunner.cs b/osu.Game/PerformFromMenuRunner.cs index 5898c116dd..e2d4fc6051 100644 --- a/osu.Game/PerformFromMenuRunner.cs +++ b/osu.Game/PerformFromMenuRunner.cs @@ -76,7 +76,7 @@ namespace osu.Game // a dialog may be blocking the execution for now. if (checkForDialog(current)) return; - game.CloseAllOverlays(false); + game?.CloseAllOverlays(false); // we may already be at the target screen type. if (validScreens.Contains(getCurrentScreen().GetType()) && !beatmap.Disabled) diff --git a/osu.Game/Rulesets/UI/HitObjectContainer.cs b/osu.Game/Rulesets/UI/HitObjectContainer.cs index 1dc029506f..5fbda305c8 100644 --- a/osu.Game/Rulesets/UI/HitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/HitObjectContainer.cs @@ -105,7 +105,7 @@ namespace osu.Game.Rulesets.UI { Debug.Assert(!drawableMap.ContainsKey(entry)); - var drawable = pooledObjectProvider.GetPooledDrawableRepresentation(entry.HitObject); + var drawable = pooledObjectProvider?.GetPooledDrawableRepresentation(entry.HitObject); if (drawable == null) throw new InvalidOperationException($"A drawable representation could not be retrieved for hitobject type: {entry.HitObject.GetType().ReadableName()}."); diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 3229719d5a..7fa7ec9fa5 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -457,6 +457,9 @@ namespace osu.Game.Screens.Edit.Compose.Components if (movementBlueprint == null) return false; + if (snapProvider == null) + return true; + Debug.Assert(movementBlueprintOriginalPosition != null); HitObject draggedObject = movementBlueprint.HitObject; diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index 0271b2def9..eef02e61a6 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -96,7 +96,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (lastDragEvent != null) OnDrag(lastDragEvent); - if (Composer != null) + if (Composer != null && timeline != null) { Composer.Playfield.PastLifetimeExtension = timeline.VisibleRange / 2; Composer.Playfield.FutureLifetimeExtension = timeline.VisibleRange / 2; diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index 09d861522a..eab909b798 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -128,7 +128,7 @@ namespace osu.Game.Screens.Edit.Timing controlPointGroups.BindCollectionChanged((sender, args) => { table.ControlGroups = controlPointGroups; - changeHandler.SaveState(); + changeHandler?.SaveState(); }, true); } diff --git a/osu.Game/Screens/Multi/Lounge/Components/FilterControl.cs b/osu.Game/Screens/Multi/Lounge/Components/FilterControl.cs index be1083ce8d..3fc1359006 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/FilterControl.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/FilterControl.cs @@ -59,6 +59,9 @@ namespace osu.Game.Screens.Multi.Lounge.Components { scheduledFilterUpdate?.Cancel(); + if (filter == null) + return; + filter.Value = new FilterCriteria { SearchString = Search.Current.Value ?? string.Empty, From 8a78d408db6ed6d4a218615459c5a66b4802d6dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 14 Nov 2020 15:52:12 +0100 Subject: [PATCH 22/62] Add more missed null checks --- osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 21810379cc..adf22a3370 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -110,7 +110,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// protected virtual void OnOperationBegan() { - ChangeHandler.BeginChange(); + ChangeHandler?.BeginChange(); } /// @@ -118,7 +118,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// protected virtual void OnOperationEnded() { - ChangeHandler.EndChange(); + ChangeHandler?.EndChange(); } #region User Input Handling From b344a1373472c3eb1b21b4b58f9a017b33b78f05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 14 Nov 2020 17:08:27 +0100 Subject: [PATCH 23/62] Add support for previewing tracks on spectator screen --- osu.Game/Screens/Play/Spectator.cs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/Spectator.cs b/osu.Game/Screens/Play/Spectator.cs index 6f51771c12..71ce157296 100644 --- a/osu.Game/Screens/Play/Spectator.cs +++ b/osu.Game/Screens/Play/Spectator.cs @@ -13,6 +13,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Screens; +using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics; @@ -35,7 +36,8 @@ using osuTK; namespace osu.Game.Screens.Play { - public class Spectator : OsuScreen + [Cached(typeof(IPreviewTrackOwner))] + public class Spectator : OsuScreen, IPreviewTrackOwner { private readonly User targetUser; @@ -62,6 +64,9 @@ namespace osu.Game.Screens.Play [Resolved] private RulesetStore rulesets { get; set; } + [Resolved] + private PreviewTrackManager previewTrackManager { get; set; } + private Score score; private readonly object scoreLock = new object(); @@ -275,6 +280,7 @@ namespace osu.Game.Screens.Play { watchButton.Enabled.Value = false; beatmapPanelContainer.Clear(); + previewTrackManager.StopAnyPlaying(this); } private void attemptStart() @@ -326,7 +332,6 @@ namespace osu.Game.Screens.Play { if (state?.BeatmapID == null) { - beatmapPanelContainer.Clear(); onlineBeatmap = null; return; } @@ -359,6 +364,12 @@ namespace osu.Game.Screens.Play beatmaps.Download(onlineBeatmap); } + public override bool OnExiting(IScreen next) + { + previewTrackManager.StopAnyPlaying(this); + return base.OnExiting(next); + } + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); From 5ae3d6cc74f13a85be1117f717ce9eff4836438a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 14 Nov 2020 20:12:46 +0100 Subject: [PATCH 24/62] Add failing asserts --- .../TestSceneSpinnerApplication.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerApplication.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerApplication.cs index 0558dad30d..05fc352a0b 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerApplication.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerApplication.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Osu.Tests public class TestSceneSpinnerApplication : OsuTestScene { [Test] - public void TestApplyNewCircle() + public void TestApplyNewSpinner() { DrawableSpinner dho = null; @@ -23,18 +23,23 @@ namespace osu.Game.Rulesets.Osu.Tests { Position = new Vector2(256, 192), IndexInCurrentCombo = 0, - Duration = 0, + Duration = 500, })) { Clock = new FramedClock(new StopwatchClock()) }); + AddStep("rotate some", () => dho.RotationTracker.AddRotation(180)); + AddAssert("rotation is set", () => dho.RotationTracker.RateAdjustedRotation == 180); + AddStep("apply new spinner", () => dho.Apply(prepareObject(new Spinner { Position = new Vector2(256, 192), ComboIndex = 1, Duration = 1000, }), null)); + + AddAssert("rotation is reset", () => dho.RotationTracker.RateAdjustedRotation == 0); } private Spinner prepareObject(Spinner circle) From f8cabbdd21bc64dc3484ec664c8ce43fb8861f62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 14 Nov 2020 21:09:22 +0100 Subject: [PATCH 25/62] Clear result when freeing pooled hitobject --- 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 b400c532c5..9735426ea1 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -285,6 +285,7 @@ namespace osu.Game.Rulesets.Objects.Drawables OnFree(HitObject); HitObject = null; + Result = null; lifetimeEntry = null; hasHitObjectApplied = false; From af392e39953ac297b2b56287204958636e91f496 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 14 Nov 2020 21:10:12 +0100 Subject: [PATCH 26/62] Move rate adjusted spinner rotation into judgement result --- .../TestSceneSpinnerApplication.cs | 4 +- .../TestSceneSpinnerRotation.cs | 12 ++--- .../Judgements/OsuSpinnerJudgementResult.cs | 46 +++++++++++++++++++ .../Objects/Drawables/DrawableSpinner.cs | 12 +++-- .../Drawables/Pieces/DefaultSpinnerDisc.cs | 2 +- .../Pieces/SpinnerRotationTracker.cs | 26 +---------- 6 files changed, 65 insertions(+), 37 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Judgements/OsuSpinnerJudgementResult.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerApplication.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerApplication.cs index 05fc352a0b..d7fbc7ac48 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerApplication.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerApplication.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.Tests }); AddStep("rotate some", () => dho.RotationTracker.AddRotation(180)); - AddAssert("rotation is set", () => dho.RotationTracker.RateAdjustedRotation == 180); + AddAssert("rotation is set", () => dho.Result.RateAdjustedRotation == 180); AddStep("apply new spinner", () => dho.Apply(prepareObject(new Spinner { @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Osu.Tests Duration = 1000, }), null)); - AddAssert("rotation is reset", () => dho.RotationTracker.RateAdjustedRotation == 0); + AddAssert("rotation is reset", () => dho.Result.RateAdjustedRotation == 0); } private Spinner prepareObject(Spinner circle) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs index 53bf1ea566..ac8d5c81bc 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs @@ -62,11 +62,11 @@ namespace osu.Game.Rulesets.Osu.Tests trackerRotationTolerance = Math.Abs(drawableSpinner.RotationTracker.Rotation * 0.1f); }); AddAssert("is disc rotation not almost 0", () => !Precision.AlmostEquals(drawableSpinner.RotationTracker.Rotation, 0, 100)); - AddAssert("is disc rotation absolute not almost 0", () => !Precision.AlmostEquals(drawableSpinner.RotationTracker.RateAdjustedRotation, 0, 100)); + AddAssert("is disc rotation absolute not almost 0", () => !Precision.AlmostEquals(drawableSpinner.Result.RateAdjustedRotation, 0, 100)); addSeekStep(0); AddAssert("is disc rotation almost 0", () => Precision.AlmostEquals(drawableSpinner.RotationTracker.Rotation, 0, trackerRotationTolerance)); - AddAssert("is disc rotation absolute almost 0", () => Precision.AlmostEquals(drawableSpinner.RotationTracker.RateAdjustedRotation, 0, 100)); + AddAssert("is disc rotation absolute almost 0", () => Precision.AlmostEquals(drawableSpinner.Result.RateAdjustedRotation, 0, 100)); } [Test] @@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Osu.Tests finalSpinnerSymbolRotation = spinnerSymbol.Rotation; spinnerSymbolRotationTolerance = Math.Abs(finalSpinnerSymbolRotation * 0.05f); }); - AddStep("retrieve cumulative disc rotation", () => finalCumulativeTrackerRotation = drawableSpinner.RotationTracker.RateAdjustedRotation); + AddStep("retrieve cumulative disc rotation", () => finalCumulativeTrackerRotation = drawableSpinner.Result.RateAdjustedRotation); addSeekStep(2500); AddAssert("disc rotation rewound", @@ -99,7 +99,7 @@ namespace osu.Game.Rulesets.Osu.Tests () => Precision.AlmostEquals(spinnerSymbol.Rotation, finalSpinnerSymbolRotation / 2, spinnerSymbolRotationTolerance)); AddAssert("is cumulative rotation rewound", // cumulative rotation is not damped, so we're treating it as the "ground truth" and allowing a comparatively smaller margin of error. - () => Precision.AlmostEquals(drawableSpinner.RotationTracker.RateAdjustedRotation, finalCumulativeTrackerRotation / 2, 100)); + () => Precision.AlmostEquals(drawableSpinner.Result.RateAdjustedRotation, finalCumulativeTrackerRotation / 2, 100)); addSeekStep(5000); AddAssert("is disc rotation almost same", @@ -107,7 +107,7 @@ namespace osu.Game.Rulesets.Osu.Tests AddAssert("is symbol rotation almost same", () => Precision.AlmostEquals(spinnerSymbol.Rotation, finalSpinnerSymbolRotation, spinnerSymbolRotationTolerance)); AddAssert("is cumulative rotation almost same", - () => Precision.AlmostEquals(drawableSpinner.RotationTracker.RateAdjustedRotation, finalCumulativeTrackerRotation, 100)); + () => Precision.AlmostEquals(drawableSpinner.Result.RateAdjustedRotation, finalCumulativeTrackerRotation, 100)); } [Test] @@ -145,7 +145,7 @@ namespace osu.Game.Rulesets.Osu.Tests { // multipled by 2 to nullify the score multiplier. (autoplay mod selected) var totalScore = ((ScoreExposedPlayer)Player).ScoreProcessor.TotalScore.Value * 2; - return totalScore == (int)(drawableSpinner.RotationTracker.RateAdjustedRotation / 360) * new SpinnerTick().CreateJudgement().MaxNumericResult; + return totalScore == (int)(drawableSpinner.Result.RateAdjustedRotation / 360) * new SpinnerTick().CreateJudgement().MaxNumericResult; }); addSeekStep(0); diff --git a/osu.Game.Rulesets.Osu/Judgements/OsuSpinnerJudgementResult.cs b/osu.Game.Rulesets.Osu/Judgements/OsuSpinnerJudgementResult.cs new file mode 100644 index 0000000000..5ffe4cd004 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Judgements/OsuSpinnerJudgementResult.cs @@ -0,0 +1,46 @@ +// 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.Objects; +using osu.Game.Rulesets.Osu.Objects; + +namespace osu.Game.Rulesets.Osu.Judgements +{ + public class OsuSpinnerJudgementResult : OsuJudgementResult + { + /// + /// The . + /// + public Spinner Spinner => (Spinner)HitObject; + + /// + /// The total rotation performed on the spinner disc, disregarding the spin direction, + /// adjusted for the track's playback rate. + /// + /// + /// + /// This value is always non-negative and is monotonically increasing with time + /// (i.e. will only increase if time is passing forward, but can decrease during rewind). + /// + /// + /// The rotation from each frame is multiplied by the clock's current playback rate. + /// The reason this is done is to ensure that spinners give the same score and require the same number of spins + /// regardless of whether speed-modifying mods are applied. + /// + /// + /// + /// Assuming no speed-modifying mods are active, + /// if the spinner is spun 360 degrees clockwise and then 360 degrees counter-clockwise, + /// this property will return the value of 720 (as opposed to 0). + /// If Double Time is active instead (with a speed multiplier of 1.5x), + /// in the same scenario the property will return 720 * 1.5 = 1080. + /// + public float RateAdjustedRotation; + + public OsuSpinnerJudgementResult(HitObject hitObject, Judgement judgement) + : base(hitObject, judgement) + { + } + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 824b8806e5..4d11adad09 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -10,8 +10,10 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; +using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; using osu.Game.Rulesets.Osu.Skinning; using osu.Game.Rulesets.Scoring; @@ -24,6 +26,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { public new Spinner HitObject => (Spinner)base.HitObject; + public new OsuSpinnerJudgementResult Result => (OsuSpinnerJudgementResult)base.Result; + public SpinnerRotationTracker RotationTracker { get; private set; } public SpinnerSpmCounter SpmCounter { get; private set; } @@ -197,10 +201,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables // these become implicitly hit. return 1; - return Math.Clamp(RotationTracker.RateAdjustedRotation / 360 / HitObject.SpinsRequired, 0, 1); + return Math.Clamp(Result.RateAdjustedRotation / 360 / HitObject.SpinsRequired, 0, 1); } } + protected override JudgementResult CreateResult(Judgement judgement) => new OsuSpinnerJudgementResult(HitObject, judgement); + protected override void CheckForResult(bool userTriggered, double timeOffset) { if (Time.Current < HitObject.StartTime) return; @@ -244,7 +250,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (!SpmCounter.IsPresent && RotationTracker.Tracking) SpmCounter.FadeIn(HitObject.TimeFadeIn); - SpmCounter.SetRotation(RotationTracker.RateAdjustedRotation); + SpmCounter.SetRotation(Result.RateAdjustedRotation); updateBonusScore(); } @@ -256,7 +262,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (ticks.Count == 0) return; - int spins = (int)(RotationTracker.RateAdjustedRotation / 360); + int spins = (int)(Result.RateAdjustedRotation / 360); if (spins < wholeSpins) { diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs index 731852c221..376aa68020 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs @@ -194,7 +194,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { get { - int rotations = (int)(drawableSpinner.RotationTracker.RateAdjustedRotation / 360); + int rotations = (int)(drawableSpinner.Result.RateAdjustedRotation / 360); if (wholeRotationCount == rotations) return false; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerRotationTracker.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerRotationTracker.cs index 910899c307..0716218420 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerRotationTracker.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerRotationTracker.cs @@ -32,30 +32,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces public readonly BindableBool Complete = new BindableBool(); - /// - /// The total rotation performed on the spinner disc, disregarding the spin direction, - /// adjusted for the track's playback rate. - /// - /// - /// - /// This value is always non-negative and is monotonically increasing with time - /// (i.e. will only increase if time is passing forward, but can decrease during rewind). - /// - /// - /// The rotation from each frame is multiplied by the clock's current playback rate. - /// The reason this is done is to ensure that spinners give the same score and require the same number of spins - /// regardless of whether speed-modifying mods are applied. - /// - /// - /// - /// Assuming no speed-modifying mods are active, - /// if the spinner is spun 360 degrees clockwise and then 360 degrees counter-clockwise, - /// this property will return the value of 720 (as opposed to 0 for ). - /// If Double Time is active instead (with a speed multiplier of 1.5x), - /// in the same scenario the property will return 720 * 1.5 = 1080. - /// - public float RateAdjustedRotation { get; private set; } - /// /// Whether the spinning is spinning at a reasonable speed to be considered visually spinning. /// @@ -131,7 +107,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces currentRotation += angle; // rate has to be applied each frame, because it's not guaranteed to be constant throughout playback // (see: ModTimeRamp) - RateAdjustedRotation += (float)(Math.Abs(angle) * (gameplayClock?.TrueGameplayRate ?? Clock.Rate)); + drawableSpinner.Result.RateAdjustedRotation += (float)(Math.Abs(angle) * (gameplayClock?.TrueGameplayRate ?? Clock.Rate)); } } } From 727a886fb310547ff918e69831121c55754b67f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 14 Nov 2020 22:42:19 +0100 Subject: [PATCH 27/62] Move spinner completion info into judgement --- .../Judgements/OsuSpinnerJudgementResult.cs | 6 ++++++ .../Objects/Drawables/DrawableSpinner.cs | 3 ++- .../Objects/Drawables/Pieces/DefaultSpinnerDisc.cs | 8 ++++++-- .../Objects/Drawables/Pieces/SpinnerRotationTracker.cs | 2 -- osu.Game.Rulesets.Osu/Skinning/LegacySpinner.cs | 7 ++++++- 5 files changed, 20 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Judgements/OsuSpinnerJudgementResult.cs b/osu.Game.Rulesets.Osu/Judgements/OsuSpinnerJudgementResult.cs index 5ffe4cd004..e58aacd86e 100644 --- a/osu.Game.Rulesets.Osu/Judgements/OsuSpinnerJudgementResult.cs +++ b/osu.Game.Rulesets.Osu/Judgements/OsuSpinnerJudgementResult.cs @@ -38,6 +38,12 @@ namespace osu.Game.Rulesets.Osu.Judgements /// public float RateAdjustedRotation; + /// + /// Time instant at which the spinner has been completed (the user has executed all required spins). + /// Will be null if all required spins haven't been completed. + /// + public double? TimeCompleted; + public OsuSpinnerJudgementResult(HitObject hitObject, Judgement judgement) : base(hitObject, judgement) { diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 4d11adad09..87c7146a64 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -211,7 +211,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { if (Time.Current < HitObject.StartTime) return; - RotationTracker.Complete.Value = Progress >= 1; + if (Progress >= 1) + Result.TimeCompleted ??= Time.Current; if (userTriggered || Time.Current < HitObject.EndTime) return; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs index 376aa68020..14ce3b014d 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs @@ -3,6 +3,7 @@ using System; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -28,6 +29,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces private SpinnerTicks ticks; private int wholeRotationCount; + private readonly BindableBool complete = new BindableBool(); private SpinnerFill fill; private Container mainContainer; @@ -89,7 +91,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { base.LoadComplete(); - drawableSpinner.RotationTracker.Complete.BindValueChanged(complete => updateComplete(complete.NewValue, 200)); + complete.BindValueChanged(complete => updateComplete(complete.NewValue, 200)); drawableSpinner.ApplyCustomUpdateState += updateStateTransforms; updateStateTransforms(drawableSpinner, drawableSpinner.State.Value); @@ -99,7 +101,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { base.Update(); - if (drawableSpinner.RotationTracker.Complete.Value) + complete.Value = Time.Current >= drawableSpinner.Result.TimeCompleted; + + if (complete.Value) { if (checkNewRotationCount) { diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerRotationTracker.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerRotationTracker.cs index 0716218420..31ab76cc0a 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerRotationTracker.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerRotationTracker.cs @@ -30,8 +30,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces public bool Tracking { get; set; } - public readonly BindableBool Complete = new BindableBool(); - /// /// Whether the spinning is spinning at a reasonable speed to be considered visually spinning. /// diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacySpinner.cs b/osu.Game.Rulesets.Osu/Skinning/LegacySpinner.cs index eb9fa85fde..5aa136cf7e 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacySpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacySpinner.cs @@ -60,7 +60,6 @@ namespace osu.Game.Rulesets.Osu.Skinning { base.LoadComplete(); - completed.BindTo(DrawableSpinner.RotationTracker.Complete); completed.BindValueChanged(onCompletedChanged, true); DrawableSpinner.ApplyCustomUpdateState += UpdateStateTransforms; @@ -93,6 +92,12 @@ namespace osu.Game.Rulesets.Osu.Skinning } } + protected override void Update() + { + base.Update(); + completed.Value = Time.Current >= DrawableSpinner.Result.TimeCompleted; + } + protected virtual void UpdateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state) { switch (drawableHitObject) From 532680bb367b1a0cb8ea06e04e6ee945151ccc6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 14 Nov 2020 23:23:11 +0100 Subject: [PATCH 28/62] Manually reset rest of rotation tracker state on object application --- .../Pieces/SpinnerRotationTracker.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerRotationTracker.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerRotationTracker.cs index 31ab76cc0a..f82003edb8 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerRotationTracker.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerRotationTracker.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; using osu.Framework.Utils; +using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Screens.Play; using osuTK; @@ -22,6 +23,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces public SpinnerRotationTracker(DrawableSpinner drawableSpinner) { this.drawableSpinner = drawableSpinner; + drawableSpinner.HitObjectApplied += resetState; RelativeSizeAxes = Axes.Both; } @@ -107,5 +109,22 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces // (see: ModTimeRamp) drawableSpinner.Result.RateAdjustedRotation += (float)(Math.Abs(angle) * (gameplayClock?.TrueGameplayRate ?? Clock.Rate)); } + + private void resetState(DrawableHitObject obj) + { + Tracking = false; + IsSpinning.Value = false; + mousePosition = default; + lastAngle = currentRotation = Rotation = 0; + rotationTransferred = false; + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (drawableSpinner != null) + drawableSpinner.HitObjectApplied -= resetState; + } } } From 199043f67778ed873903ef2e51aa22b05b7129b9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 15 Nov 2020 13:21:09 +0900 Subject: [PATCH 29/62] Allow preview tracks to exist without an owner (without hard crashing) --- osu.Game/Audio/PreviewTrackManager.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/osu.Game/Audio/PreviewTrackManager.cs b/osu.Game/Audio/PreviewTrackManager.cs index 862be41c1a..8d02af6574 100644 --- a/osu.Game/Audio/PreviewTrackManager.cs +++ b/osu.Game/Audio/PreviewTrackManager.cs @@ -11,6 +11,7 @@ using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.IO.Stores; +using osu.Framework.Logging; using osu.Game.Beatmaps; namespace osu.Game.Audio @@ -76,7 +77,7 @@ namespace osu.Game.Audio /// The which may be the owner of the . public void StopAnyPlaying(IPreviewTrackOwner source) { - if (CurrentTrack == null || CurrentTrack.Owner != source) + if (CurrentTrack == null || (CurrentTrack.Owner != null && CurrentTrack.Owner != source)) return; CurrentTrack.Stop(); @@ -86,11 +87,12 @@ namespace osu.Game.Audio /// /// Creates the . /// - protected virtual TrackManagerPreviewTrack CreatePreviewTrack(BeatmapSetInfo beatmapSetInfo, ITrackStore trackStore) => new TrackManagerPreviewTrack(beatmapSetInfo, trackStore); + protected virtual TrackManagerPreviewTrack CreatePreviewTrack(BeatmapSetInfo beatmapSetInfo, ITrackStore trackStore) => + new TrackManagerPreviewTrack(beatmapSetInfo, trackStore); public class TrackManagerPreviewTrack : PreviewTrack { - [Resolved] + [Resolved(canBeNull: true)] public IPreviewTrackOwner Owner { get; private set; } private readonly BeatmapSetInfo beatmapSetInfo; @@ -102,6 +104,12 @@ namespace osu.Game.Audio this.trackManager = trackManager; } + protected override void LoadComplete() + { + base.LoadComplete(); + Logger.Log($"A {nameof(PreviewTrack)} was created without a containing {nameof(IPreviewTrackOwner)}. An owner should be added for correct behaviour."); + } + protected override Track GetTrack() => trackManager.Get($"https://b.ppy.sh/preview/{beatmapSetInfo?.OnlineBeatmapSetID}.mp3"); } From 9899687bfe27d75a584a582b3a918e2624e7024a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 15 Nov 2020 14:17:41 +0100 Subject: [PATCH 30/62] Rename existing method to allow for new one --- osu.Game.Rulesets.Catch.Tests/CatchLegacyModConversionTest.cs | 2 +- osu.Game.Rulesets.Mania.Tests/ManiaLegacyModConversionTest.cs | 2 +- osu.Game.Rulesets.Osu.Tests/OsuLegacyModConversionTest.cs | 2 +- osu.Game.Rulesets.Taiko.Tests/TaikoLegacyModConversionTest.cs | 2 +- osu.Game/Tests/Beatmaps/LegacyModConversionTest.cs | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/CatchLegacyModConversionTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchLegacyModConversionTest.cs index 04e6dea376..4102dff37c 100644 --- a/osu.Game.Rulesets.Catch.Tests/CatchLegacyModConversionTest.cs +++ b/osu.Game.Rulesets.Catch.Tests/CatchLegacyModConversionTest.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Catch.Tests [TestCase(LegacyMods.SuddenDeath, new[] { typeof(CatchModSuddenDeath) })] [TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath, new[] { typeof(CatchModPerfect) })] [TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath | LegacyMods.DoubleTime, new[] { typeof(CatchModDoubleTime), typeof(CatchModPerfect) })] - public new void Test(LegacyMods legacyMods, Type[] expectedMods) => base.Test(legacyMods, expectedMods); + public new void TestFromLegacy(LegacyMods legacyMods, Type[] expectedMods) => base.TestFromLegacy(legacyMods, expectedMods); protected override Ruleset CreateRuleset() => new CatchRuleset(); } diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaLegacyModConversionTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaLegacyModConversionTest.cs index b22687a0a7..221eed3576 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaLegacyModConversionTest.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaLegacyModConversionTest.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Mania.Tests [TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath | LegacyMods.DoubleTime, new[] { typeof(ManiaModDoubleTime), typeof(ManiaModPerfect) })] [TestCase(LegacyMods.Random | LegacyMods.SuddenDeath, new[] { typeof(ManiaModRandom), typeof(ManiaModSuddenDeath) })] [TestCase(LegacyMods.Flashlight | LegacyMods.Mirror, new[] { typeof(ManiaModFlashlight), typeof(ManiaModMirror) })] - public new void Test(LegacyMods legacyMods, Type[] expectedMods) => base.Test(legacyMods, expectedMods); + public new void TestFromLegacy(LegacyMods legacyMods, Type[] expectedMods) => base.TestFromLegacy(legacyMods, expectedMods); protected override Ruleset CreateRuleset() => new ManiaRuleset(); } diff --git a/osu.Game.Rulesets.Osu.Tests/OsuLegacyModConversionTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuLegacyModConversionTest.cs index 495f2738b5..731150a584 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuLegacyModConversionTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuLegacyModConversionTest.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Tests [TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath, new[] { typeof(OsuModPerfect) })] [TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath | LegacyMods.DoubleTime, new[] { typeof(OsuModDoubleTime), typeof(OsuModPerfect) })] [TestCase(LegacyMods.SpunOut | LegacyMods.Easy, new[] { typeof(OsuModSpunOut), typeof(OsuModEasy) })] - public new void Test(LegacyMods legacyMods, Type[] expectedMods) => base.Test(legacyMods, expectedMods); + public new void TestFromLegacy(LegacyMods legacyMods, Type[] expectedMods) => base.TestFromLegacy(legacyMods, expectedMods); protected override Ruleset CreateRuleset() => new OsuRuleset(); } diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoLegacyModConversionTest.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoLegacyModConversionTest.cs index a59544386b..b67dc74ab1 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TaikoLegacyModConversionTest.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TaikoLegacyModConversionTest.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Taiko.Tests [TestCase(LegacyMods.SuddenDeath, new[] { typeof(TaikoModSuddenDeath) })] [TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath, new[] { typeof(TaikoModPerfect) })] [TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath | LegacyMods.DoubleTime, new[] { typeof(TaikoModDoubleTime), typeof(TaikoModPerfect) })] - public new void Test(LegacyMods legacyMods, Type[] expectedMods) => base.Test(legacyMods, expectedMods); + public new void TestFromLegacy(LegacyMods legacyMods, Type[] expectedMods) => base.TestFromLegacy(legacyMods, expectedMods); protected override Ruleset CreateRuleset() => new TaikoRuleset(); } diff --git a/osu.Game/Tests/Beatmaps/LegacyModConversionTest.cs b/osu.Game/Tests/Beatmaps/LegacyModConversionTest.cs index e93bf916c7..5327adc428 100644 --- a/osu.Game/Tests/Beatmaps/LegacyModConversionTest.cs +++ b/osu.Game/Tests/Beatmaps/LegacyModConversionTest.cs @@ -20,7 +20,7 @@ namespace osu.Game.Tests.Beatmaps /// protected abstract Ruleset CreateRuleset(); - protected void Test(LegacyMods legacyMods, Type[] expectedMods) + protected void TestFromLegacy(LegacyMods legacyMods, Type[] expectedMods) { var ruleset = CreateRuleset(); var mods = ruleset.ConvertFromLegacyMods(legacyMods).ToList(); From 268bbcf77dee56a5ca1f0e4a0fb0b931c4d6f9a5 Mon Sep 17 00:00:00 2001 From: kamp Date: Sun, 15 Nov 2020 14:22:46 +0100 Subject: [PATCH 31/62] Clamp selection movement instead of refusing to move on borders --- .../Edit/OsuSelectionHandler.cs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 24bf79f9ae..ec8c68005f 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -207,11 +207,17 @@ namespace osu.Game.Rulesets.Osu.Edit Quad quad = getSurroundingQuad(hitObjects); - if (quad.TopLeft.X + delta.X < 0 || - quad.TopLeft.Y + delta.Y < 0 || - quad.BottomRight.X + delta.X > DrawWidth || - quad.BottomRight.Y + delta.Y > DrawHeight) - return false; + Vector2 newTopLeft = quad.TopLeft + delta; + if (newTopLeft.X < 0) + delta.X -= newTopLeft.X; + if (newTopLeft.Y < 0) + delta.Y -= newTopLeft.Y; + + Vector2 newBottomRight = quad.BottomRight + delta; + if (newBottomRight.X > DrawWidth) + delta.X -= newBottomRight.X - DrawWidth; + if (newBottomRight.Y > DrawHeight) + delta.Y -= newBottomRight.Y - DrawHeight; foreach (var h in hitObjects) h.Position += delta; From 8b7429856791f31dfab695e5fa01edcd20c937dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 15 Nov 2020 14:25:22 +0100 Subject: [PATCH 32/62] Add base method for testing conversion in other direction --- osu.Game/Tests/Beatmaps/LegacyModConversionTest.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game/Tests/Beatmaps/LegacyModConversionTest.cs b/osu.Game/Tests/Beatmaps/LegacyModConversionTest.cs index 5327adc428..76f97db59f 100644 --- a/osu.Game/Tests/Beatmaps/LegacyModConversionTest.cs +++ b/osu.Game/Tests/Beatmaps/LegacyModConversionTest.cs @@ -31,5 +31,15 @@ namespace osu.Game.Tests.Beatmaps Assert.IsNotNull(mods.SingleOrDefault(mod => mod.GetType() == modType)); } } + + protected void TestToLegacy(LegacyMods expectedLegacyMods, Type[] providedModTypes) + { + var ruleset = CreateRuleset(); + var modInstances = ruleset.GetAllMods() + .Where(mod => providedModTypes.Contains(mod.GetType())) + .ToArray(); + var actualLegacyMods = ruleset.ConvertToLegacyMods(modInstances); + Assert.AreEqual(expectedLegacyMods, actualLegacyMods); + } } } From f9fa7c86eaa169657485dbdaabf975abbedcc65c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 15 Nov 2020 14:53:25 +0100 Subject: [PATCH 33/62] Cover mapping fully for catch mods --- .../CatchLegacyModConversionTest.cs | 31 ++++++++++++++----- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/CatchLegacyModConversionTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchLegacyModConversionTest.cs index 4102dff37c..eae07daa3d 100644 --- a/osu.Game.Rulesets.Catch.Tests/CatchLegacyModConversionTest.cs +++ b/osu.Game.Rulesets.Catch.Tests/CatchLegacyModConversionTest.cs @@ -12,18 +12,33 @@ namespace osu.Game.Rulesets.Catch.Tests [TestFixture] public class CatchLegacyModConversionTest : LegacyModConversionTest { - [TestCase(LegacyMods.Easy, new[] { typeof(CatchModEasy) })] - [TestCase(LegacyMods.HardRock | LegacyMods.DoubleTime, new[] { typeof(CatchModHardRock), typeof(CatchModDoubleTime) })] - [TestCase(LegacyMods.DoubleTime, new[] { typeof(CatchModDoubleTime) })] - [TestCase(LegacyMods.Nightcore, new[] { typeof(CatchModNightcore) })] + private static readonly object[][] catch_mod_mapping = + { + new object[] { LegacyMods.NoFail, new[] { typeof(CatchModNoFail) } }, + new object[] { LegacyMods.Easy, new[] { typeof(CatchModEasy) } }, + new object[] { LegacyMods.Hidden, new[] { typeof(CatchModHidden) } }, + new object[] { LegacyMods.HardRock, new[] { typeof(CatchModHardRock) } }, + new object[] { LegacyMods.SuddenDeath, new[] { typeof(CatchModSuddenDeath) } }, + new object[] { LegacyMods.DoubleTime, new[] { typeof(CatchModDoubleTime) } }, + new object[] { LegacyMods.Relax, new[] { typeof(CatchModRelax) } }, + new object[] { LegacyMods.HalfTime, new[] { typeof(CatchModHalfTime) } }, + new object[] { LegacyMods.Nightcore, new[] { typeof(CatchModNightcore) } }, + new object[] { LegacyMods.Flashlight, new[] { typeof(CatchModFlashlight) } }, + new object[] { LegacyMods.Autoplay, new[] { typeof(CatchModAutoplay) } }, + new object[] { LegacyMods.Perfect, new[] { typeof(CatchModPerfect) } }, + new object[] { LegacyMods.Cinema, new[] { typeof(CatchModCinema) } }, + new object[] { LegacyMods.HardRock | LegacyMods.DoubleTime, new[] { typeof(CatchModHardRock), typeof(CatchModDoubleTime) } } + }; + + [TestCaseSource(nameof(catch_mod_mapping))] + [TestCase(LegacyMods.Cinema | LegacyMods.Autoplay, new[] { typeof(CatchModCinema) })] [TestCase(LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(CatchModNightcore) })] - [TestCase(LegacyMods.Flashlight | LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(CatchModFlashlight), typeof(CatchModNightcore) })] - [TestCase(LegacyMods.Perfect, new[] { typeof(CatchModPerfect) })] - [TestCase(LegacyMods.SuddenDeath, new[] { typeof(CatchModSuddenDeath) })] [TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath, new[] { typeof(CatchModPerfect) })] - [TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath | LegacyMods.DoubleTime, new[] { typeof(CatchModDoubleTime), typeof(CatchModPerfect) })] public new void TestFromLegacy(LegacyMods legacyMods, Type[] expectedMods) => base.TestFromLegacy(legacyMods, expectedMods); + [TestCaseSource(nameof(catch_mod_mapping))] + public new void TestToLegacy(LegacyMods legacyMods, Type[] givenMods) => base.TestToLegacy(legacyMods, givenMods); + protected override Ruleset CreateRuleset() => new CatchRuleset(); } } From ee5e70135f42df4bc31fa2747413886ee090a131 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 15 Nov 2020 15:09:02 +0100 Subject: [PATCH 34/62] Cover mapping fully for mania mods --- .../ManiaLegacyModConversionTest.cs | 45 ++++++++++++++----- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaLegacyModConversionTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaLegacyModConversionTest.cs index 221eed3576..a28c188051 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaLegacyModConversionTest.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaLegacyModConversionTest.cs @@ -12,20 +12,45 @@ namespace osu.Game.Rulesets.Mania.Tests [TestFixture] public class ManiaLegacyModConversionTest : LegacyModConversionTest { - [TestCase(LegacyMods.Easy, new[] { typeof(ManiaModEasy) })] - [TestCase(LegacyMods.HardRock | LegacyMods.DoubleTime, new[] { typeof(ManiaModHardRock), typeof(ManiaModDoubleTime) })] - [TestCase(LegacyMods.DoubleTime, new[] { typeof(ManiaModDoubleTime) })] - [TestCase(LegacyMods.Nightcore, new[] { typeof(ManiaModNightcore) })] + private static readonly object[][] mania_mod_mapping = + { + new object[] { LegacyMods.NoFail, new[] { typeof(ManiaModNoFail) } }, + new object[] { LegacyMods.Easy, new[] { typeof(ManiaModEasy) } }, + new object[] { LegacyMods.Hidden, new[] { typeof(ManiaModHidden) } }, + new object[] { LegacyMods.HardRock, new[] { typeof(ManiaModHardRock) } }, + new object[] { LegacyMods.SuddenDeath, new[] { typeof(ManiaModSuddenDeath) } }, + new object[] { LegacyMods.DoubleTime, new[] { typeof(ManiaModDoubleTime) } }, + new object[] { LegacyMods.HalfTime, new[] { typeof(ManiaModHalfTime) } }, + new object[] { LegacyMods.Nightcore, new[] { typeof(ManiaModNightcore) } }, + new object[] { LegacyMods.Flashlight, new[] { typeof(ManiaModFlashlight) } }, + new object[] { LegacyMods.Autoplay, new[] { typeof(ManiaModAutoplay) } }, + new object[] { LegacyMods.Perfect, new[] { typeof(ManiaModPerfect) } }, + new object[] { LegacyMods.Key4, new[] { typeof(ManiaModKey4) } }, + new object[] { LegacyMods.Key5, new[] { typeof(ManiaModKey5) } }, + new object[] { LegacyMods.Key6, new[] { typeof(ManiaModKey6) } }, + new object[] { LegacyMods.Key7, new[] { typeof(ManiaModKey7) } }, + new object[] { LegacyMods.Key8, new[] { typeof(ManiaModKey8) } }, + new object[] { LegacyMods.FadeIn, new[] { typeof(ManiaModFadeIn) } }, + new object[] { LegacyMods.Random, new[] { typeof(ManiaModRandom) } }, + new object[] { LegacyMods.Cinema, new[] { typeof(ManiaModCinema) } }, + new object[] { LegacyMods.Key9, new[] { typeof(ManiaModKey9) } }, + new object[] { LegacyMods.KeyCoop, new[] { typeof(ManiaModDualStages) } }, + new object[] { LegacyMods.Key1, new[] { typeof(ManiaModKey1) } }, + new object[] { LegacyMods.Key3, new[] { typeof(ManiaModKey3) } }, + new object[] { LegacyMods.Key2, new[] { typeof(ManiaModKey2) } }, + new object[] { LegacyMods.Mirror, new[] { typeof(ManiaModMirror) } }, + new object[] { LegacyMods.HardRock | LegacyMods.DoubleTime, new[] { typeof(ManiaModHardRock), typeof(ManiaModDoubleTime) } } + }; + + [TestCaseSource(nameof(mania_mod_mapping))] + [TestCase(LegacyMods.Cinema | LegacyMods.Autoplay, new[] { typeof(ManiaModCinema) })] [TestCase(LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(ManiaModNightcore) })] - [TestCase(LegacyMods.Flashlight | LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(ManiaModFlashlight), typeof(ManiaModNightcore) })] - [TestCase(LegacyMods.Perfect, new[] { typeof(ManiaModPerfect) })] - [TestCase(LegacyMods.SuddenDeath, new[] { typeof(ManiaModSuddenDeath) })] [TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath, new[] { typeof(ManiaModPerfect) })] - [TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath | LegacyMods.DoubleTime, new[] { typeof(ManiaModDoubleTime), typeof(ManiaModPerfect) })] - [TestCase(LegacyMods.Random | LegacyMods.SuddenDeath, new[] { typeof(ManiaModRandom), typeof(ManiaModSuddenDeath) })] - [TestCase(LegacyMods.Flashlight | LegacyMods.Mirror, new[] { typeof(ManiaModFlashlight), typeof(ManiaModMirror) })] public new void TestFromLegacy(LegacyMods legacyMods, Type[] expectedMods) => base.TestFromLegacy(legacyMods, expectedMods); + [TestCaseSource(nameof(mania_mod_mapping))] + public new void TestToLegacy(LegacyMods legacyMods, Type[] givenMods) => base.TestToLegacy(legacyMods, givenMods); + protected override Ruleset CreateRuleset() => new ManiaRuleset(); } } From 9f27d4a9f45a438c07463d2d5c41b3a6a1d4f26f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 15 Nov 2020 15:16:17 +0100 Subject: [PATCH 35/62] Cover mapping fully for osu! mods --- .../OsuLegacyModConversionTest.cs | 36 ++++++++++++++----- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/OsuLegacyModConversionTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuLegacyModConversionTest.cs index 731150a584..51da5b85cd 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuLegacyModConversionTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuLegacyModConversionTest.cs @@ -12,19 +12,37 @@ namespace osu.Game.Rulesets.Osu.Tests [TestFixture] public class OsuLegacyModConversionTest : LegacyModConversionTest { - [TestCase(LegacyMods.Easy, new[] { typeof(OsuModEasy) })] - [TestCase(LegacyMods.HardRock | LegacyMods.DoubleTime, new[] { typeof(OsuModHardRock), typeof(OsuModDoubleTime) })] - [TestCase(LegacyMods.DoubleTime, new[] { typeof(OsuModDoubleTime) })] - [TestCase(LegacyMods.Nightcore, new[] { typeof(OsuModNightcore) })] + private static readonly object[][] osu_mod_mapping = + { + new object[] { LegacyMods.NoFail, new[] { typeof(OsuModNoFail) } }, + new object[] { LegacyMods.Easy, new[] { typeof(OsuModEasy) } }, + new object[] { LegacyMods.TouchDevice, new[] { typeof(OsuModTouchDevice) } }, + new object[] { LegacyMods.Hidden, new[] { typeof(OsuModHidden) } }, + new object[] { LegacyMods.HardRock, new[] { typeof(OsuModHardRock) } }, + new object[] { LegacyMods.SuddenDeath, new[] { typeof(OsuModSuddenDeath) } }, + new object[] { LegacyMods.DoubleTime, new[] { typeof(OsuModDoubleTime) } }, + new object[] { LegacyMods.Relax, new[] { typeof(OsuModRelax) } }, + new object[] { LegacyMods.HalfTime, new[] { typeof(OsuModHalfTime) } }, + new object[] { LegacyMods.Nightcore, new[] { typeof(OsuModNightcore) } }, + new object[] { LegacyMods.Flashlight, new[] { typeof(OsuModFlashlight) } }, + new object[] { LegacyMods.Autoplay, new[] { typeof(OsuModAutoplay) } }, + new object[] { LegacyMods.SpunOut, new[] { typeof(OsuModSpunOut) } }, + new object[] { LegacyMods.Autopilot, new[] { typeof(OsuModAutopilot) } }, + new object[] { LegacyMods.Perfect, new[] { typeof(OsuModPerfect) } }, + new object[] { LegacyMods.Cinema, new[] { typeof(OsuModCinema) } }, + new object[] { LegacyMods.Target, new[] { typeof(OsuModTarget) } }, + new object[] { LegacyMods.HardRock | LegacyMods.DoubleTime, new[] { typeof(OsuModHardRock), typeof(OsuModDoubleTime) } } + }; + + [TestCaseSource(nameof(osu_mod_mapping))] + [TestCase(LegacyMods.Cinema | LegacyMods.Autoplay, new[] { typeof(OsuModCinema) })] [TestCase(LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(OsuModNightcore) })] - [TestCase(LegacyMods.Flashlight | LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(OsuModFlashlight), typeof(OsuModFlashlight) })] - [TestCase(LegacyMods.Perfect, new[] { typeof(OsuModPerfect) })] - [TestCase(LegacyMods.SuddenDeath, new[] { typeof(OsuModSuddenDeath) })] [TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath, new[] { typeof(OsuModPerfect) })] - [TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath | LegacyMods.DoubleTime, new[] { typeof(OsuModDoubleTime), typeof(OsuModPerfect) })] - [TestCase(LegacyMods.SpunOut | LegacyMods.Easy, new[] { typeof(OsuModSpunOut), typeof(OsuModEasy) })] public new void TestFromLegacy(LegacyMods legacyMods, Type[] expectedMods) => base.TestFromLegacy(legacyMods, expectedMods); + [TestCaseSource(nameof(osu_mod_mapping))] + public new void TestToLegacy(LegacyMods legacyMods, Type[] givenMods) => base.TestToLegacy(legacyMods, givenMods); + protected override Ruleset CreateRuleset() => new OsuRuleset(); } } From e9b5f54128b8ced841062a40406a21946af888ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 15 Nov 2020 15:24:57 +0100 Subject: [PATCH 36/62] Cover mapping fully for taiko mods --- .../TaikoLegacyModConversionTest.cs | 32 ++++++++++++++----- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoLegacyModConversionTest.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoLegacyModConversionTest.cs index b67dc74ab1..a039e84106 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TaikoLegacyModConversionTest.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TaikoLegacyModConversionTest.cs @@ -12,18 +12,34 @@ namespace osu.Game.Rulesets.Taiko.Tests [TestFixture] public class TaikoLegacyModConversionTest : LegacyModConversionTest { - [TestCase(LegacyMods.Easy, new[] { typeof(TaikoModEasy) })] - [TestCase(LegacyMods.HardRock | LegacyMods.DoubleTime, new[] { typeof(TaikoModHardRock), typeof(TaikoModDoubleTime) })] - [TestCase(LegacyMods.DoubleTime, new[] { typeof(TaikoModDoubleTime) })] - [TestCase(LegacyMods.Nightcore, new[] { typeof(TaikoModNightcore) })] + private static readonly object[][] taiko_mod_mapping = + { + new object[] { LegacyMods.NoFail, new[] { typeof(TaikoModNoFail) } }, + new object[] { LegacyMods.Easy, new[] { typeof(TaikoModEasy) } }, + new object[] { LegacyMods.Hidden, new[] { typeof(TaikoModHidden) } }, + new object[] { LegacyMods.HardRock, new[] { typeof(TaikoModHardRock) } }, + new object[] { LegacyMods.SuddenDeath, new[] { typeof(TaikoModSuddenDeath) } }, + new object[] { LegacyMods.DoubleTime, new[] { typeof(TaikoModDoubleTime) } }, + new object[] { LegacyMods.Relax, new[] { typeof(TaikoModRelax) } }, + new object[] { LegacyMods.HalfTime, new[] { typeof(TaikoModHalfTime) } }, + new object[] { LegacyMods.Nightcore, new[] { typeof(TaikoModNightcore) } }, + new object[] { LegacyMods.Flashlight, new[] { typeof(TaikoModFlashlight) } }, + new object[] { LegacyMods.Autoplay, new[] { typeof(TaikoModAutoplay) } }, + new object[] { LegacyMods.Perfect, new[] { typeof(TaikoModPerfect) } }, + new object[] { LegacyMods.Random, new[] { typeof(TaikoModRandom) } }, + new object[] { LegacyMods.Cinema, new[] { typeof(TaikoModCinema) } }, + new object[] { LegacyMods.HardRock | LegacyMods.DoubleTime, new[] { typeof(TaikoModHardRock), typeof(TaikoModDoubleTime) } } + }; + + [TestCaseSource(nameof(taiko_mod_mapping))] + [TestCase(LegacyMods.Cinema | LegacyMods.Autoplay, new[] { typeof(TaikoModCinema) })] [TestCase(LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(TaikoModNightcore) })] - [TestCase(LegacyMods.Flashlight | LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(TaikoModFlashlight), typeof(TaikoModNightcore) })] - [TestCase(LegacyMods.Perfect, new[] { typeof(TaikoModPerfect) })] - [TestCase(LegacyMods.SuddenDeath, new[] { typeof(TaikoModSuddenDeath) })] [TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath, new[] { typeof(TaikoModPerfect) })] - [TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath | LegacyMods.DoubleTime, new[] { typeof(TaikoModDoubleTime), typeof(TaikoModPerfect) })] public new void TestFromLegacy(LegacyMods legacyMods, Type[] expectedMods) => base.TestFromLegacy(legacyMods, expectedMods); + [TestCaseSource(nameof(taiko_mod_mapping))] + public new void TestToLegacy(LegacyMods legacyMods, Type[] givenMods) => base.TestToLegacy(legacyMods, givenMods); + protected override Ruleset CreateRuleset() => new TaikoRuleset(); } } From 5ace7abaa8f49372293ca77ffde1ec8613a4f014 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 15 Nov 2020 15:29:50 +0100 Subject: [PATCH 37/62] Add abstract non-generic ModNightcore to pattern-match over --- osu.Game/Rulesets/Mods/ModNightcore.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModNightcore.cs b/osu.Game/Rulesets/Mods/ModNightcore.cs index e8b051b4d9..a44967c21c 100644 --- a/osu.Game/Rulesets/Mods/ModNightcore.cs +++ b/osu.Game/Rulesets/Mods/ModNightcore.cs @@ -18,14 +18,17 @@ using osu.Game.Skinning; namespace osu.Game.Rulesets.Mods { - public abstract class ModNightcore : ModDoubleTime, IApplicableToDrawableRuleset - where TObject : HitObject + public abstract class ModNightcore : ModDoubleTime { public override string Name => "Nightcore"; public override string Acronym => "NC"; public override IconUsage? Icon => OsuIcon.ModNightcore; public override string Description => "Uguuuuuuuu..."; + } + public abstract class ModNightcore : ModNightcore, IApplicableToDrawableRuleset + where TObject : HitObject + { private readonly BindableNumber tempoAdjust = new BindableDouble(1); private readonly BindableNumber freqAdjust = new BindableDouble(1); From 5d44286d387bd2fe19994bf9682809599f610607 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 15 Nov 2020 15:35:06 +0100 Subject: [PATCH 38/62] Add missing generic mappings to legacy mods --- osu.Game/Rulesets/Ruleset.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 8caadffd1d..b3b3d11ab3 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -81,10 +81,18 @@ namespace osu.Game.Rulesets value |= LegacyMods.HardRock; break; + case ModPerfect _: + value |= LegacyMods.Perfect; + break; + case ModSuddenDeath _: value |= LegacyMods.SuddenDeath; break; + case ModNightcore _: + value |= LegacyMods.Nightcore; + break; + case ModDoubleTime _: value |= LegacyMods.DoubleTime; break; @@ -100,6 +108,14 @@ namespace osu.Game.Rulesets case ModFlashlight _: value |= LegacyMods.Flashlight; break; + + case ModCinema _: + value |= LegacyMods.Cinema; + break; + + case ModAutoplay _: + value |= LegacyMods.Autoplay; + break; } } From e5d9cca9de2654b8decdf41708ed6ffda2838230 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 15 Nov 2020 15:38:12 +0100 Subject: [PATCH 39/62] Fix mania-specific missing cases --- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index b92e042686..f70e7b315e 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -119,6 +119,9 @@ namespace osu.Game.Rulesets.Mania if (mods.HasFlag(LegacyMods.Key9)) yield return new ManiaModKey9(); + if (mods.HasFlag(LegacyMods.KeyCoop)) + yield return new ManiaModDualStages(); + if (mods.HasFlag(LegacyMods.NoFail)) yield return new ManiaModNoFail(); @@ -173,13 +176,22 @@ namespace osu.Game.Rulesets.Mania value |= LegacyMods.Key9; break; + case ManiaModDualStages _: + value |= LegacyMods.KeyCoop; + break; + case ManiaModFadeIn _: value |= LegacyMods.FadeIn; + value &= ~LegacyMods.Hidden; // due to inheritance break; case ManiaModMirror _: value |= LegacyMods.Mirror; break; + + case ManiaModRandom _: + value |= LegacyMods.Random; + break; } } From a17990f2eea290477ad9b0bc740a652202520cae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 15 Nov 2020 15:40:31 +0100 Subject: [PATCH 40/62] Fix osu!-specific missing cases --- osu.Game.Rulesets.Osu/OsuRuleset.cs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 678fb8aba6..d8180b0e58 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -107,6 +107,35 @@ namespace osu.Game.Rulesets.Osu yield return new OsuModTouchDevice(); } + public override LegacyMods ConvertToLegacyMods(Mod[] mods) + { + var value = base.ConvertToLegacyMods(mods); + + foreach (var mod in mods) + { + switch (mod) + { + case OsuModAutopilot _: + value |= LegacyMods.Autopilot; + break; + + case OsuModSpunOut _: + value |= LegacyMods.SpunOut; + break; + + case OsuModTarget _: + value |= LegacyMods.Target; + break; + + case OsuModTouchDevice _: + value |= LegacyMods.TouchDevice; + break; + } + } + + return value; + } + public override IEnumerable GetModsFor(ModType type) { switch (type) From 7736d1ae85bfcfd7cfc6343a69e742da59c7423a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 15 Nov 2020 15:41:58 +0100 Subject: [PATCH 41/62] Fix taiko-specific missing cases --- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 73e9c16d07..2a49dd655c 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -92,6 +92,19 @@ namespace osu.Game.Rulesets.Taiko if (mods.HasFlag(LegacyMods.Relax)) yield return new TaikoModRelax(); + + if (mods.HasFlag(LegacyMods.Random)) + yield return new TaikoModRandom(); + } + + public override LegacyMods ConvertToLegacyMods(Mod[] mods) + { + var value = base.ConvertToLegacyMods(mods); + + if (mods.OfType().Any()) + value |= LegacyMods.Random; + + return value; } public override IEnumerable GetModsFor(ModType type) From 83727a2e85a868d612a80bdc7a65bb0bf7b6befd Mon Sep 17 00:00:00 2001 From: kamp Date: Sun, 15 Nov 2020 16:06:29 +0100 Subject: [PATCH 42/62] Add quick-delete tests --- .../Editing/TestSceneEditorQuickDelete.cs | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 osu.Game.Tests/Visual/Editing/TestSceneEditorQuickDelete.cs diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorQuickDelete.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorQuickDelete.cs new file mode 100644 index 0000000000..cb921fd857 --- /dev/null +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorQuickDelete.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 System.Linq; +using NUnit.Framework; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles; +using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components; +using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders; +using osu.Game.Tests.Beatmaps; +using osu.Game.Rulesets.Edit; +using osu.Game.Screens.Edit.Compose; +using osu.Game.Screens.Edit.Compose.Components; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Tests.Visual.Editing +{ + public class TestSceneEditorQuickDelete : EditorTestScene + { + protected override Ruleset CreateEditorRuleset() => new OsuRuleset(); + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false); + + [Test] + public void TestQuickDeleteRemovesObject() + { + var addedObject = new HitCircle { StartTime = 1000 }; + + AddStep("add hitobject", () => EditorBeatmap.Add(addedObject)); + + AddStep("select added object", () => EditorBeatmap.SelectedHitObjects.Add(addedObject)); + + AddStep("move mouse to object", () => + { + var pos = getBlueprintContainer.SelectionBlueprints + .ChildrenOfType().First() + .ChildrenOfType().First().ScreenSpaceDrawQuad.Centre; + InputManager.MoveMouseTo(pos); + }); + AddStep("hold shift", () => InputManager.PressKey(Key.ShiftLeft)); + AddStep("rightclick", () => InputManager.Click(MouseButton.Right)); + AddStep("release shift", () => InputManager.ReleaseKey(Key.ShiftLeft)); + + AddAssert("no hitobjects in beatmap", () => EditorBeatmap.HitObjects.Count == 0); + } + + [Test] + public void TestQuickDeleteRemovesSliderControlPoint() + { + Slider slider = new Slider { StartTime = 1000 }; + + PathControlPoint[] points = new PathControlPoint[] + { + new PathControlPoint(), + new PathControlPoint(new Vector2(50, 0)), + new PathControlPoint(new Vector2(100, 0)) + }; + + AddStep("add slider", () => + { + slider.Path = new SliderPath(points); + EditorBeatmap.Add(slider); + }); + + AddStep("select added slider", () => EditorBeatmap.SelectedHitObjects.Add(slider)); + + AddStep("move mouse to controlpoint", () => + { + // This doesn't get the HitCirclePiece corresponding to the last control point on consecutive runs, + // causing the slider to translate by 50 every time and go off the screen after about 10 runs. + var pos = getBlueprintContainer.SelectionBlueprints + .ChildrenOfType().First() + .ChildrenOfType().Last().ScreenSpaceDrawQuad.Centre; + InputManager.MoveMouseTo(pos); + }); + AddStep("hold shift", () => InputManager.PressKey(Key.ShiftLeft)); + AddStep("rightclick", () => InputManager.Click(MouseButton.Right)); + AddStep("release shift", () => InputManager.ReleaseKey(Key.ShiftLeft)); + + AddAssert("slider has 2 points", () => slider.Path.ControlPoints.Count == 2); + } + + private BlueprintContainer getBlueprintContainer => Editor.ChildrenOfType().First() + .ChildrenOfType().First() + .ChildrenOfType().First(); + } +} From 6b38bb9d76e8da7857ef2e197ba77ce674942285 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 15 Nov 2020 16:38:25 +0100 Subject: [PATCH 43/62] Add test coverage for new ownerless behaviour --- .../TestScenePreviewTrackManager.cs | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs b/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs index d76905dab8..a3db20ce83 100644 --- a/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs +++ b/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs @@ -167,6 +167,21 @@ namespace osu.Game.Tests.Visual.Components AddAssert("game not muted", () => audio.Tracks.AggregateVolume.Value != 0); } + [Test] + public void TestOwnerNotRegistered() + { + PreviewTrack track = null; + + AddStep("get track", () => Add(new TestTrackOwner(track = getTrack(), registerAsOwner: false))); + AddUntilStep("wait for loaded", () => track.IsLoaded); + + AddStep("start track", () => track.Start()); + AddUntilStep("track is running", () => track.IsRunning); + + AddStep("cancel from anyone", () => trackManager.StopAnyPlaying(this)); + AddAssert("track stopped", () => !track.IsRunning); + } + private TestPreviewTrack getTrack() => (TestPreviewTrack)trackManager.Get(null); private TestPreviewTrack getOwnedTrack() @@ -181,10 +196,12 @@ namespace osu.Game.Tests.Visual.Components private class TestTrackOwner : CompositeDrawable, IPreviewTrackOwner { private readonly PreviewTrack track; + private readonly bool registerAsOwner; - public TestTrackOwner(PreviewTrack track) + public TestTrackOwner(PreviewTrack track, bool registerAsOwner = true) { this.track = track; + this.registerAsOwner = registerAsOwner; } [BackgroundDependencyLoader] @@ -196,7 +213,8 @@ namespace osu.Game.Tests.Visual.Components protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - dependencies.CacheAs(this); + if (registerAsOwner) + dependencies.CacheAs(this); return dependencies; } } From 1db303b15924cba91b828d553a858508c6840b38 Mon Sep 17 00:00:00 2001 From: kamp Date: Sun, 15 Nov 2020 16:54:48 +0100 Subject: [PATCH 44/62] Revert beginClickSelection logic --- .../Edit/Compose/Components/BlueprintContainer.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index c9043ccef3..4390ed4f10 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -338,15 +338,18 @@ namespace osu.Game.Screens.Edit.Compose.Components /// Whether a selection was performed. private bool beginClickSelection(MouseButtonEvent e) { - foreach (SelectionBlueprint blueprint in SelectionBlueprints.AliveChildren.ToList()) + bool selectedPerformed = true; + + foreach (SelectionBlueprint blueprint in SelectionBlueprints.AliveChildren) { if (!blueprint.IsHovered) continue; - if (SelectionHandler.HandleSelectionRequested(blueprint, e)) - return clickSelectionBegan = true; + selectedPerformed &= SelectionHandler.HandleSelectionRequested(blueprint, e); + clickSelectionBegan = true; + break; } - return false; + return selectedPerformed; } /// From c77ec3e905adc7ffd5bf2e74001cea184081887d Mon Sep 17 00:00:00 2001 From: kamp Date: Sun, 15 Nov 2020 17:42:52 +0100 Subject: [PATCH 45/62] Fix slider control point quickdelete test --- osu.Game.Tests/Visual/Editing/TestSceneEditorQuickDelete.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorQuickDelete.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorQuickDelete.cs index cb921fd857..55daf130a8 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorQuickDelete.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorQuickDelete.cs @@ -12,6 +12,7 @@ using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components; using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders; +using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components; using osu.Game.Tests.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit.Compose; @@ -72,11 +73,10 @@ namespace osu.Game.Tests.Visual.Editing AddStep("move mouse to controlpoint", () => { - // This doesn't get the HitCirclePiece corresponding to the last control point on consecutive runs, - // causing the slider to translate by 50 every time and go off the screen after about 10 runs. var pos = getBlueprintContainer.SelectionBlueprints .ChildrenOfType().First() - .ChildrenOfType().Last().ScreenSpaceDrawQuad.Centre; + .ChildrenOfType().First() + .ChildrenOfType().ElementAt(1).ScreenSpaceDrawQuad.Centre; InputManager.MoveMouseTo(pos); }); AddStep("hold shift", () => InputManager.PressKey(Key.ShiftLeft)); From 42de4437ccf3d18271e6c119a8f2d40fd619b4c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 15 Nov 2020 19:43:42 +0100 Subject: [PATCH 46/62] Add failing test case --- .../Gameplay/TestScenePoolingRuleset.cs | 44 ++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs index d009d805f0..3e777119c4 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs @@ -57,6 +57,43 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("DHO reused", () => this.ChildrenOfType().Single() == firstObject); } + [Test] + public void TestCustomTransformsClearedBetweenReuses() + { + ManualClock clock = null; + + createTest(new Beatmap + { + HitObjects = + { + new HitObject(), + new HitObject { StartTime = 2000 } + } + }, 1, () => new FramedClock(clock = new ManualClock())); + + DrawableTestHitObject firstObject = null; + Vector2 position = default; + + AddUntilStep("first object shown", () => this.ChildrenOfType().SingleOrDefault()?.HitObject == drawableRuleset.Beatmap.HitObjects[0]); + AddStep("get DHO", () => firstObject = this.ChildrenOfType().Single()); + AddStep("store position", () => position = firstObject.Position); + AddStep("add custom transform", () => firstObject.ApplyCustomUpdateState += onStateUpdate); + + AddStep("fast forward past first object", () => clock.CurrentTime = 1500); + AddStep("unapply custom transform", () => firstObject.ApplyCustomUpdateState -= onStateUpdate); + + AddStep("fast forward to second object", () => clock.CurrentTime = drawableRuleset.Beatmap.HitObjects[1].StartTime); + AddUntilStep("second object shown", () => this.ChildrenOfType().SingleOrDefault()?.HitObject == drawableRuleset.Beatmap.HitObjects[1]); + AddAssert("DHO reused", () => this.ChildrenOfType().Single() == firstObject); + AddAssert("object in new position", () => firstObject.Position != position); + + void onStateUpdate(DrawableHitObject hitObject, ArmedState state) + { + using (hitObject.BeginAbsoluteSequence(hitObject.StateUpdateTime)) + hitObject.MoveToOffset(new Vector2(-100, 0)); + } + } + [Test] public void TestNotReusedWithHitObjectsSpacedClose() { @@ -210,7 +247,6 @@ namespace osu.Game.Tests.Visual.Gameplay Anchor = Anchor.Centre; Origin = Anchor.Centre; - Position = new Vector2(RNG.Next(-200, 200), RNG.Next(-200, 200)); Size = new Vector2(50, 50); Colour = new Color4(RNG.NextSingle(), RNG.NextSingle(), RNG.NextSingle(), 1f); @@ -225,6 +261,12 @@ namespace osu.Game.Tests.Visual.Gameplay }); } + protected override void OnApply(HitObject hitObject) + { + base.OnApply(hitObject); + Position = new Vector2(RNG.Next(-200, 200), RNG.Next(-200, 200)); + } + protected override void CheckForResult(bool userTriggered, double timeOffset) { if (timeOffset > HitObject.Duration) From 9c0a0031d671712e96061b534732732af1989776 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 15 Nov 2020 19:45:49 +0100 Subject: [PATCH 47/62] Clear existing transforms on DHO return to pool --- .../Rulesets/Objects/Drawables/DrawableHitObject.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index b400c532c5..930b1471f3 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -287,6 +287,8 @@ namespace osu.Game.Rulesets.Objects.Drawables HitObject = null; lifetimeEntry = null; + clearExistingStateTransforms(); + hasHitObjectApplied = false; } @@ -403,8 +405,7 @@ namespace osu.Game.Rulesets.Objects.Drawables double transformTime = HitObject.StartTime - InitialLifetimeOffset; - base.ApplyTransformsAt(double.MinValue, true); - base.ClearTransformsAfter(double.MinValue, true); + clearExistingStateTransforms(); using (BeginAbsoluteSequence(transformTime, true)) UpdateInitialTransforms(); @@ -432,6 +433,12 @@ namespace osu.Game.Rulesets.Objects.Drawables PlaySamples(); } + private void clearExistingStateTransforms() + { + base.ApplyTransformsAt(double.MinValue, true); + base.ClearTransformsAfter(double.MinValue, true); + } + /// /// Apply (generally fade-in) transforms leading into the start time. /// The local drawable hierarchy is recursively delayed to for convenience. From 4e77800b98aa35758c555d4c690e34d4aa6312aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 15 Nov 2020 20:51:35 +0100 Subject: [PATCH 48/62] Rename & simplify property --- .../Visual/Editing/TestSceneEditorQuickDelete.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorQuickDelete.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorQuickDelete.cs index 55daf130a8..5bae6ec5f7 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorQuickDelete.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorQuickDelete.cs @@ -28,6 +28,9 @@ namespace osu.Game.Tests.Visual.Editing protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false); + private BlueprintContainer blueprintContainer + => Editor.ChildrenOfType().First(); + [Test] public void TestQuickDeleteRemovesObject() { @@ -39,7 +42,7 @@ namespace osu.Game.Tests.Visual.Editing AddStep("move mouse to object", () => { - var pos = getBlueprintContainer.SelectionBlueprints + var pos = blueprintContainer.SelectionBlueprints .ChildrenOfType().First() .ChildrenOfType().First().ScreenSpaceDrawQuad.Centre; InputManager.MoveMouseTo(pos); @@ -73,7 +76,7 @@ namespace osu.Game.Tests.Visual.Editing AddStep("move mouse to controlpoint", () => { - var pos = getBlueprintContainer.SelectionBlueprints + var pos = blueprintContainer.SelectionBlueprints .ChildrenOfType().First() .ChildrenOfType().First() .ChildrenOfType().ElementAt(1).ScreenSpaceDrawQuad.Centre; @@ -85,9 +88,5 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("slider has 2 points", () => slider.Path.ControlPoints.Count == 2); } - - private BlueprintContainer getBlueprintContainer => Editor.ChildrenOfType().First() - .ChildrenOfType().First() - .ChildrenOfType().First(); } } From 1f0945d4deef4678fcc785a7931bdd6fcf7c9665 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 15 Nov 2020 20:52:33 +0100 Subject: [PATCH 49/62] Simplify accesses via ChildrenOfType() --- .../Visual/Editing/TestSceneEditorQuickDelete.cs | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorQuickDelete.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorQuickDelete.cs index 5bae6ec5f7..9d35185f7e 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorQuickDelete.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorQuickDelete.cs @@ -9,13 +9,9 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components; -using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders; using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components; using osu.Game.Tests.Beatmaps; -using osu.Game.Rulesets.Edit; -using osu.Game.Screens.Edit.Compose; using osu.Game.Screens.Edit.Compose.Components; using osuTK; using osuTK.Input; @@ -42,9 +38,7 @@ namespace osu.Game.Tests.Visual.Editing AddStep("move mouse to object", () => { - var pos = blueprintContainer.SelectionBlueprints - .ChildrenOfType().First() - .ChildrenOfType().First().ScreenSpaceDrawQuad.Centre; + var pos = blueprintContainer.ChildrenOfType().First().ScreenSpaceDrawQuad.Centre; InputManager.MoveMouseTo(pos); }); AddStep("hold shift", () => InputManager.PressKey(Key.ShiftLeft)); @@ -76,10 +70,7 @@ namespace osu.Game.Tests.Visual.Editing AddStep("move mouse to controlpoint", () => { - var pos = blueprintContainer.SelectionBlueprints - .ChildrenOfType().First() - .ChildrenOfType().First() - .ChildrenOfType().ElementAt(1).ScreenSpaceDrawQuad.Centre; + var pos = blueprintContainer.ChildrenOfType().ElementAt(1).ScreenSpaceDrawQuad.Centre; InputManager.MoveMouseTo(pos); }); AddStep("hold shift", () => InputManager.PressKey(Key.ShiftLeft)); From 337311c3236c848c9ead6c885991a227f288a581 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 15 Nov 2020 20:52:58 +0100 Subject: [PATCH 50/62] Remove redundant type specification --- osu.Game.Tests/Visual/Editing/TestSceneEditorQuickDelete.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorQuickDelete.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorQuickDelete.cs index 9d35185f7e..9bcb056f25 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorQuickDelete.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorQuickDelete.cs @@ -53,7 +53,7 @@ namespace osu.Game.Tests.Visual.Editing { Slider slider = new Slider { StartTime = 1000 }; - PathControlPoint[] points = new PathControlPoint[] + PathControlPoint[] points = { new PathControlPoint(), new PathControlPoint(new Vector2(50, 0)), From 399a1a16a070d31e36a2076f505dd9111de8f381 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 15 Nov 2020 21:06:47 +0100 Subject: [PATCH 51/62] Refactor beginClickSelection in a slightly different way --- .../Screens/Edit/Compose/Components/BlueprintContainer.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 4390ed4f10..8f3c86b98a 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -338,18 +338,14 @@ namespace osu.Game.Screens.Edit.Compose.Components /// Whether a selection was performed. private bool beginClickSelection(MouseButtonEvent e) { - bool selectedPerformed = true; - foreach (SelectionBlueprint blueprint in SelectionBlueprints.AliveChildren) { if (!blueprint.IsHovered) continue; - selectedPerformed &= SelectionHandler.HandleSelectionRequested(blueprint, e); - clickSelectionBegan = true; - break; + return clickSelectionBegan = SelectionHandler.HandleSelectionRequested(blueprint, e); } - return selectedPerformed; + return false; } /// From 7169dc91735feb4250636432ee7f5a07114cd94a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 16 Nov 2020 14:06:37 +0900 Subject: [PATCH 52/62] Add extra step checking slider deletion on second click --- .../Visual/Editing/TestSceneEditorQuickDelete.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorQuickDelete.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorQuickDelete.cs index 9bcb056f25..9efd299fba 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorQuickDelete.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorQuickDelete.cs @@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.Editing InputManager.MoveMouseTo(pos); }); AddStep("hold shift", () => InputManager.PressKey(Key.ShiftLeft)); - AddStep("rightclick", () => InputManager.Click(MouseButton.Right)); + AddStep("right click", () => InputManager.Click(MouseButton.Right)); AddStep("release shift", () => InputManager.ReleaseKey(Key.ShiftLeft)); AddAssert("no hitobjects in beatmap", () => EditorBeatmap.HitObjects.Count == 0); @@ -74,10 +74,15 @@ namespace osu.Game.Tests.Visual.Editing InputManager.MoveMouseTo(pos); }); AddStep("hold shift", () => InputManager.PressKey(Key.ShiftLeft)); - AddStep("rightclick", () => InputManager.Click(MouseButton.Right)); - AddStep("release shift", () => InputManager.ReleaseKey(Key.ShiftLeft)); + AddStep("right click", () => InputManager.Click(MouseButton.Right)); AddAssert("slider has 2 points", () => slider.Path.ControlPoints.Count == 2); + + // second click should nuke the object completely. + AddStep("right click", () => InputManager.Click(MouseButton.Right)); + AddAssert("no hitobjects in beatmap", () => EditorBeatmap.HitObjects.Count == 0); + + AddStep("release shift", () => InputManager.ReleaseKey(Key.ShiftLeft)); } } } From a4c17906b402781c6041f71585e7bc3a015db0bc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 16 Nov 2020 14:52:18 +0900 Subject: [PATCH 53/62] Make comment a bit more explicit --- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index f70e7b315e..906f7382c5 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -182,7 +182,7 @@ namespace osu.Game.Rulesets.Mania case ManiaModFadeIn _: value |= LegacyMods.FadeIn; - value &= ~LegacyMods.Hidden; // due to inheritance + value &= ~LegacyMods.Hidden; // this is toggled on in the base call due to inheritance, but we don't want that. break; case ManiaModMirror _: From b20898a1ac289e552c2514b995534173d2077b99 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 16 Nov 2020 14:57:52 +0900 Subject: [PATCH 54/62] Use `dotnet tool` for InspectCode build script --- .config/dotnet-tools.json | 18 ++++++++++++++++++ build/InspectCode.cake | 23 ++++++----------------- 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 6ba6ae82c8..dd53eefd23 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -13,6 +13,24 @@ "commands": [ "dotnet-format" ] + }, + "jetbrains.resharper.globaltools": { + "version": "2020.2.4", + "commands": [ + "jb" + ] + }, + "nvika": { + "version": "2.0.0", + "commands": [ + "nvika" + ] + }, + "codefilesanity": { + "version": "15.0.0", + "commands": [ + "CodeFileSanity" + ] } } } \ No newline at end of file diff --git a/build/InspectCode.cake b/build/InspectCode.cake index c8f4f37c94..6836d9071b 100644 --- a/build/InspectCode.cake +++ b/build/InspectCode.cake @@ -1,7 +1,4 @@ #addin "nuget:?package=CodeFileSanity&version=0.0.36" -#addin "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2020.1.3" -#tool "nuget:?package=NVika.MSBuild&version=1.0.1" -var nVikaToolPath = GetFiles("./tools/NVika.MSBuild.*/tools/NVika.exe").First(); /////////////////////////////////////////////////////////////////////////////// // ARGUMENTS @@ -18,23 +15,15 @@ var desktopSlnf = rootDirectory.CombineWithFilePath("osu.Desktop.slnf"); // TASKS /////////////////////////////////////////////////////////////////////////////// -// windows only because both inspectcode and nvika depend on net45 Task("InspectCode") - .WithCriteria(IsRunningOnWindows()) .Does(() => { - InspectCode(desktopSlnf, new InspectCodeSettings { - CachesHome = "inspectcode", - OutputFile = "inspectcodereport.xml", - ArgumentCustomization = arg => { - if (AppVeyor.IsRunningOnAppVeyor) // Don't flood CI output - arg.Append("--verbosity:WARN"); - return arg; - }, - }); + var inspectcodereport = "inspectcodereport.xml"; + var cacheDir = "inspectcode"; + var verbosity = AppVeyor.IsRunningOnAppVeyor ? "WARN" : "INFO"; // Don't flood CI output - int returnCode = StartProcess(nVikaToolPath, $@"parsereport ""inspectcodereport.xml"" --treatwarningsaserrors"); - if (returnCode != 0) - throw new Exception($"inspectcode failed with return code {returnCode}"); + DotNetCoreTool(rootDirectory.FullPath, + "jb", $@"inspectcode ""{desktopSlnf}"" --output=""{inspectcodereport}"" --caches-home=""{cacheDir}"" --verbosity={verbosity}"); + DotNetCoreTool(rootDirectory.FullPath, "nvika", $@"parsereport ""{inspectcodereport}"" --treatwarningsaserrors"); }); Task("CodeFileSanity") From 9a7fdb2b7e31e0495c12e5294c89a24042f8254d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 16 Nov 2020 16:43:17 +0900 Subject: [PATCH 55/62] Move skin deletion logic to OsuGameBase to promote thread safety `CurrentSkinInfo` is used in multiple places expecting thread safety, while ItemRemoved events are explicitly mentioning they are not thread safe. As SkinManager itself doesn't have the ability to schedule to the update thread, I've just moved the logic to `OsuGameBase`. We may want to move the current skin bindable out of the manager class in the future to match things like `BeatmapManager`. Closes https://github.com/ppy/osu/issues/10837. --- osu.Game/OsuGameBase.cs | 11 +++++++++++ osu.Game/Skinning/SkinManager.cs | 10 ---------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 193f6fe61b..1147f67ad2 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -194,6 +194,17 @@ namespace osu.Game dependencies.Cache(SkinManager = new SkinManager(Storage, contextFactory, Host, Audio, new NamespacedResourceStore(Resources, "Skins/Legacy"))); dependencies.CacheAs(SkinManager); + // needs to be done here rather than inside SkinManager to ensure thread safety of CurrentSkinInfo. + SkinManager.ItemRemoved.BindValueChanged(weakRemovedInfo => + { + if (weakRemovedInfo.NewValue.TryGetTarget(out var removedInfo)) + { + // check the removed skin is not the current user choice. if it is, switch back to default. + if (removedInfo.ID == SkinManager.CurrentSkinInfo.Value.ID) + Schedule(() => SkinManager.CurrentSkinInfo.Value = SkinInfo.Default); + } + }); + dependencies.CacheAs(API ??= new APIAccess(LocalConfig)); dependencies.CacheAs(spectatorStreaming = new SpectatorStreamingClient()); diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index bef3e86a4d..9b69a1eecd 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -48,16 +48,6 @@ namespace osu.Game.Skinning this.audio = audio; this.legacyDefaultResources = legacyDefaultResources; - ItemRemoved.BindValueChanged(weakRemovedInfo => - { - if (weakRemovedInfo.NewValue.TryGetTarget(out var removedInfo)) - { - // check the removed skin is not the current user choice. if it is, switch back to default. - if (removedInfo.ID == CurrentSkinInfo.Value.ID) - CurrentSkinInfo.Value = SkinInfo.Default; - } - }); - CurrentSkinInfo.ValueChanged += skin => CurrentSkin.Value = GetSkin(skin.NewValue); CurrentSkin.ValueChanged += skin => { From 709370c69b366718596ccb6409a992c1ffacf957 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 16 Nov 2020 16:49:31 +0900 Subject: [PATCH 56/62] Move schedule call outwards --- osu.Game/OsuGameBase.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 1147f67ad2..e7b5d3304d 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -199,9 +199,12 @@ namespace osu.Game { if (weakRemovedInfo.NewValue.TryGetTarget(out var removedInfo)) { - // check the removed skin is not the current user choice. if it is, switch back to default. - if (removedInfo.ID == SkinManager.CurrentSkinInfo.Value.ID) - Schedule(() => SkinManager.CurrentSkinInfo.Value = SkinInfo.Default); + Schedule(() => + { + // check the removed skin is not the current user choice. if it is, switch back to default. + if (removedInfo.ID == SkinManager.CurrentSkinInfo.Value.ID) + SkinManager.CurrentSkinInfo.Value = SkinInfo.Default; + }); } }); From a53b5ef8b95c2d0f7d3c776025e38f7e845cb4ce Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 16 Nov 2020 19:22:08 +0900 Subject: [PATCH 57/62] Remove `--no-restore` from VSCode build tasks --- .vscode/tasks.json | 17 ----------------- .../.vscode/tasks.json | 11 ----------- .../.vscode/tasks.json | 11 ----------- osu.Game.Rulesets.Osu.Tests/.vscode/tasks.json | 11 ----------- .../.vscode/tasks.json | 11 ----------- osu.Game.Tournament.Tests/.vscode/tasks.json | 11 ----------- 6 files changed, 72 deletions(-) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index aa77d4f055..a70e5ac3a9 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -9,7 +9,6 @@ "command": "dotnet", "args": [ "build", - "--no-restore", "osu.Desktop", "-p:GenerateFullPaths=true", "-m", @@ -24,7 +23,6 @@ "command": "dotnet", "args": [ "build", - "--no-restore", "osu.Desktop", "-p:Configuration=Release", "-p:GenerateFullPaths=true", @@ -40,7 +38,6 @@ "command": "dotnet", "args": [ "build", - "--no-restore", "osu.Game.Tests", "-p:GenerateFullPaths=true", "-m", @@ -55,7 +52,6 @@ "command": "dotnet", "args": [ "build", - "--no-restore", "osu.Game.Tests", "-p:Configuration=Release", "-p:GenerateFullPaths=true", @@ -71,7 +67,6 @@ "command": "dotnet", "args": [ "build", - "--no-restore", "osu.Game.Tournament.Tests", "-p:GenerateFullPaths=true", "-m", @@ -86,7 +81,6 @@ "command": "dotnet", "args": [ "build", - "--no-restore", "osu.Game.Tournament.Tests", "-p:Configuration=Release", "-p:GenerateFullPaths=true", @@ -102,7 +96,6 @@ "command": "dotnet", "args": [ "build", - "--no-restore", "osu.Game.Benchmarks", "-p:Configuration=Release", "-p:GenerateFullPaths=true", @@ -111,16 +104,6 @@ ], "group": "build", "problemMatcher": "$msCompile" - }, - { - "label": "Restore (netcoreapp3.1)", - "type": "shell", - "command": "dotnet", - "args": [ - "restore", - "build/Desktop.proj" - ], - "problemMatcher": [] } ] } \ No newline at end of file diff --git a/osu.Game.Rulesets.Catch.Tests/.vscode/tasks.json b/osu.Game.Rulesets.Catch.Tests/.vscode/tasks.json index 2c915a31b7..d8feacc8a7 100644 --- a/osu.Game.Rulesets.Catch.Tests/.vscode/tasks.json +++ b/osu.Game.Rulesets.Catch.Tests/.vscode/tasks.json @@ -9,7 +9,6 @@ "command": "dotnet", "args": [ "build", - "--no-restore", "osu.Game.Rulesets.Catch.Tests.csproj", "-p:GenerateFullPaths=true", "-m", @@ -24,7 +23,6 @@ "command": "dotnet", "args": [ "build", - "--no-restore", "osu.Game.Rulesets.Catch.Tests.csproj", "-p:Configuration=Release", "-p:GenerateFullPaths=true", @@ -33,15 +31,6 @@ ], "group": "build", "problemMatcher": "$msCompile" - }, - { - "label": "Restore", - "type": "shell", - "command": "dotnet", - "args": [ - "restore" - ], - "problemMatcher": [] } ] } \ No newline at end of file diff --git a/osu.Game.Rulesets.Mania.Tests/.vscode/tasks.json b/osu.Game.Rulesets.Mania.Tests/.vscode/tasks.json index ca03924c70..323110b605 100644 --- a/osu.Game.Rulesets.Mania.Tests/.vscode/tasks.json +++ b/osu.Game.Rulesets.Mania.Tests/.vscode/tasks.json @@ -9,7 +9,6 @@ "command": "dotnet", "args": [ "build", - "--no-restore", "osu.Game.Rulesets.Mania.Tests.csproj", "-p:GenerateFullPaths=true", "-m", @@ -24,7 +23,6 @@ "command": "dotnet", "args": [ "build", - "--no-restore", "osu.Game.Rulesets.Mania.Tests.csproj", "-p:Configuration=Release", "-p:GenerateFullPaths=true", @@ -33,15 +31,6 @@ ], "group": "build", "problemMatcher": "$msCompile" - }, - { - "label": "Restore", - "type": "shell", - "command": "dotnet", - "args": [ - "restore" - ], - "problemMatcher": [] } ] } \ No newline at end of file diff --git a/osu.Game.Rulesets.Osu.Tests/.vscode/tasks.json b/osu.Game.Rulesets.Osu.Tests/.vscode/tasks.json index 14ffbfb4ae..590bedb8b2 100644 --- a/osu.Game.Rulesets.Osu.Tests/.vscode/tasks.json +++ b/osu.Game.Rulesets.Osu.Tests/.vscode/tasks.json @@ -9,7 +9,6 @@ "command": "dotnet", "args": [ "build", - "--no-restore", "osu.Game.Rulesets.Osu.Tests.csproj", "-p:GenerateFullPaths=true", "-m", @@ -24,7 +23,6 @@ "command": "dotnet", "args": [ "build", - "--no-restore", "osu.Game.Rulesets.Osu.Tests.csproj", "-p:Configuration=Release", "-p:GenerateFullPaths=true", @@ -33,15 +31,6 @@ ], "group": "build", "problemMatcher": "$msCompile" - }, - { - "label": "Restore", - "type": "shell", - "command": "dotnet", - "args": [ - "restore" - ], - "problemMatcher": [] } ] } \ No newline at end of file diff --git a/osu.Game.Rulesets.Taiko.Tests/.vscode/tasks.json b/osu.Game.Rulesets.Taiko.Tests/.vscode/tasks.json index 09340f6f9f..63f25c2402 100644 --- a/osu.Game.Rulesets.Taiko.Tests/.vscode/tasks.json +++ b/osu.Game.Rulesets.Taiko.Tests/.vscode/tasks.json @@ -9,7 +9,6 @@ "command": "dotnet", "args": [ "build", - "--no-restore", "osu.Game.Rulesets.Taiko.Tests.csproj", "-p:GenerateFullPaths=true", "-m", @@ -24,7 +23,6 @@ "command": "dotnet", "args": [ "build", - "--no-restore", "osu.Game.Rulesets.Taiko.Tests.csproj", "-p:Configuration=Release", "-p:GenerateFullPaths=true", @@ -33,15 +31,6 @@ ], "group": "build", "problemMatcher": "$msCompile" - }, - { - "label": "Restore", - "type": "shell", - "command": "dotnet", - "args": [ - "restore" - ], - "problemMatcher": [] } ] } \ No newline at end of file diff --git a/osu.Game.Tournament.Tests/.vscode/tasks.json b/osu.Game.Tournament.Tests/.vscode/tasks.json index c69ac0391a..04ec7275ac 100644 --- a/osu.Game.Tournament.Tests/.vscode/tasks.json +++ b/osu.Game.Tournament.Tests/.vscode/tasks.json @@ -9,7 +9,6 @@ "command": "dotnet", "args": [ "build", - "--no-restore", "osu.Game.Tournament.Tests.csproj", "-p:GenerateFullPaths=true", "-m", @@ -24,7 +23,6 @@ "command": "dotnet", "args": [ "build", - "--no-restore", "osu.Game.Tournament.Tests.csproj", "-p:Configuration=Release", "-p:GenerateFullPaths=true", @@ -33,15 +31,6 @@ ], "group": "build", "problemMatcher": "$msCompile" - }, - { - "label": "Restore", - "type": "shell", - "command": "dotnet", - "args": [ - "restore" - ], - "problemMatcher": [] } ] } \ No newline at end of file From 16d25c502204c74eca2e6b21b18ec3ca14e9004b Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 16 Nov 2020 19:25:36 +0900 Subject: [PATCH 58/62] Adjast readme for the removed VSCode restore task --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 86c42dae12..c9443ba063 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,6 @@ git pull Build configurations for the recommended IDEs (listed above) are included. You should use the provided Build/Run functionality of your IDE to get things going. When testing or building new components, it's highly encouraged you use the `VisualTests` project/configuration. More information on this is provided [below](#contributing). - Visual Studio / Rider users should load the project via one of the platform-specific `.slnf` files, rather than the main `.sln.` This will allow access to template run configurations. -- Visual Studio Code users must run the `Restore` task before any build attempt. You can also build and run *osu!* from the command-line with a single command: From cead67d51209a1f3a1869448d15345b9d78577f5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 16 Nov 2020 21:47:33 +0900 Subject: [PATCH 59/62] Add back removed InitialLifetimeOffset removal --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 3b7c8bcc2a..ca49ed9e75 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -201,6 +201,8 @@ namespace osu.Game.Rulesets.Objects.Drawables // Copy any existing result from the entry (required for rewind / judgement revert). Result = lifetimeEntry.Result; } + else + LifetimeStart = HitObject.StartTime - InitialLifetimeOffset; // Ensure this DHO has a result. Result ??= CreateResult(HitObject.CreateJudgement()) @@ -646,6 +648,10 @@ namespace osu.Game.Rulesets.Objects.Drawables /// This is only used as an optimisation to delay the initial update of this and may be tuned more aggressively if required. /// It is indirectly used to decide the automatic transform offset provided to . /// A more accurate should be set for further optimisation (in , for example). + /// + /// Only has an effect if this is not being pooled. + /// For pooled s, use instead. + /// /// protected virtual double InitialLifetimeOffset => 10000; From 4cf6aca8735c43c36d1a9ed90660b0600a591942 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 16 Nov 2020 22:40:25 +0900 Subject: [PATCH 60/62] Fix slider ball tint not working --- .../TestSceneSliderApplication.cs | 45 +++++++++++++++++++ .../Objects/Drawables/DrawableSlider.cs | 11 ++++- 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs index fb1ebbb0d0..084af7dafe 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs @@ -1,20 +1,30 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; +using osu.Game.Skinning; using osu.Game.Tests.Visual; using osuTK; +using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Tests { public class TestSceneSliderApplication : OsuTestScene { + [Resolved] + private SkinManager skinManager { get; set; } + [Test] public void TestApplyNewSlider() { @@ -50,6 +60,41 @@ namespace osu.Game.Rulesets.Osu.Tests }), null)); } + [Test] + public void TestBallTintChangedOnAccentChange() + { + DrawableSlider dho = null; + + AddStep("create slider", () => + { + var tintingSkin = skinManager.GetSkin(DefaultLegacySkin.Info); + tintingSkin.Configuration.ConfigDictionary["AllowSliderBallTint"] = "1"; + + Child = new SkinProvidingContainer(tintingSkin) + { + RelativeSizeAxes = Axes.Both, + Child = dho = new DrawableSlider(prepareObject(new Slider + { + Position = new Vector2(256, 192), + IndexInCurrentCombo = 0, + StartTime = Time.Current, + Path = new SliderPath(PathType.Linear, new[] + { + Vector2.Zero, + new Vector2(150, 100), + new Vector2(300, 0), + }) + })) + }; + }); + + AddStep("set accent white", () => dho.AccentColour.Value = Color4.White); + AddAssert("ball is white", () => dho.ChildrenOfType().Single().AccentColour == Color4.White); + + AddStep("set accent red", () => dho.AccentColour.Value = Color4.Red); + AddAssert("ball is red", () => dho.ChildrenOfType().Single().AccentColour == Color4.Red); + } + private Slider prepareObject(Slider slider) { slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 04fc755da5..f7b1894058 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -80,6 +80,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { foreach (var drawableHitObject in NestedHitObjects) drawableHitObject.AccentColour.Value = colour.NewValue; + updateBallTint(); }, true); Tracking.BindValueChanged(updateSlidingSample); @@ -244,7 +245,15 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { base.ApplySkin(skin, allowFallback); - bool allowBallTint = skin.GetConfig(OsuSkinConfiguration.AllowSliderBallTint)?.Value ?? false; + updateBallTint(); + } + + private void updateBallTint() + { + if (CurrentSkin == null) + return; + + bool allowBallTint = CurrentSkin.GetConfig(OsuSkinConfiguration.AllowSliderBallTint)?.Value ?? false; Ball.AccentColour = allowBallTint ? AccentColour.Value : Color4.White; } From e8dbc190f1ce8584980eb25486c04a786d6e218f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 16 Nov 2020 23:30:24 +0900 Subject: [PATCH 61/62] Remove ability to pool DHOs in parent playfields --- osu.Game/Rulesets/UI/Playfield.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index 80e33e0ec5..82ec653f31 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -218,9 +218,6 @@ namespace osu.Game.Rulesets.UI #region Pooling support - [Resolved(CanBeNull = true)] - private IPooledHitObjectProvider parentPooledObjectProvider { get; set; } - private readonly Dictionary pools = new Dictionary(); /// @@ -320,10 +317,7 @@ namespace osu.Game.Rulesets.UI } } - if (pool == null) - return parentPooledObjectProvider?.GetPooledDrawableRepresentation(hitObject); - - return (DrawableHitObject)pool.Get(d => + return (DrawableHitObject)pool?.Get(d => { var dho = (DrawableHitObject)d; From f5e12b9d7c0828efdd527706216fbee7efa02375 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 16 Nov 2020 23:53:54 +0900 Subject: [PATCH 62/62] Adjust TestScenePlayerLoader for safety --- .../Visual/Gameplay/TestScenePlayerLoader.cs | 127 ++++++++---------- 1 file changed, 53 insertions(+), 74 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index 9b31dd045a..88fbf09ef4 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -23,17 +23,15 @@ using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; -using osu.Game.Screens; using osu.Game.Screens.Play; using osu.Game.Screens.Play.PlayerSettings; using osuTK.Input; namespace osu.Game.Tests.Visual.Gameplay { - public class TestScenePlayerLoader : OsuManualInputManagerTestScene + public class TestScenePlayerLoader : ScreenTestScene { private TestPlayerLoader loader; - private TestPlayerLoaderContainer container; private TestPlayer player; private bool epilepsyWarning; @@ -44,21 +42,46 @@ namespace osu.Game.Tests.Visual.Gameplay [Resolved] private SessionStatics sessionStatics { get; set; } + [Cached] + private readonly NotificationOverlay notificationOverlay; + + [Cached] + private readonly VolumeOverlay volumeOverlay; + + private readonly ChangelogOverlay changelogOverlay; + + public TestScenePlayerLoader() + { + AddRange(new Drawable[] + { + notificationOverlay = new NotificationOverlay + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + }, + volumeOverlay = new VolumeOverlay + { + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, + }, + changelogOverlay = new ChangelogOverlay() + }); + } + + [SetUp] + public void Setup() => Schedule(() => + { + player = null; + audioManager.Volume.SetDefault(); + }); + /// /// Sets the input manager child to a new test player loader container instance. /// /// If the test player should behave like the production one. /// An action to run before player load but after bindable leases are returned. - public void ResetPlayer(bool interactive, Action beforeLoadAction = null) + private void resetPlayer(bool interactive, Action beforeLoadAction = null) { - player = null; - - audioManager.Volume.SetDefault(); - - InputManager.Clear(); - - container = new TestPlayerLoaderContainer(loader = new TestPlayerLoader(() => player = new TestPlayer(interactive, interactive))); - beforeLoadAction?.Invoke(); Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); @@ -67,13 +90,13 @@ namespace osu.Game.Tests.Visual.Gameplay foreach (var mod in SelectedMods.Value.OfType()) mod.ApplyToTrack(Beatmap.Value.Track); - InputManager.Child = container; + LoadScreen(loader = new TestPlayerLoader(() => player = new TestPlayer(interactive, interactive))); } [Test] public void TestEarlyExitBeforePlayerConstruction() { - AddStep("load dummy beatmap", () => ResetPlayer(false, () => SelectedMods.Value = new[] { new OsuModNightcore() })); + AddStep("load dummy beatmap", () => resetPlayer(false, () => SelectedMods.Value = new[] { new OsuModNightcore() })); AddUntilStep("wait for current", () => loader.IsCurrentScreen()); AddStep("exit loader", () => loader.Exit()); AddUntilStep("wait for not current", () => !loader.IsCurrentScreen()); @@ -90,7 +113,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestEarlyExitAfterPlayerConstruction() { - AddStep("load dummy beatmap", () => ResetPlayer(false, () => SelectedMods.Value = new[] { new OsuModNightcore() })); + AddStep("load dummy beatmap", () => resetPlayer(false, () => SelectedMods.Value = new[] { new OsuModNightcore() })); AddUntilStep("wait for current", () => loader.IsCurrentScreen()); AddAssert("mod rate applied", () => Beatmap.Value.Track.Rate != 1); AddUntilStep("wait for non-null player", () => player != null); @@ -104,7 +127,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestBlockLoadViaMouseMovement() { - AddStep("load dummy beatmap", () => ResetPlayer(false)); + AddStep("load dummy beatmap", () => resetPlayer(false)); AddUntilStep("wait for current", () => loader.IsCurrentScreen()); AddUntilStep("wait for load ready", () => @@ -129,20 +152,18 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestBlockLoadViaFocus() { - OsuFocusedOverlayContainer overlay = null; - - AddStep("load dummy beatmap", () => ResetPlayer(false)); + AddStep("load dummy beatmap", () => resetPlayer(false)); AddUntilStep("wait for current", () => loader.IsCurrentScreen()); - AddStep("show focused overlay", () => { container.Add(overlay = new ChangelogOverlay { State = { Value = Visibility.Visible } }); }); - AddUntilStep("overlay visible", () => overlay.IsPresent); + AddStep("show focused overlay", () => changelogOverlay.Show()); + AddUntilStep("overlay visible", () => changelogOverlay.IsPresent); - AddUntilStep("wait for load ready", () => player.LoadState == LoadState.Ready); + AddUntilStep("wait for load ready", () => player?.LoadState == LoadState.Ready); AddRepeatStep("twiddle thumbs", () => { }, 20); AddAssert("loader still active", () => loader.IsCurrentScreen()); - AddStep("hide overlay", () => overlay.Hide()); + AddStep("hide overlay", () => changelogOverlay.Hide()); AddUntilStep("loads after idle", () => !loader.IsCurrentScreen()); } @@ -151,15 +172,9 @@ namespace osu.Game.Tests.Visual.Gameplay { SlowLoadPlayer slowPlayer = null; - AddStep("load dummy beatmap", () => ResetPlayer(false)); - AddUntilStep("wait for current", () => loader.IsCurrentScreen()); - AddStep("mouse in centre", () => InputManager.MoveMouseTo(loader.ScreenSpaceDrawQuad.Centre)); - AddUntilStep("wait for player to be current", () => player.IsCurrentScreen()); AddStep("load slow dummy beatmap", () => { - InputManager.Child = container = new TestPlayerLoaderContainer( - loader = new TestPlayerLoader(() => slowPlayer = new SlowLoadPlayer(false, false))); - + LoadScreen(loader = new TestPlayerLoader(() => slowPlayer = new SlowLoadPlayer(false, false))); Scheduler.AddDelayed(() => slowPlayer.AllowLoad.Set(), 5000); }); @@ -173,7 +188,7 @@ namespace osu.Game.Tests.Visual.Gameplay TestMod playerMod1 = null; TestMod playerMod2 = null; - AddStep("load player", () => { ResetPlayer(true, () => SelectedMods.Value = new[] { gameMod = new TestMod() }); }); + AddStep("load player", () => { resetPlayer(true, () => SelectedMods.Value = new[] { gameMod = new TestMod() }); }); AddUntilStep("wait for loader to become current", () => loader.IsCurrentScreen()); AddStep("mouse in centre", () => InputManager.MoveMouseTo(loader.ScreenSpaceDrawQuad.Centre)); @@ -201,7 +216,7 @@ namespace osu.Game.Tests.Visual.Gameplay { var testMod = new TestMod(); - AddStep("load player", () => ResetPlayer(true)); + AddStep("load player", () => resetPlayer(true)); AddUntilStep("wait for loader to become current", () => loader.IsCurrentScreen()); AddStep("set test mod in loader", () => loader.Mods.Value = new[] { testMod }); @@ -223,7 +238,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestMutedNotificationMuteButton() { - addVolumeSteps("mute button", () => container.VolumeOverlay.IsMuted.Value = true, () => !container.VolumeOverlay.IsMuted.Value); + addVolumeSteps("mute button", () => volumeOverlay.IsMuted.Value = true, () => !volumeOverlay.IsMuted.Value); } /// @@ -236,13 +251,13 @@ namespace osu.Game.Tests.Visual.Gameplay { AddStep("reset notification lock", () => sessionStatics.GetBindable(Static.MutedAudioNotificationShownOnce).Value = false); - AddStep("load player", () => ResetPlayer(false, beforeLoad)); + AddStep("load player", () => resetPlayer(false, beforeLoad)); AddUntilStep("wait for player", () => player?.LoadState == LoadState.Ready); - AddAssert("check for notification", () => container.NotificationOverlay.UnreadCount.Value == 1); + AddAssert("check for notification", () => notificationOverlay.UnreadCount.Value == 1); AddStep("click notification", () => { - var scrollContainer = (OsuScrollContainer)container.NotificationOverlay.Children.Last(); + var scrollContainer = (OsuScrollContainer)notificationOverlay.Children.Last(); var flowContainer = scrollContainer.Children.OfType>().First(); var notification = flowContainer.First(); @@ -260,7 +275,7 @@ namespace osu.Game.Tests.Visual.Gameplay public void TestEpilepsyWarning(bool warning) { AddStep("change epilepsy warning", () => epilepsyWarning = warning); - AddStep("load dummy beatmap", () => ResetPlayer(false)); + AddStep("load dummy beatmap", () => resetPlayer(false)); AddUntilStep("wait for current", () => loader.IsCurrentScreen()); @@ -277,7 +292,7 @@ namespace osu.Game.Tests.Visual.Gameplay public void TestEpilepsyWarningEarlyExit() { AddStep("set epilepsy warning", () => epilepsyWarning = true); - AddStep("load dummy beatmap", () => ResetPlayer(false)); + AddStep("load dummy beatmap", () => resetPlayer(false)); AddUntilStep("wait for current", () => loader.IsCurrentScreen()); @@ -287,42 +302,6 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("sound volume restored", () => Beatmap.Value.Track.AggregateVolume.Value == 1); } - private class TestPlayerLoaderContainer : Container - { - [Cached] - public readonly NotificationOverlay NotificationOverlay; - - [Cached] - public readonly VolumeOverlay VolumeOverlay; - - public TestPlayerLoaderContainer(IScreen screen) - { - RelativeSizeAxes = Axes.Both; - - OsuScreenStack stack; - - InternalChildren = new Drawable[] - { - stack = new OsuScreenStack - { - RelativeSizeAxes = Axes.Both, - }, - NotificationOverlay = new NotificationOverlay - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - }, - VolumeOverlay = new VolumeOverlay - { - Anchor = Anchor.TopLeft, - Origin = Anchor.TopLeft, - } - }; - - stack.Push(screen); - } - } - private class TestPlayerLoader : PlayerLoader { public new VisualSettings VisualSettings => base.VisualSettings;