From 39d37c4779b82c1069a1cc89b4d0342ceb34b686 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 12 Nov 2020 15:24:45 +0900 Subject: [PATCH 01/15] 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/15] 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/15] 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/15] 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/15] 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/15] 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/15] 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/15] 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/15] 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/15] 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/15] 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 5b60f32c7f96bc1b17fff30bf875c7e8f6acaf6c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Sat, 14 Nov 2020 01:03:23 +0900 Subject: [PATCH 12/15] 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 90f37ff4ab651281059a5f8bdf2bc41cf6c69a1a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 14 Nov 2020 18:04:59 +0900 Subject: [PATCH 13/15] 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 14/15] 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 15/15] 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 _: