diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs index 70afc1e308..f1edb7dc7e 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Specialized; using System.Linq; using osu.Framework.Bindables; @@ -31,7 +32,16 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts case NotifyCollectionChangedAction.Add: foreach (var group in args.NewItems.OfType()) + { + // as an optimisation, don't add a visualisation if there are already groups with the same types in close proximity. + // for newly added control points (ie. lazer editor first where group is added empty) we always skip for simplicity. + // that is fine, because cases where this is causing a performance issue are mostly where external tools were used to create an insane number of points. + if (Children.Any(g => Math.Abs(g.Group.Time - group.Time) < 500 && g.IsVisuallyRedundant(group))) + continue; + Add(new GroupVisualisation(group)); + } + break; case NotifyCollectionChangedAction.Remove: @@ -39,7 +49,20 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts { var matching = Children.SingleOrDefault(gv => gv.Group == group); - matching?.Expire(); + if (matching != null) + matching.Expire(); + else + { + // due to the add optimisation above, if a point is deleted which wasn't being displayed we need to recreate all points + // to guarantee an accurate representation. + // + // note that the case where control point (type) is added or removed from a non-displayed group is not handled correctly. + // this is an edge case which shouldn't affect the user too badly. we may flatten control point groups in the future + // which would allow this to be handled better. + Clear(); + foreach (var g in controlPointGroups) + Add(new GroupVisualisation(g)); + } } break; diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointVisualisation.cs index a8e41d220a..41716f9c23 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointVisualisation.cs @@ -9,7 +9,7 @@ using osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations; namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts { - public class ControlPointVisualisation : PointVisualisation + public class ControlPointVisualisation : PointVisualisation, IControlPointVisualisation { protected readonly ControlPoint Point; @@ -26,5 +26,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts { Colour = Point.GetRepresentingColour(colours); } + + public bool IsVisuallyRedundant(ControlPoint other) => other.GetType() == Point.GetType(); } } diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs index 801372305b..7c14152b3d 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs @@ -13,7 +13,7 @@ using osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations; namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts { - public class EffectPointVisualisation : CompositeDrawable + public class EffectPointVisualisation : CompositeDrawable, IControlPointVisualisation { private readonly EffectControlPoint effect; private Bindable kiai; @@ -68,5 +68,8 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts } }, true); } + + // kiai sections display duration, so are required to be visualised. + public bool IsVisuallyRedundant(ControlPoint other) => other is EffectControlPoint otherEffect && effect.KiaiMode == otherEffect.KiaiMode; } } diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs index f0e643f805..88587399f2 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -23,12 +24,8 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts Group = group; X = (float)group.Time; - } - - protected override void LoadComplete() - { - base.LoadComplete(); + // Run in constructor so IsRedundant calls can work correctly. controlPoints.BindTo(Group.ControlPoints); controlPoints.BindCollectionChanged((_, __) => { @@ -60,5 +57,11 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts } }, true); } + + /// + /// For display purposes, check whether the proposed group is made redundant by this visualisation group. + /// + public bool IsVisuallyRedundant(ControlPointGroup other) => + other.ControlPoints.All(c => InternalChildren.OfType().Any(c2 => c2.IsVisuallyRedundant(c))); } } diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/IControlPointVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/IControlPointVisualisation.cs new file mode 100644 index 0000000000..c81f1828f7 --- /dev/null +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/IControlPointVisualisation.cs @@ -0,0 +1,15 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Beatmaps.ControlPoints; + +namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts +{ + public interface IControlPointVisualisation + { + /// + /// For display purposes, check whether the proposed point is made redundant by this visualisation. + /// + bool IsVisuallyRedundant(ControlPoint other); + } +}