From 7a25fe79b71c8c6ca7f08d9934ca4107a1b0fb36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 11 Jan 2022 21:27:22 +0100 Subject: [PATCH] Fix sample control point time being calculated before defaults applied In editor contexts, the `StartTimeBindable` subscription in `HitObject` was firing before defaults were applied, which in the case of sliders manifested in an infinite end time. `ApplyDefaults()` also did not always set the time of the control point to the correct value, which matters when the beatmap is encoded. Ensure that the control points receive the correct time values during default application, and only register the `StartTimeBindable` change callback after defaults have been successfully applied. --- osu.Game/Rulesets/Objects/HitObject.cs | 44 ++++++++++++-------------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index a80b3d0fa5..e63ccbc0d3 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -87,23 +87,6 @@ namespace osu.Game.Rulesets.Objects [JsonIgnore] public SlimReadOnlyListWrapper NestedHitObjects => nestedHitObjects.AsSlimReadOnly(); - public HitObject() - { - StartTimeBindable.ValueChanged += time => - { - double offset = time.NewValue - time.OldValue; - - foreach (var nested in nestedHitObjects) - nested.StartTime += offset; - - if (DifficultyControlPoint != DifficultyControlPoint.DEFAULT) - DifficultyControlPoint.Time = time.NewValue; - - if (SampleControlPoint != SampleControlPoint.DEFAULT) - SampleControlPoint.Time = this.GetEndTime() + control_point_leniency; - }; - } - /// /// Applies default values to this HitObject. /// @@ -115,24 +98,22 @@ namespace osu.Game.Rulesets.Objects var legacyInfo = controlPointInfo as LegacyControlPointInfo; if (legacyInfo != null) - { DifficultyControlPoint = (DifficultyControlPoint)legacyInfo.DifficultyPointAt(StartTime).DeepClone(); - DifficultyControlPoint.Time = StartTime; - } else if (DifficultyControlPoint == DifficultyControlPoint.DEFAULT) DifficultyControlPoint = new DifficultyControlPoint(); + DifficultyControlPoint.Time = StartTime; + ApplyDefaultsToSelf(controlPointInfo, difficulty); // This is done here after ApplyDefaultsToSelf as we may require custom defaults to be applied to have an accurate end time. if (legacyInfo != null) - { SampleControlPoint = (SampleControlPoint)legacyInfo.SamplePointAt(this.GetEndTime() + control_point_leniency).DeepClone(); - SampleControlPoint.Time = this.GetEndTime() + control_point_leniency; - } else if (SampleControlPoint == SampleControlPoint.DEFAULT) SampleControlPoint = new SampleControlPoint(); + SampleControlPoint.Time = this.GetEndTime() + control_point_leniency; + nestedHitObjects.Clear(); CreateNestedHitObjects(cancellationToken); @@ -155,6 +136,23 @@ namespace osu.Game.Rulesets.Objects foreach (var h in nestedHitObjects) h.ApplyDefaults(controlPointInfo, difficulty, cancellationToken); + // importantly, this callback is only registered after default application + // to ensure that the read of `this.GetEndTime()` within doesn't return an invalid value + // if `StartTimeBindable` is changed prior to default application. + StartTimeBindable.ValueChanged += time => + { + double offset = time.NewValue - time.OldValue; + + foreach (var nested in nestedHitObjects) + nested.StartTime += offset; + + if (DifficultyControlPoint != DifficultyControlPoint.DEFAULT) + DifficultyControlPoint.Time = time.NewValue; + + if (SampleControlPoint != SampleControlPoint.DEFAULT) + SampleControlPoint.Time = this.GetEndTime() + control_point_leniency; + }; + DefaultsApplied?.Invoke(this); }