From 5c0ef55691f4debd21c0b2c1c094ec9a73ad7642 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Apr 2021 14:09:35 +0900 Subject: [PATCH 1/8] Rename `SliderPlacementState` to make way for more generic version --- .../Sliders/SliderPlacementBlueprint.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 16e2a52279..efa249694a 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private InputManager inputManager; - private PlacementState state; + private SliderPlacementState state; private PathControlPoint segmentStart; private PathControlPoint cursor; private int currentSegmentLength; @@ -58,7 +58,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders controlPointVisualiser = new PathControlPointVisualiser(HitObject, false) }; - setState(PlacementState.Initial); + setState(SliderPlacementState.Initial); } protected override void LoadComplete() @@ -73,12 +73,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders switch (state) { - case PlacementState.Initial: + case SliderPlacementState.Initial: BeginPlacement(); HitObject.Position = ToLocalSpace(result.ScreenSpacePosition); break; - case PlacementState.Body: + case SliderPlacementState.Body: updateCursor(); break; } @@ -91,11 +91,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders switch (state) { - case PlacementState.Initial: + case SliderPlacementState.Initial: beginCurve(); break; - case PlacementState.Body: + case SliderPlacementState.Body: if (canPlaceNewControlPoint(out var lastPoint)) { // Place a new point by detatching the current cursor. @@ -121,7 +121,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders protected override void OnMouseUp(MouseUpEvent e) { - if (state == PlacementState.Body && e.Button == MouseButton.Right) + if (state == SliderPlacementState.Body && e.Button == MouseButton.Right) endCurve(); base.OnMouseUp(e); } @@ -129,7 +129,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private void beginCurve() { BeginPlacement(commitStart: true); - setState(PlacementState.Body); + setState(SliderPlacementState.Body); } private void endCurve() @@ -219,12 +219,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders tailCirclePiece.UpdateFrom(HitObject.TailCircle); } - private void setState(PlacementState newState) + private void setState(SliderPlacementState newState) { state = newState; } - private enum PlacementState + private enum SliderPlacementState { Initial, Body, From 119c9b4294a2b5574b6f57bec7620562b4ae19ef Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Apr 2021 14:10:21 +0900 Subject: [PATCH 2/8] Fix placement blueprints not being correctly removed after a rolled back placement --- .../Blueprints/HoldNotePlacementBlueprint.cs | 2 +- .../Blueprints/ManiaPlacementBlueprint.cs | 2 +- .../Blueprints/TaikoSpanPlacementBlueprint.cs | 2 +- osu.Game/Rulesets/Edit/PlacementBlueprint.cs | 29 +++++++++++++++---- .../Components/ComposeBlueprintContainer.cs | 27 ++++++++++++----- 5 files changed, 45 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs index 1f92929392..a13afdfffe 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs @@ -82,7 +82,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints { base.UpdateTimeAndPosition(result); - if (PlacementActive) + if (PlacementActive == PlacementState.Active) { if (result.Time is double endTime) { diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs index 5e09054667..8f25668dd0 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs @@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints { base.UpdateTimeAndPosition(result); - if (!PlacementActive) + if (PlacementActive == PlacementState.Waiting) Column = result.Playfield as Column; } } diff --git a/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs b/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs index e53b331f46..59249e6bf4 100644 --- a/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs @@ -72,7 +72,7 @@ namespace osu.Game.Rulesets.Taiko.Edit.Blueprints { base.UpdateTimeAndPosition(result); - if (PlacementActive) + if (PlacementActive == PlacementState.Active) { if (result.Time is double dragTime) { diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs index bfff93e7c5..6c1cd01796 100644 --- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Edit /// /// Whether the is currently mid-placement, but has not necessarily finished being placed. /// - public bool PlacementActive { get; private set; } + public PlacementState PlacementActive { get; private set; } /// /// The that is being placed. @@ -72,7 +72,8 @@ namespace osu.Game.Rulesets.Edit protected void BeginPlacement(bool commitStart = false) { placementHandler.BeginPlacement(HitObject); - PlacementActive |= commitStart; + if (commitStart) + PlacementActive = PlacementState.Active; } /// @@ -82,10 +83,19 @@ namespace osu.Game.Rulesets.Edit /// Whether the object should be committed. public void EndPlacement(bool commit) { - if (!PlacementActive) - BeginPlacement(); + switch (PlacementActive) + { + case PlacementState.Finished: + return; + + case PlacementState.Waiting: + // ensure placement was started before ending to make state handling simpler. + BeginPlacement(); + break; + } + placementHandler.EndPlacement(HitObject, commit); - PlacementActive = false; + PlacementActive = PlacementState.Finished; } /// @@ -94,7 +104,7 @@ namespace osu.Game.Rulesets.Edit /// The snap result information. public virtual void UpdateTimeAndPosition(SnapResult result) { - if (!PlacementActive) + if (PlacementActive == PlacementState.Waiting) HitObject.StartTime = result.Time ?? EditorClock?.CurrentTime ?? Time.Current; } @@ -125,5 +135,12 @@ namespace osu.Game.Rulesets.Edit return false; } } + + public enum PlacementState + { + Waiting, + Active, + Finished + } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index 5ab557804e..b0a6a091f0 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -196,7 +196,7 @@ namespace osu.Game.Screens.Edit.Compose.Components private void refreshTool() { removePlacement(); - createPlacement(); + ensurePlacementCreated(); } private void updatePlacementPosition() @@ -215,15 +215,26 @@ namespace osu.Game.Screens.Edit.Compose.Components { base.Update(); - if (Composer.CursorInPlacementArea) - createPlacement(); - else if (currentPlacement?.PlacementActive == false) - removePlacement(); - if (currentPlacement != null) { - updatePlacementPosition(); + switch (currentPlacement.PlacementActive) + { + case PlacementBlueprint.PlacementState.Waiting: + if (!Composer.CursorInPlacementArea) + removePlacement(); + break; + + case PlacementBlueprint.PlacementState.Finished: + removePlacement(); + break; + } } + + if (Composer.CursorInPlacementArea) + ensurePlacementCreated(); + + if (currentPlacement != null) + updatePlacementPosition(); } protected sealed override SelectionBlueprint CreateBlueprintFor(HitObject hitObject) @@ -249,7 +260,7 @@ namespace osu.Game.Screens.Edit.Compose.Components NewCombo.Value = TernaryState.False; } - private void createPlacement() + private void ensurePlacementCreated() { if (currentPlacement != null) return; From 936bde28a35ef8ea0ba9f4af801c09e8ea6b4bcc Mon Sep 17 00:00:00 2001 From: ekrctb Date: Fri, 16 Apr 2021 13:59:11 +0900 Subject: [PATCH 3/8] Remove manual handling of IsActive in RulesetInputManager Now it is supported in framework --- osu.Game/Rulesets/UI/RulesetInputManager.cs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 5ab09f9516..d6f002ea2c 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -11,7 +10,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; -using osu.Framework.Input.StateChanges; using osu.Framework.Input.StateChanges.Events; using osu.Framework.Input.States; using osu.Game.Configuration; @@ -102,17 +100,6 @@ namespace osu.Game.Rulesets.UI #endregion - // to avoid allocation - private readonly List emptyInputList = new List(); - - protected override List GetPendingInputs() - { - if (replayInputHandler?.IsActive == false) - return emptyInputList; - - return base.GetPendingInputs(); - } - #region Setting application (disables etc.) private Bindable mouseDisabled; From 84bc81a6deda614ce407730d62750c7a52195671 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Fri, 16 Apr 2021 12:31:32 +0900 Subject: [PATCH 4/8] Make FramedReplayInputHandler.CurrentTime non-null --- .../EmptyFreeformFramedReplayInputHandler.cs | 5 +---- .../Replays/PippidonFramedReplayInputHandler.cs | 5 +---- .../Replays/CatchFramedReplayInputHandler.cs | 5 +---- .../Replays/OsuFramedReplayInputHandler.cs | 5 +---- .../Rulesets/Replays/FramedReplayInputHandler.cs | 13 +++++++------ 5 files changed, 11 insertions(+), 22 deletions(-) diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Replays/EmptyFreeformFramedReplayInputHandler.cs b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Replays/EmptyFreeformFramedReplayInputHandler.cs index f25ea6ec62..b7e2031bb9 100644 --- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Replays/EmptyFreeformFramedReplayInputHandler.cs +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Replays/EmptyFreeformFramedReplayInputHandler.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using osu.Framework.Input.StateChanges; using osu.Framework.Utils; @@ -30,9 +29,7 @@ namespace osu.Game.Rulesets.EmptyFreeform.Replays if (frame == null) return Vector2.Zero; - Debug.Assert(CurrentTime != null); - - return Interpolation.ValueAt(CurrentTime.Value, frame.Position, NextFrame.Position, frame.Time, NextFrame.Time); + return Interpolation.ValueAt(CurrentTime, frame.Position, NextFrame.Position, frame.Time, NextFrame.Time); } } diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs index 18efa6b885..69c7df5fac 100644 --- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using System.Diagnostics; using osu.Framework.Input.StateChanges; using osu.Framework.Utils; using osu.Game.Replays; @@ -29,9 +28,7 @@ namespace osu.Game.Rulesets.Pippidon.Replays if (frame == null) return Vector2.Zero; - Debug.Assert(CurrentTime != null); - - return NextFrame != null ? Interpolation.ValueAt(CurrentTime.Value, frame.Position, NextFrame.Position, frame.Time, NextFrame.Time) : frame.Position; + return NextFrame != null ? Interpolation.ValueAt(CurrentTime, frame.Position, NextFrame.Position, frame.Time, NextFrame.Time) : frame.Position; } } diff --git a/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs b/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs index 99d899db80..5fea855a16 100644 --- a/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs +++ b/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using osu.Framework.Input.StateChanges; using osu.Framework.Utils; @@ -29,9 +28,7 @@ namespace osu.Game.Rulesets.Catch.Replays if (frame == null) return null; - Debug.Assert(CurrentTime != null); - - return NextFrame != null ? Interpolation.ValueAt(CurrentTime.Value, frame.Position, NextFrame.Position, frame.Time, NextFrame.Time) : frame.Position; + return NextFrame != null ? Interpolation.ValueAt(CurrentTime, frame.Position, NextFrame.Position, frame.Time, NextFrame.Time) : frame.Position; } } diff --git a/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs b/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs index cf48dc053f..6ac0bbd045 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using osu.Framework.Input.StateChanges; using osu.Framework.Utils; @@ -30,9 +29,7 @@ namespace osu.Game.Rulesets.Osu.Replays if (frame == null) return null; - Debug.Assert(CurrentTime != null); - - return NextFrame != null ? Interpolation.ValueAt(CurrentTime.Value, frame.Position, NextFrame.Position, frame.Time, NextFrame.Time) : frame.Position; + return NextFrame != null ? Interpolation.ValueAt(CurrentTime, frame.Position, NextFrame.Position, frame.Time, NextFrame.Time) : frame.Position; } } diff --git a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs index 279087ead9..0442acebe5 100644 --- a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs +++ b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +#nullable enable + using System; using System.Collections.Generic; using JetBrains.Annotations; @@ -32,7 +34,7 @@ namespace osu.Game.Rulesets.Replays /// /// Returns null if the current time is strictly before the first frame. /// The replay is empty. - public TFrame CurrentFrame + public TFrame? CurrentFrame { get { @@ -49,7 +51,7 @@ namespace osu.Game.Rulesets.Replays /// /// Returns null if the current frame is the last frame. /// The replay is empty. - public TFrame NextFrame + public TFrame? NextFrame { get { @@ -69,8 +71,7 @@ namespace osu.Game.Rulesets.Replays // This input handler should be enabled only if there is at least one replay frame. public override bool IsActive => HasFrames; - // Can make it non-null but that is a breaking change. - protected double? CurrentTime { get; private set; } + protected double CurrentTime { get; private set; } protected virtual double AllowedImportantTimeSpan => sixty_frame_time * 1.2; @@ -101,7 +102,7 @@ namespace osu.Game.Rulesets.Replays return false; return IsImportant(CurrentFrame) && // a button is in a pressed state - Math.Abs(CurrentTime - NextFrame.Time ?? 0) <= AllowedImportantTimeSpan; // the next frame is within an allowable time span + Math.Abs(CurrentTime - NextFrame?.Time ?? 0) <= AllowedImportantTimeSpan; // the next frame is within an allowable time span } } @@ -151,7 +152,7 @@ namespace osu.Game.Rulesets.Replays CurrentTime = Math.Clamp(time, frameStart, frameEnd); // In an important section, a mid-frame time cannot be used and a null is returned instead. - return inImportantSection && frameStart < time && time < frameEnd ? null : CurrentTime; + return inImportantSection && frameStart < time && time < frameEnd ? null : (double?)CurrentTime; } private double getFrameTime(int index) From 91c7d8d26cf2143cb85c53cf9770676947ab57e4 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Fri, 16 Apr 2021 12:53:58 +0900 Subject: [PATCH 5/8] Introduce `StartFrame` and `EndFrame` to simplify the replay interpolation code --- .../EmptyFreeformFramedReplayInputHandler.cs | 18 ++-------- .../PippidonFramedReplayInputHandler.cs | 18 ++-------- .../Replays/CatchFramedReplayInputHandler.cs | 17 ++-------- .../Replays/OsuFramedReplayInputHandler.cs | 18 ++-------- .../Replays/FramedReplayInputHandler.cs | 34 ++++++++++++------- 5 files changed, 33 insertions(+), 72 deletions(-) diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Replays/EmptyFreeformFramedReplayInputHandler.cs b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Replays/EmptyFreeformFramedReplayInputHandler.cs index b7e2031bb9..cc4483de31 100644 --- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Replays/EmptyFreeformFramedReplayInputHandler.cs +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Replays/EmptyFreeformFramedReplayInputHandler.cs @@ -7,7 +7,6 @@ using osu.Framework.Input.StateChanges; using osu.Framework.Utils; using osu.Game.Replays; using osu.Game.Rulesets.Replays; -using osuTK; namespace osu.Game.Rulesets.EmptyFreeform.Replays { @@ -20,24 +19,13 @@ namespace osu.Game.Rulesets.EmptyFreeform.Replays protected override bool IsImportant(EmptyFreeformReplayFrame frame) => frame.Actions.Any(); - protected Vector2 Position - { - get - { - var frame = CurrentFrame; - - if (frame == null) - return Vector2.Zero; - - return Interpolation.ValueAt(CurrentTime, frame.Position, NextFrame.Position, frame.Time, NextFrame.Time); - } - } - public override void CollectPendingInputs(List inputs) { + var position = Interpolation.ValueAt(CurrentTime, StartFrame.Position, EndFrame.Position, StartFrame.Time, EndFrame.Time); + inputs.Add(new MousePositionAbsoluteInput { - Position = GamefieldToScreenSpace(Position), + Position = GamefieldToScreenSpace(position), }); inputs.Add(new ReplayState { diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs index 69c7df5fac..e005346e1e 100644 --- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs @@ -6,7 +6,6 @@ using osu.Framework.Input.StateChanges; using osu.Framework.Utils; using osu.Game.Replays; using osu.Game.Rulesets.Replays; -using osuTK; namespace osu.Game.Rulesets.Pippidon.Replays { @@ -19,24 +18,13 @@ namespace osu.Game.Rulesets.Pippidon.Replays protected override bool IsImportant(PippidonReplayFrame frame) => true; - protected Vector2 Position - { - get - { - var frame = CurrentFrame; - - if (frame == null) - return Vector2.Zero; - - return NextFrame != null ? Interpolation.ValueAt(CurrentTime, frame.Position, NextFrame.Position, frame.Time, NextFrame.Time) : frame.Position; - } - } - public override void CollectPendingInputs(List inputs) { + var position = Interpolation.ValueAt(CurrentTime, StartFrame.Position, EndFrame.Position, StartFrame.Time, EndFrame.Time); + inputs.Add(new MousePositionAbsoluteInput { - Position = GamefieldToScreenSpace(Position) + Position = GamefieldToScreenSpace(position) }); } } diff --git a/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs b/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs index 5fea855a16..137328b1c3 100644 --- a/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs +++ b/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs @@ -19,27 +19,14 @@ namespace osu.Game.Rulesets.Catch.Replays protected override bool IsImportant(CatchReplayFrame frame) => frame.Actions.Any(); - protected float? Position - { - get - { - var frame = CurrentFrame; - - if (frame == null) - return null; - - return NextFrame != null ? Interpolation.ValueAt(CurrentTime, frame.Position, NextFrame.Position, frame.Time, NextFrame.Time) : frame.Position; - } - } - public override void CollectPendingInputs(List inputs) { - if (!Position.HasValue) return; + var position = Interpolation.ValueAt(CurrentTime, StartFrame.Position, EndFrame.Position, StartFrame.Time, EndFrame.Time); inputs.Add(new CatchReplayState { PressedActions = CurrentFrame?.Actions ?? new List(), - CatcherX = Position.Value + CatcherX = position }); } diff --git a/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs b/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs index 6ac0bbd045..7d696dfb79 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs @@ -7,7 +7,6 @@ using osu.Framework.Input.StateChanges; using osu.Framework.Utils; using osu.Game.Replays; using osu.Game.Rulesets.Replays; -using osuTK; namespace osu.Game.Rulesets.Osu.Replays { @@ -20,22 +19,11 @@ namespace osu.Game.Rulesets.Osu.Replays protected override bool IsImportant(OsuReplayFrame frame) => frame.Actions.Any(); - protected Vector2? Position - { - get - { - var frame = CurrentFrame; - - if (frame == null) - return null; - - return NextFrame != null ? Interpolation.ValueAt(CurrentTime, frame.Position, NextFrame.Position, frame.Time, NextFrame.Time) : frame.Position; - } - } - public override void CollectPendingInputs(List inputs) { - inputs.Add(new MousePositionAbsoluteInput { Position = GamefieldToScreenSpace(Position ?? Vector2.Zero) }); + var position = Interpolation.ValueAt(CurrentTime, StartFrame.Position, EndFrame.Position, StartFrame.Time, EndFrame.Time); + + inputs.Add(new MousePositionAbsoluteInput { Position = GamefieldToScreenSpace(position) }); inputs.Add(new ReplayState { PressedActions = CurrentFrame?.Actions ?? new List() }); } } diff --git a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs index 0442acebe5..0f25a45177 100644 --- a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs +++ b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs @@ -33,32 +33,42 @@ namespace osu.Game.Rulesets.Replays /// The current time is always between the start and the end time of the current frame. /// /// Returns null if the current time is strictly before the first frame. + public TFrame? CurrentFrame => currentFrameIndex == -1 ? null : (TFrame)Frames[currentFrameIndex]; + + /// + /// The next frame of the replay. + /// The start time of is always greater or equal to the start time of regardless of the seeking direction. + /// + /// Returns null if the current frame is the last frame. + public TFrame? NextFrame => currentFrameIndex == Frames.Count - 1 ? null : (TFrame)Frames[currentFrameIndex + 1]; + + /// + /// The frame for the start value of the interpolation of the replay movement. + /// /// The replay is empty. - public TFrame? CurrentFrame + public TFrame StartFrame { get { if (!HasFrames) - throw new InvalidOperationException($"Attempted to get {nameof(CurrentFrame)} of an empty replay"); + throw new InvalidOperationException($"Attempted to get {nameof(StartFrame)} of an empty replay"); - return currentFrameIndex == -1 ? null : (TFrame)Frames[currentFrameIndex]; + return (TFrame)Frames[Math.Max(0, currentFrameIndex)]; } } /// - /// The next frame of the replay. - /// The start time is always greater or equal to the start time of regardless of the seeking direction. + /// The frame for the end value of the interpolation of the replay movement. /// - /// Returns null if the current frame is the last frame. /// The replay is empty. - public TFrame? NextFrame + public TFrame EndFrame { get { if (!HasFrames) - throw new InvalidOperationException($"Attempted to get {nameof(NextFrame)} of an empty replay"); + throw new InvalidOperationException($"Attempted to get {nameof(EndFrame)} of an empty replay"); - return currentFrameIndex == Frames.Count - 1 ? null : (TFrame)Frames[currentFrameIndex + 1]; + return (TFrame)Frames[Math.Min(currentFrameIndex + 1, Frames.Count - 1)]; } } @@ -98,11 +108,11 @@ namespace osu.Game.Rulesets.Replays { get { - if (!HasFrames || !FrameAccuratePlayback || CurrentFrame == null) + if (!HasFrames || !FrameAccuratePlayback || currentFrameIndex == -1) return false; - return IsImportant(CurrentFrame) && // a button is in a pressed state - Math.Abs(CurrentTime - NextFrame?.Time ?? 0) <= AllowedImportantTimeSpan; // the next frame is within an allowable time span + return IsImportant(StartFrame) && // a button is in a pressed state + Math.Abs(CurrentTime - EndFrame.Time) <= AllowedImportantTimeSpan; // the next frame is within an allowable time span } } From a965e8a75d1ae770d6e3d1a4548132e3b6abc2ce Mon Sep 17 00:00:00 2001 From: ekrctb Date: Fri, 16 Apr 2021 13:06:02 +0900 Subject: [PATCH 6/8] Remove AutoGenerator workaround of now-fixed issue --- .../Replays/CatchAutoGenerator.cs | 4 ---- .../TestSceneAutoGeneration.cs | 16 ++++++++-------- .../Replays/ManiaAutoGenerator.cs | 4 ---- .../Replays/OsuAutoGenerator.cs | 2 -- .../Replays/TaikoAutoGenerator.cs | 1 - 5 files changed, 8 insertions(+), 19 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs index 64ded8e94f..10230b6b78 100644 --- a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs +++ b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs @@ -125,10 +125,6 @@ namespace osu.Game.Rulesets.Catch.Replays private void addFrame(double time, float? position = null, bool dashing = false) { - // todo: can be removed once FramedReplayInputHandler correctly handles rewinding before first frame. - if (Replay.Frames.Count == 0) - Replay.Frames.Add(new CatchReplayFrame(time - 1, position, false, null)); - var last = currentFrame; currentFrame = new CatchReplayFrame(time, position, dashing, last); Replay.Frames.Add(currentFrame); diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs index 399a46aa77..cffec3dfd5 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mania.Tests /// /// The number of frames which are generated at the start of a replay regardless of hitobject content. /// - private const int frame_offset = 1; + private const int frame_offset = 0; [Test] public void TestSingleNote() @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Mania.Tests var generated = new ManiaAutoGenerator(beatmap).Generate(); - Assert.IsTrue(generated.Frames.Count == frame_offset + 2, "Replay must have 3 frames"); + Assert.AreEqual(generated.Frames.Count, frame_offset + 2, "Incorrect number of frames"); Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time"); Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 1].Time, "Incorrect release time"); Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Special1), "Special1 has not been pressed"); @@ -54,7 +54,7 @@ namespace osu.Game.Rulesets.Mania.Tests var generated = new ManiaAutoGenerator(beatmap).Generate(); - Assert.IsTrue(generated.Frames.Count == frame_offset + 2, "Replay must have 3 frames"); + Assert.AreEqual(generated.Frames.Count, frame_offset + 2, "Incorrect number of frames"); Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time"); Assert.AreEqual(3000, generated.Frames[frame_offset + 1].Time, "Incorrect release time"); Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Special1), "Special1 has not been pressed"); @@ -74,7 +74,7 @@ namespace osu.Game.Rulesets.Mania.Tests var generated = new ManiaAutoGenerator(beatmap).Generate(); - Assert.IsTrue(generated.Frames.Count == frame_offset + 2, "Replay must have 3 frames"); + Assert.AreEqual(generated.Frames.Count, frame_offset + 2, "Incorrect number of frames"); Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time"); Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 1].Time, "Incorrect release time"); Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed"); @@ -96,7 +96,7 @@ namespace osu.Game.Rulesets.Mania.Tests var generated = new ManiaAutoGenerator(beatmap).Generate(); - Assert.IsTrue(generated.Frames.Count == frame_offset + 2, "Replay must have 3 frames"); + Assert.AreEqual(generated.Frames.Count, frame_offset + 2, "Incorrect number of frames"); Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time"); Assert.AreEqual(3000, generated.Frames[frame_offset + 1].Time, "Incorrect release time"); @@ -119,7 +119,7 @@ namespace osu.Game.Rulesets.Mania.Tests var generated = new ManiaAutoGenerator(beatmap).Generate(); - Assert.IsTrue(generated.Frames.Count == frame_offset + 4, "Replay must have 4 generated frames"); + Assert.AreEqual(generated.Frames.Count, frame_offset + 4, "Incorrect number of frames"); Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect first note hit time"); Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 1].Time, "Incorrect first note release time"); Assert.AreEqual(2000, generated.Frames[frame_offset + 2].Time, "Incorrect second note hit time"); @@ -146,7 +146,7 @@ namespace osu.Game.Rulesets.Mania.Tests var generated = new ManiaAutoGenerator(beatmap).Generate(); - Assert.IsTrue(generated.Frames.Count == frame_offset + 4, "Replay must have 4 generated frames"); + Assert.AreEqual(generated.Frames.Count, frame_offset + 4, "Incorrect number of frames"); Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect first note hit time"); Assert.AreEqual(3000, generated.Frames[frame_offset + 2].Time, "Incorrect first note release time"); Assert.AreEqual(2000, generated.Frames[frame_offset + 1].Time, "Incorrect second note hit time"); @@ -173,7 +173,7 @@ namespace osu.Game.Rulesets.Mania.Tests var generated = new ManiaAutoGenerator(beatmap).Generate(); - Assert.IsTrue(generated.Frames.Count == frame_offset + 3, "Replay must have 3 generated frames"); + Assert.AreEqual(generated.Frames.Count, frame_offset + 3, "Incorrect number of frames"); Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect first note hit time"); Assert.AreEqual(3000, generated.Frames[frame_offset + 1].Time, "Incorrect second note press time + first note release time"); Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 2].Time, "Incorrect second note release time"); diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs index 7c51d58b74..ada84dfac2 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs @@ -70,10 +70,6 @@ namespace osu.Game.Rulesets.Mania.Replays } } - // todo: can be removed once FramedReplayInputHandler correctly handles rewinding before first frame. - if (Replay.Frames.Count == 0) - Replay.Frames.Add(new ManiaReplayFrame(group.First().Time - 1)); - Replay.Frames.Add(new ManiaReplayFrame(group.First().Time, actions.ToArray())); } diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs index 693943a08a..7b0cf651c8 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs @@ -71,8 +71,6 @@ namespace osu.Game.Rulesets.Osu.Replays buttonIndex = 0; - AddFrameToReplay(new OsuReplayFrame(-100000, new Vector2(256, 500))); - AddFrameToReplay(new OsuReplayFrame(Beatmap.HitObjects[0].StartTime - 1500, new Vector2(256, 500))); AddFrameToReplay(new OsuReplayFrame(Beatmap.HitObjects[0].StartTime - 1500, new Vector2(256, 500))); for (int i = 0; i < Beatmap.HitObjects.Count; i++) diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs index a3dbe672a4..fa0134aa94 100644 --- a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs +++ b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs @@ -35,7 +35,6 @@ namespace osu.Game.Rulesets.Taiko.Replays bool hitButton = true; - Frames.Add(new TaikoReplayFrame(-100000)); Frames.Add(new TaikoReplayFrame(Beatmap.HitObjects[0].StartTime - 1000)); for (int i = 0; i < Beatmap.HitObjects.Count; i++) From c59906e9253fefed8c72c17b8434c3089c3cb44f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Apr 2021 15:55:33 +0900 Subject: [PATCH 7/8] Fix an occasional crash when deleting a HitObject via internal means --- 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 b5a28dc022..b1afbe0d61 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -438,8 +438,8 @@ namespace osu.Game.Screens.Edit.Compose.Components private void onBlueprintDeselected(SelectionBlueprint blueprint) { - SelectionHandler.HandleDeselected(blueprint); SelectionBlueprints.ChangeChildDepth(blueprint, 0); + SelectionHandler.HandleDeselected(blueprint); Composer.Playfield.SetKeepAlive(blueprint.HitObject, false); } From 25f0f17766bffceb205a6f162aa621d7e35970ed Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 16 Apr 2021 16:16:28 +0900 Subject: [PATCH 8/8] Attempt to fix match subscreen test failure --- .../Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index caa731f985..7c6c158b5a 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -13,6 +13,7 @@ using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; +using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.OnlinePlay.Multiplayer.Match; using osu.Game.Tests.Beatmaps; @@ -128,6 +129,8 @@ namespace osu.Game.Tests.Visual.Multiplayer InputManager.Click(MouseButton.Left); }); + AddUntilStep("wait for ready button to be enabled", () => this.ChildrenOfType().Single().ChildrenOfType().Single().Enabled.Value); + AddStep("click ready button", () => { InputManager.MoveMouseTo(this.ChildrenOfType().Single());