From 589c3a47e2e4cd7432f1dbd5c50725dc0c493ecf Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 30 Oct 2018 18:00:55 +0900 Subject: [PATCH] Remove state computation + updates from ISpeedChangeVisualiser --- .../Scrolling/ScrollingHitObjectContainer.cs | 101 ++++++++++++++---- .../ConstantSpeedChangeVisualiser.cs | 65 ++--------- .../Visualisers/ISpeedChangeVisualiser.cs | 23 ---- .../OverlappingSpeedChangeVisualiser.cs | 71 ++---------- .../SequentialSpeedChangeVisualiser.cs | 78 +++----------- 5 files changed, 110 insertions(+), 228 deletions(-) diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index 52b4072523..31b9c22a2e 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Lists; using osu.Game.Configuration; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Timing; using osu.Game.Rulesets.UI.Scrolling.Visualisers; @@ -29,30 +30,19 @@ namespace osu.Game.Rulesets.UI.Scrolling protected readonly SortedList ControlPoints = new SortedList(); public readonly Bindable Direction = new Bindable(); + private readonly SpeedChangeVisualisationMethod visualisationMethod; private Cached initialStateCache = new Cached(); - - private readonly ISpeedChangeVisualiser speedChangeVisualiser; + private ISpeedChangeVisualiser visualiser; public ScrollingHitObjectContainer(SpeedChangeVisualisationMethod visualisationMethod) { + this.visualisationMethod = visualisationMethod; + RelativeSizeAxes = Axes.Both; TimeRange.ValueChanged += _ => initialStateCache.Invalidate(); Direction.ValueChanged += _ => initialStateCache.Invalidate(); - - switch (visualisationMethod) - { - case SpeedChangeVisualisationMethod.Sequential: - speedChangeVisualiser = new SequentialSpeedChangeVisualiser(ControlPoints); - break; - case SpeedChangeVisualisationMethod.Overlapping: - speedChangeVisualiser = new OverlappingSpeedChangeVisualiser(ControlPoints); - break; - case SpeedChangeVisualisationMethod.Constant: - speedChangeVisualiser = new ConstantSpeedChangeVisualiser(); - break; - } } public override void Add(DrawableHitObject hitObject) @@ -95,23 +85,68 @@ namespace osu.Game.Rulesets.UI.Scrolling { base.Update(); - speedChangeVisualiser.TimeRange = TimeRange.Value; + if (!initialStateCache.IsValid) + { + visualiser = createVisualiser(); + + foreach (var obj in Objects) + computeInitialStateRecursive(obj); + initialStateCache.Validate(); + } + } + + private ISpeedChangeVisualiser createVisualiser() + { + float scrollLength; switch (Direction.Value) { case ScrollingDirection.Up: case ScrollingDirection.Down: - speedChangeVisualiser.ScrollLength = DrawSize.Y; + scrollLength = DrawSize.Y; break; default: - speedChangeVisualiser.ScrollLength = DrawSize.X; + scrollLength = DrawSize.X; break; } - if (!initialStateCache.IsValid) + switch (visualisationMethod) { - speedChangeVisualiser.ComputeInitialStates(Objects, Direction); - initialStateCache.Validate(); + default: + case SpeedChangeVisualisationMethod.Constant: + return new ConstantSpeedChangeVisualiser(TimeRange, scrollLength); + case SpeedChangeVisualisationMethod.Overlapping: + return new OverlappingSpeedChangeVisualiser(ControlPoints, TimeRange, scrollLength); + case SpeedChangeVisualisationMethod.Sequential: + return new SequentialSpeedChangeVisualiser(ControlPoints, TimeRange, scrollLength); + } + } + + private void computeInitialStateRecursive(DrawableHitObject hitObject) + { + hitObject.LifetimeStart = visualiser.GetDisplayStartTime(hitObject.HitObject.StartTime); + + if (hitObject.HitObject is IHasEndTime endTime) + { + switch (Direction.Value) + { + case ScrollingDirection.Up: + case ScrollingDirection.Down: + hitObject.Height = visualiser.GetLength(hitObject.HitObject.StartTime, endTime.EndTime); + break; + case ScrollingDirection.Left: + case ScrollingDirection.Right: + hitObject.Height = visualiser.GetLength(hitObject.HitObject.StartTime, endTime.EndTime); + break; + } + } + + foreach (var obj in hitObject.NestedHitObjects) + { + computeInitialStateRecursive(obj); + + // Nested hitobjects don't need to scroll, but they do need accurate positions + updatePosition(obj, hitObject.HitObject.StartTime); } } @@ -119,8 +154,28 @@ namespace osu.Game.Rulesets.UI.Scrolling { base.UpdateAfterChildrenLife(); - // We need to calculate this as soon as possible after lifetimes so that hitobjects get the final say in their positions - speedChangeVisualiser.UpdatePositions(AliveObjects, Direction, Time.Current); + // We need to calculate hitobject positions as soon as possible after lifetimes so that hitobjects get the final say in their positions + foreach (var obj in AliveObjects) + updatePosition(obj, Time.Current); + } + + private void updatePosition(DrawableHitObject hitObject, double currentTime) + { + switch (Direction.Value) + { + case ScrollingDirection.Up: + hitObject.Y = visualiser.PositionAt(currentTime, hitObject.HitObject.StartTime); + break; + case ScrollingDirection.Down: + hitObject.Y = -visualiser.PositionAt(currentTime, hitObject.HitObject.StartTime); + break; + case ScrollingDirection.Left: + hitObject.X = visualiser.PositionAt(currentTime, hitObject.HitObject.StartTime); + break; + case ScrollingDirection.Right: + hitObject.X = -visualiser.PositionAt(currentTime, hitObject.HitObject.StartTime); + break; + } } } } diff --git a/osu.Game/Rulesets/UI/Scrolling/Visualisers/ConstantSpeedChangeVisualiser.cs b/osu.Game/Rulesets/UI/Scrolling/Visualisers/ConstantSpeedChangeVisualiser.cs index f4417e393a..1d3020ec8b 100644 --- a/osu.Game/Rulesets/UI/Scrolling/Visualisers/ConstantSpeedChangeVisualiser.cs +++ b/osu.Game/Rulesets/UI/Scrolling/Visualisers/ConstantSpeedChangeVisualiser.cs @@ -1,69 +1,20 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using System.Collections.Generic; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Objects.Types; - namespace osu.Game.Rulesets.UI.Scrolling.Visualisers { - public class ConstantSpeedChangeVisualiser : ISpeedChangeVisualiser + public readonly struct ConstantSpeedChangeVisualiser : ISpeedChangeVisualiser { - public double TimeRange { get; set; } + private readonly double timeRange; + private readonly float scrollLength; - public float ScrollLength { get; set; } - - public void ComputeInitialStates(IEnumerable hitObjects, ScrollingDirection direction) + public ConstantSpeedChangeVisualiser(double timeRange, float scrollLength) { - foreach (var obj in hitObjects) - { - obj.LifetimeStart = GetDisplayStartTime(obj.HitObject.StartTime); - - if (obj.HitObject is IHasEndTime endTime) - { - switch (direction) - { - case ScrollingDirection.Up: - case ScrollingDirection.Down: - obj.Height = GetLength(obj.HitObject.StartTime, endTime.EndTime); - break; - case ScrollingDirection.Left: - case ScrollingDirection.Right: - obj.Height = GetLength(obj.HitObject.StartTime, endTime.EndTime); - break; - } - } - - ComputeInitialStates(obj.NestedHitObjects, direction); - - // Nested hitobjects don't need to scroll, but they do need accurate positions - UpdatePositions(obj.NestedHitObjects, direction, obj.HitObject.StartTime); - } + this.timeRange = timeRange; + this.scrollLength = scrollLength; } - public void UpdatePositions(IEnumerable hitObjects, ScrollingDirection direction, double currentTime) - { - foreach (var obj in hitObjects) - { - switch (direction) - { - case ScrollingDirection.Up: - obj.Y = PositionAt(currentTime, obj.HitObject.StartTime); - break; - case ScrollingDirection.Down: - obj.Y = -PositionAt(currentTime, obj.HitObject.StartTime); - break; - case ScrollingDirection.Left: - obj.X = PositionAt(currentTime, obj.HitObject.StartTime); - break; - case ScrollingDirection.Right: - obj.X = -PositionAt(currentTime, obj.HitObject.StartTime); - break; - } - } - } - - public double GetDisplayStartTime(double startTime) => startTime - TimeRange; + public double GetDisplayStartTime(double startTime) => startTime - timeRange; public float GetLength(double startTime, double endTime) { @@ -72,6 +23,6 @@ namespace osu.Game.Rulesets.UI.Scrolling.Visualisers return -PositionAt(endTime, startTime); } - public float PositionAt(double currentTime, double startTime) => (float)((startTime - currentTime) / TimeRange * ScrollLength); + public float PositionAt(double currentTime, double startTime) => (float)((startTime - currentTime) / timeRange * scrollLength); } } diff --git a/osu.Game/Rulesets/UI/Scrolling/Visualisers/ISpeedChangeVisualiser.cs b/osu.Game/Rulesets/UI/Scrolling/Visualisers/ISpeedChangeVisualiser.cs index b7d611df50..478c10c6ce 100644 --- a/osu.Game/Rulesets/UI/Scrolling/Visualisers/ISpeedChangeVisualiser.cs +++ b/osu.Game/Rulesets/UI/Scrolling/Visualisers/ISpeedChangeVisualiser.cs @@ -1,33 +1,10 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using System.Collections.Generic; -using osu.Game.Rulesets.Objects.Drawables; - namespace osu.Game.Rulesets.UI.Scrolling.Visualisers { public interface ISpeedChangeVisualiser { - double TimeRange { get; set; } - - float ScrollLength { get; set; } - - /// - /// Computes the states of s that remain constant while scrolling, such as lifetime and spatial length. - /// This is invoked once whenever or changes. - /// - /// The s whose states should be computed. - /// The scrolling direction. - void ComputeInitialStates(IEnumerable hitObjects, ScrollingDirection direction); - - /// - /// Updates the positions of s, depending on the current time. This is invoked once per frame. - /// - /// The s whose positions should be computed. - /// The scrolling direction. - /// The current time. - void UpdatePositions(IEnumerable hitObjects, ScrollingDirection direction, double currentTime); - double GetDisplayStartTime(double startTime); float GetLength(double startTime, double endTime); diff --git a/osu.Game/Rulesets/UI/Scrolling/Visualisers/OverlappingSpeedChangeVisualiser.cs b/osu.Game/Rulesets/UI/Scrolling/Visualisers/OverlappingSpeedChangeVisualiser.cs index f6fbe9063f..646ea0c280 100644 --- a/osu.Game/Rulesets/UI/Scrolling/Visualisers/OverlappingSpeedChangeVisualiser.cs +++ b/osu.Game/Rulesets/UI/Scrolling/Visualisers/OverlappingSpeedChangeVisualiser.cs @@ -1,81 +1,32 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using System.Collections.Generic; using osu.Framework.Lists; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Timing; namespace osu.Game.Rulesets.UI.Scrolling.Visualisers { - public class OverlappingSpeedChangeVisualiser : ISpeedChangeVisualiser + public readonly struct OverlappingSpeedChangeVisualiser : ISpeedChangeVisualiser { - public double TimeRange { get; set; } - - public float ScrollLength { get; set; } + private readonly MultiplierControlPoint searchPoint; private readonly SortedList controlPoints; + private readonly double timeRange; + private readonly float scrollLength; - public OverlappingSpeedChangeVisualiser(SortedList controlPoints) + public OverlappingSpeedChangeVisualiser(SortedList controlPoints, double timeRange, float scrollLength) { this.controlPoints = controlPoints; - } + this.timeRange = timeRange; + this.scrollLength = scrollLength; - public void ComputeInitialStates(IEnumerable hitObjects, ScrollingDirection direction) - { - foreach (var obj in hitObjects) - { - obj.LifetimeStart = GetDisplayStartTime(obj.HitObject.StartTime); - - if (obj.HitObject is IHasEndTime endTime) - { - switch (direction) - { - case ScrollingDirection.Up: - case ScrollingDirection.Down: - obj.Height = GetLength(obj.HitObject.StartTime, endTime.EndTime); - break; - case ScrollingDirection.Left: - case ScrollingDirection.Right: - obj.Width = GetLength(obj.HitObject.StartTime, endTime.EndTime); - break; - } - } - - ComputeInitialStates(obj.NestedHitObjects, direction); - - // Nested hitobjects don't need to scroll, but they do need accurate positions - UpdatePositions(obj.NestedHitObjects, direction, obj.HitObject.StartTime); - } - } - - public void UpdatePositions(IEnumerable hitObjects, ScrollingDirection direction, double currentTime) - { - foreach (var obj in hitObjects) - { - switch (direction) - { - case ScrollingDirection.Up: - obj.Y = PositionAt(currentTime, obj.HitObject.StartTime); - break; - case ScrollingDirection.Down: - obj.Y = -PositionAt(currentTime, obj.HitObject.StartTime); - break; - case ScrollingDirection.Left: - obj.X = PositionAt(currentTime, obj.HitObject.StartTime); - break; - case ScrollingDirection.Right: - obj.X = -PositionAt(currentTime, obj.HitObject.StartTime); - break; - } - } + searchPoint = new MultiplierControlPoint(); } public double GetDisplayStartTime(double startTime) { // The total amount of time that the hitobject will remain visible within the timeRange, which decreases as the speed multiplier increases - double visibleDuration = TimeRange / controlPointAt(startTime).Multiplier; + double visibleDuration = timeRange / controlPointAt(startTime).Multiplier; return startTime - visibleDuration; } @@ -87,9 +38,7 @@ namespace osu.Game.Rulesets.UI.Scrolling.Visualisers } public float PositionAt(double currentTime, double startTime) - => (float)((startTime - currentTime) / TimeRange * controlPointAt(startTime).Multiplier * ScrollLength); - - private readonly MultiplierControlPoint searchPoint = new MultiplierControlPoint(); + => (float)((startTime - currentTime) / timeRange * controlPointAt(startTime).Multiplier * scrollLength); /// /// Finds the which affects the speed of hitobjects at a specific time. diff --git a/osu.Game/Rulesets/UI/Scrolling/Visualisers/SequentialSpeedChangeVisualiser.cs b/osu.Game/Rulesets/UI/Scrolling/Visualisers/SequentialSpeedChangeVisualiser.cs index 7b2471e674..9e8099fdb5 100644 --- a/osu.Game/Rulesets/UI/Scrolling/Visualisers/SequentialSpeedChangeVisualiser.cs +++ b/osu.Game/Rulesets/UI/Scrolling/Visualisers/SequentialSpeedChangeVisualiser.cs @@ -3,90 +3,40 @@ using System; using System.Collections.Generic; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Timing; namespace osu.Game.Rulesets.UI.Scrolling.Visualisers { - public class SequentialSpeedChangeVisualiser : ISpeedChangeVisualiser + public readonly struct SequentialSpeedChangeVisualiser : ISpeedChangeVisualiser { - public double TimeRange { get; set; } - - public float ScrollLength { get; set; } - - private readonly Dictionary positionCache = new Dictionary(); + private readonly Dictionary positionCache; private readonly IReadOnlyList controlPoints; + private readonly double timeRange; + private readonly float scrollLength; - public SequentialSpeedChangeVisualiser(IReadOnlyList controlPoints) + public SequentialSpeedChangeVisualiser(IReadOnlyList controlPoints, double timeRange, float scrollLength) { this.controlPoints = controlPoints; + this.timeRange = timeRange; + this.scrollLength = scrollLength; + + positionCache = new Dictionary(); } - public void ComputeInitialStates(IEnumerable hitObjects, ScrollingDirection direction) - { - foreach (var obj in hitObjects) - { - obj.LifetimeStart = GetDisplayStartTime(obj.HitObject.StartTime); - - if (obj.HitObject is IHasEndTime endTime) - { - switch (direction) - { - case ScrollingDirection.Up: - case ScrollingDirection.Down: - obj.Height = GetLength(obj.HitObject.StartTime, endTime.EndTime); - break; - case ScrollingDirection.Left: - case ScrollingDirection.Right: - obj.Width = GetLength(obj.HitObject.StartTime, endTime.EndTime); - break; - } - } - - ComputeInitialStates(obj.NestedHitObjects, direction); - - // Nested hitobjects don't need to scroll, but they do need accurate positions - UpdatePositions(obj.NestedHitObjects, direction, obj.HitObject.StartTime); - } - } - - public void UpdatePositions(IEnumerable hitObjects, ScrollingDirection direction, double currentTime) - { - foreach (var obj in hitObjects) - { - switch (direction) - { - case ScrollingDirection.Up: - obj.Y = PositionAt(currentTime, obj.HitObject.StartTime); - break; - case ScrollingDirection.Down: - obj.Y = -PositionAt(currentTime, obj.HitObject.StartTime); - break; - case ScrollingDirection.Left: - obj.X = PositionAt(currentTime, obj.HitObject.StartTime); - break; - case ScrollingDirection.Right: - obj.X = -PositionAt(currentTime, obj.HitObject.StartTime); - break; - } - } - } - - public double GetDisplayStartTime(double startTime) => startTime - TimeRange - 1000; + public double GetDisplayStartTime(double startTime) => startTime - timeRange - 1000; public float GetLength(double startTime, double endTime) { var objectLength = relativePositionAtCached(endTime) - relativePositionAtCached(startTime); - return (float)(objectLength * ScrollLength); + return (float)(objectLength * scrollLength); } public float PositionAt(double currentTime, double startTime) { // Caching is not used here as currentTime is unlikely to have been previously cached double timelinePosition = relativePositionAt(currentTime); - return (float)((relativePositionAtCached(startTime) - timelinePosition) * ScrollLength); + return (float)((relativePositionAtCached(startTime) - timelinePosition) * scrollLength); } private double relativePositionAtCached(double time) @@ -106,7 +56,7 @@ namespace osu.Game.Rulesets.UI.Scrolling.Visualisers private double relativePositionAt(double time) { if (controlPoints.Count == 0) - return time / TimeRange; + return time / timeRange; double length = 0; @@ -130,7 +80,7 @@ namespace osu.Game.Rulesets.UI.Scrolling.Visualisers var durationInCurrent = Math.Min(currentDuration, time - current.StartTime); // Figure out how much of the time range the duration represents, and adjust it by the speed multiplier - length += durationInCurrent / TimeRange * current.Multiplier; + length += durationInCurrent / timeRange * current.Multiplier; } return length;