1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-14 15:17:27 +08:00

use stack to pass action state when applying hit results

this removes closure allocations
This commit is contained in:
Chandler Stowell 2024-01-24 13:13:45 -05:00
parent b272d34960
commit d2775680e6
22 changed files with 91 additions and 50 deletions

View File

@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.EmptyFreeform.Objects.Drawables
{ {
if (timeOffset >= 0) if (timeOffset >= 0)
// todo: implement judgement logic // todo: implement judgement logic
ApplyResult(r => r.Type = HitResult.Perfect); ApplyResult(static r => r.Type = HitResult.Perfect);
} }
protected override void UpdateHitStateTransforms(ArmedState state) protected override void UpdateHitStateTransforms(ArmedState state)

View File

@ -49,7 +49,12 @@ namespace osu.Game.Rulesets.Pippidon.Objects.Drawables
protected override void CheckForResult(bool userTriggered, double timeOffset) protected override void CheckForResult(bool userTriggered, double timeOffset)
{ {
if (timeOffset >= 0) if (timeOffset >= 0)
ApplyResult(r => r.Type = IsHovered ? HitResult.Perfect : HitResult.Miss); {
ApplyResult(static (r, isHovered) =>
{
r.Type = isHovered ? HitResult.Perfect : HitResult.Miss;
}, IsHovered);
}
} }
protected override double InitialLifetimeOffset => time_preempt; protected override double InitialLifetimeOffset => time_preempt;

View File

@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.EmptyScrolling.Objects.Drawables
{ {
if (timeOffset >= 0) if (timeOffset >= 0)
// todo: implement judgement logic // todo: implement judgement logic
ApplyResult(r => r.Type = HitResult.Perfect); ApplyResult(static r => r.Type = HitResult.Perfect);
} }
protected override void UpdateHitStateTransforms(ArmedState state) protected override void UpdateHitStateTransforms(ArmedState state)

View File

@ -49,7 +49,12 @@ namespace osu.Game.Rulesets.Pippidon.Objects.Drawables
protected override void CheckForResult(bool userTriggered, double timeOffset) protected override void CheckForResult(bool userTriggered, double timeOffset)
{ {
if (timeOffset >= 0) if (timeOffset >= 0)
ApplyResult(r => r.Type = currentLane.Value == HitObject.Lane ? HitResult.Perfect : HitResult.Miss); {
ApplyResult(static (r, pippidonHitObject) =>
{
r.Type = pippidonHitObject.currentLane.Value == pippidonHitObject.HitObject.Lane ? HitResult.Perfect : HitResult.Miss;
}, this);
}
} }
protected override void UpdateHitStateTransforms(ArmedState state) protected override void UpdateHitStateTransforms(ArmedState state)

View File

@ -63,7 +63,12 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
if (CheckPosition == null) return; if (CheckPosition == null) return;
if (timeOffset >= 0 && Result != null) if (timeOffset >= 0 && Result != null)
ApplyResult(r => r.Type = CheckPosition.Invoke(HitObject) ? r.Judgement.MaxResult : r.Judgement.MinResult); {
ApplyResult(static (r, state) =>
{
r.Type = state.CheckPosition.Invoke(state.HitObject) ? r.Judgement.MaxResult : r.Judgement.MinResult;
}, this);
}
} }
protected override void UpdateHitStateTransforms(ArmedState state) protected override void UpdateHitStateTransforms(ArmedState state)

View File

@ -265,7 +265,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
if (Tail.AllJudged) if (Tail.AllJudged)
{ {
if (Tail.IsHit) if (Tail.IsHit)
ApplyResult(r => r.Type = r.Judgement.MaxResult); ApplyResult(static r => r.Type = r.Judgement.MaxResult);
else else
MissForcefully(); MissForcefully();
} }

View File

@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
{ {
if (AllJudged) return; if (AllJudged) return;
ApplyResult(r => r.Type = hit ? r.Judgement.MaxResult : r.Judgement.MinResult); ApplyResult(static (r, hit) => r.Type = hit ? r.Judgement.MaxResult : r.Judgement.MinResult, hit);
} }
} }
} }

View File

@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
/// <summary> /// <summary>
/// Causes this <see cref="DrawableManiaHitObject"/> to get missed, disregarding all conditions in implementations of <see cref="DrawableHitObject.CheckForResult"/>. /// Causes this <see cref="DrawableManiaHitObject"/> to get missed, disregarding all conditions in implementations of <see cref="DrawableHitObject.CheckForResult"/>.
/// </summary> /// </summary>
public virtual void MissForcefully() => ApplyResult(r => r.Type = r.Judgement.MinResult); public virtual void MissForcefully() => ApplyResult(static r => r.Type = r.Judgement.MinResult);
} }
public abstract partial class DrawableManiaHitObject<TObject> : DrawableManiaHitObject public abstract partial class DrawableManiaHitObject<TObject> : DrawableManiaHitObject

View File

@ -89,7 +89,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
if (!userTriggered) if (!userTriggered)
{ {
if (!HitObject.HitWindows.CanBeHit(timeOffset)) if (!HitObject.HitWindows.CanBeHit(timeOffset))
ApplyResult(r => r.Type = r.Judgement.MinResult); ApplyResult(static r => r.Type = r.Judgement.MinResult);
return; return;
} }
@ -100,7 +100,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
result = GetCappedResult(result); result = GetCappedResult(result);
ApplyResult(r => r.Type = result); ApplyResult(static (r, result) => r.Type = result, result);
} }
/// <summary> /// <summary>

View File

@ -155,7 +155,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
if (!userTriggered) if (!userTriggered)
{ {
if (!HitObject.HitWindows.CanBeHit(timeOffset)) if (!HitObject.HitWindows.CanBeHit(timeOffset))
ApplyResult(r => r.Type = r.Judgement.MinResult); ApplyResult(static r => r.Type = r.Judgement.MinResult);
return; return;
} }
@ -169,19 +169,20 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
if (result == HitResult.None || clickAction != ClickAction.Hit) if (result == HitResult.None || clickAction != ClickAction.Hit)
return; return;
ApplyResult(r => ApplyResult(static (r, state) =>
{ {
var (hitCircle, hitResult) = state;
var circleResult = (OsuHitCircleJudgementResult)r; var circleResult = (OsuHitCircleJudgementResult)r;
// Todo: This should also consider misses, but they're a little more interesting to handle, since we don't necessarily know the position at the time of a miss. // Todo: This should also consider misses, but they're a little more interesting to handle, since we don't necessarily know the position at the time of a miss.
if (result.IsHit()) if (hitResult.IsHit())
{ {
var localMousePosition = ToLocalSpace(inputManager.CurrentState.Mouse.Position); var localMousePosition = hitCircle.ToLocalSpace(hitCircle.inputManager.CurrentState.Mouse.Position);
circleResult.CursorPositionAtHit = HitObject.StackedPosition + (localMousePosition - DrawSize / 2); circleResult.CursorPositionAtHit = hitCircle.HitObject.StackedPosition + (localMousePosition - hitCircle.DrawSize / 2);
} }
circleResult.Type = result; circleResult.Type = hitResult;
}); }, (this, result));
} }
/// <summary> /// <summary>

View File

@ -100,12 +100,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
/// <summary> /// <summary>
/// Causes this <see cref="DrawableOsuHitObject"/> to get hit, disregarding all conditions in implementations of <see cref="DrawableHitObject.CheckForResult"/>. /// Causes this <see cref="DrawableOsuHitObject"/> to get hit, disregarding all conditions in implementations of <see cref="DrawableHitObject.CheckForResult"/>.
/// </summary> /// </summary>
public void HitForcefully() => ApplyResult(r => r.Type = r.Judgement.MaxResult); public void HitForcefully() => ApplyResult(static r => r.Type = r.Judgement.MaxResult);
/// <summary> /// <summary>
/// Causes this <see cref="DrawableOsuHitObject"/> to get missed, disregarding all conditions in implementations of <see cref="DrawableHitObject.CheckForResult"/>. /// Causes this <see cref="DrawableOsuHitObject"/> to get missed, disregarding all conditions in implementations of <see cref="DrawableHitObject.CheckForResult"/>.
/// </summary> /// </summary>
public void MissForcefully() => ApplyResult(r => r.Type = r.Judgement.MinResult); public void MissForcefully() => ApplyResult(static r => r.Type = r.Judgement.MinResult);
private RectangleF parentScreenSpaceRectangle => ((DrawableOsuHitObject)ParentHitObject)?.parentScreenSpaceRectangle ?? Parent!.ScreenSpaceDrawQuad.AABBFloat; private RectangleF parentScreenSpaceRectangle => ((DrawableOsuHitObject)ParentHitObject)?.parentScreenSpaceRectangle ?? Parent!.ScreenSpaceDrawQuad.AABBFloat;

View File

@ -292,10 +292,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
if (HitObject.ClassicSliderBehaviour) if (HitObject.ClassicSliderBehaviour)
{ {
// Classic behaviour means a slider is judged proportionally to the number of nested hitobjects hit. This is the classic osu!stable scoring. // Classic behaviour means a slider is judged proportionally to the number of nested hitobjects hit. This is the classic osu!stable scoring.
ApplyResult(r => ApplyResult(static (r, nestedHitObjects) =>
{ {
int totalTicks = NestedHitObjects.Count; int totalTicks = nestedHitObjects.Count;
int hitTicks = NestedHitObjects.Count(h => h.IsHit); int hitTicks = nestedHitObjects.Count(h => h.IsHit);
if (hitTicks == totalTicks) if (hitTicks == totalTicks)
r.Type = HitResult.Great; r.Type = HitResult.Great;
@ -306,13 +306,16 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
double hitFraction = (double)hitTicks / totalTicks; double hitFraction = (double)hitTicks / totalTicks;
r.Type = hitFraction >= 0.5 ? HitResult.Ok : HitResult.Meh; r.Type = hitFraction >= 0.5 ? HitResult.Ok : HitResult.Meh;
} }
}); }, NestedHitObjects);
} }
else else
{ {
// If only the nested hitobjects are judged, then the slider's own judgement is ignored for scoring purposes. // If only the nested hitobjects are judged, then the slider's own judgement is ignored for scoring purposes.
// But the slider needs to still be judged with a reasonable hit/miss result for visual purposes (hit/miss transforms, etc). // But the slider needs to still be judged with a reasonable hit/miss result for visual purposes (hit/miss transforms, etc).
ApplyResult(r => r.Type = NestedHitObjects.Any(h => h.Result.IsHit) ? r.Judgement.MaxResult : r.Judgement.MinResult); ApplyResult(static (r, nestedHitObjects) =>
{
r.Type = nestedHitObjects.Any(h => h.Result.IsHit) ? r.Judgement.MaxResult : r.Judgement.MinResult;
}, NestedHitObjects);
} }
} }

View File

@ -258,17 +258,17 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
foreach (var tick in ticks.Where(t => !t.Result.HasResult)) foreach (var tick in ticks.Where(t => !t.Result.HasResult))
tick.TriggerResult(false); tick.TriggerResult(false);
ApplyResult(r => ApplyResult(static (r, spinner) =>
{ {
if (Progress >= 1) if (spinner.Progress >= 1)
r.Type = HitResult.Great; r.Type = HitResult.Great;
else if (Progress > .9) else if (spinner.Progress > .9)
r.Type = HitResult.Ok; r.Type = HitResult.Ok;
else if (Progress > .75) else if (spinner.Progress > .75)
r.Type = HitResult.Meh; r.Type = HitResult.Meh;
else if (Time.Current >= HitObject.EndTime) else if (spinner.Time.Current >= spinner.HitObject.EndTime)
r.Type = r.Judgement.MinResult; r.Type = r.Judgement.MinResult;
}); }, this);
} }
protected override void Update() protected override void Update()

View File

@ -35,6 +35,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
/// Apply a judgement result. /// Apply a judgement result.
/// </summary> /// </summary>
/// <param name="hit">Whether this tick was reached.</param> /// <param name="hit">Whether this tick was reached.</param>
internal void TriggerResult(bool hit) => ApplyResult(r => r.Type = hit ? r.Judgement.MaxResult : r.Judgement.MinResult); internal void TriggerResult(bool hit) => ApplyResult(static (r, hit) => r.Type = hit ? r.Judgement.MaxResult : r.Judgement.MinResult, hit);
} }
} }

View File

@ -143,7 +143,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
if (timeOffset < 0) if (timeOffset < 0)
return; return;
ApplyResult(r => r.Type = r.Judgement.MaxResult); ApplyResult(static r => r.Type = r.Judgement.MaxResult);
} }
protected override void UpdateHitStateTransforms(ArmedState state) protected override void UpdateHitStateTransforms(ArmedState state)
@ -192,7 +192,10 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
if (!ParentHitObject.Judged) if (!ParentHitObject.Judged)
return; return;
ApplyResult(r => r.Type = ParentHitObject.IsHit ? r.Judgement.MaxResult : r.Judgement.MinResult); ApplyResult(static (r, parentHitObject) =>
{
r.Type = parentHitObject.IsHit ? r.Judgement.MaxResult : r.Judgement.MinResult;
}, ParentHitObject);
} }
public override bool OnPressed(KeyBindingPressEvent<TaikoAction> e) => false; public override bool OnPressed(KeyBindingPressEvent<TaikoAction> e) => false;

View File

@ -49,14 +49,14 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
if (!userTriggered) if (!userTriggered)
{ {
if (timeOffset > HitObject.HitWindow) if (timeOffset > HitObject.HitWindow)
ApplyResult(r => r.Type = r.Judgement.MinResult); ApplyResult(static r => r.Type = r.Judgement.MinResult);
return; return;
} }
if (Math.Abs(timeOffset) > HitObject.HitWindow) if (Math.Abs(timeOffset) > HitObject.HitWindow)
return; return;
ApplyResult(r => r.Type = r.Judgement.MaxResult); ApplyResult(static r => r.Type = r.Judgement.MaxResult);
} }
public override void OnKilled() public override void OnKilled()
@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
base.OnKilled(); base.OnKilled();
if (Time.Current > HitObject.GetEndTime() && !Judged) if (Time.Current > HitObject.GetEndTime() && !Judged)
ApplyResult(r => r.Type = r.Judgement.MinResult); ApplyResult(static r => r.Type = r.Judgement.MinResult);
} }
protected override void UpdateHitStateTransforms(ArmedState state) protected override void UpdateHitStateTransforms(ArmedState state)
@ -105,7 +105,10 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
if (!ParentHitObject.Judged) if (!ParentHitObject.Judged)
return; return;
ApplyResult(r => r.Type = ParentHitObject.IsHit ? r.Judgement.MaxResult : r.Judgement.MinResult); ApplyResult(static (r, parentHitObject) =>
{
r.Type = parentHitObject.IsHit ? r.Judgement.MaxResult : r.Judgement.MinResult;
}, ParentHitObject);
} }
public override bool OnPressed(KeyBindingPressEvent<TaikoAction> e) => false; public override bool OnPressed(KeyBindingPressEvent<TaikoAction> e) => false;

View File

@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
protected override void LoadComplete() protected override void LoadComplete()
{ {
base.LoadComplete(); base.LoadComplete();
ApplyResult(r => r.Type = r.Judgement.MaxResult); ApplyResult(static r => r.Type = r.Judgement.MaxResult);
} }
protected override void LoadSamples() protected override void LoadSamples()

View File

@ -99,7 +99,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
if (!userTriggered) if (!userTriggered)
{ {
if (!HitObject.HitWindows.CanBeHit(timeOffset)) if (!HitObject.HitWindows.CanBeHit(timeOffset))
ApplyResult(r => r.Type = r.Judgement.MinResult); ApplyResult(static r => r.Type = r.Judgement.MinResult);
return; return;
} }
@ -108,9 +108,9 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
return; return;
if (!validActionPressed) if (!validActionPressed)
ApplyResult(r => r.Type = r.Judgement.MinResult); ApplyResult(static r => r.Type = r.Judgement.MinResult);
else else
ApplyResult(r => r.Type = result); ApplyResult(static (r, result) => r.Type = result, result);
} }
public override bool OnPressed(KeyBindingPressEvent<TaikoAction> e) public override bool OnPressed(KeyBindingPressEvent<TaikoAction> e)
@ -209,19 +209,19 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
if (!ParentHitObject.Result.IsHit) if (!ParentHitObject.Result.IsHit)
{ {
ApplyResult(r => r.Type = r.Judgement.MinResult); ApplyResult(static r => r.Type = r.Judgement.MinResult);
return; return;
} }
if (!userTriggered) if (!userTriggered)
{ {
if (timeOffset - ParentHitObject.Result.TimeOffset > SECOND_HIT_WINDOW) if (timeOffset - ParentHitObject.Result.TimeOffset > SECOND_HIT_WINDOW)
ApplyResult(r => r.Type = r.Judgement.MinResult); ApplyResult(static r => r.Type = r.Judgement.MinResult);
return; return;
} }
if (Math.Abs(timeOffset - ParentHitObject.Result.TimeOffset) <= SECOND_HIT_WINDOW) if (Math.Abs(timeOffset - ParentHitObject.Result.TimeOffset) <= SECOND_HIT_WINDOW)
ApplyResult(r => r.Type = r.Judgement.MaxResult); ApplyResult(static r => r.Type = r.Judgement.MaxResult);
} }
public override bool OnPressed(KeyBindingPressEvent<TaikoAction> e) public override bool OnPressed(KeyBindingPressEvent<TaikoAction> e)

View File

@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
// it can happen that the hit window of the nested strong hit extends past the lifetime of the parent object. // it can happen that the hit window of the nested strong hit extends past the lifetime of the parent object.
// this is a safety to prevent such cases from causing the nested hit to never be judged and as such prevent gameplay from completing. // this is a safety to prevent such cases from causing the nested hit to never be judged and as such prevent gameplay from completing.
if (!Judged && Time.Current > ParentHitObject?.HitObject.GetEndTime()) if (!Judged && Time.Current > ParentHitObject?.HitObject.GetEndTime())
ApplyResult(r => r.Type = r.Judgement.MinResult); ApplyResult(static r => r.Type = r.Judgement.MinResult);
} }
} }
} }

View File

@ -206,7 +206,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
expandingRing.ScaleTo(1f + Math.Min(target_ring_scale - 1f, (target_ring_scale - 1f) * completion * 1.3f), 260, Easing.OutQuint); expandingRing.ScaleTo(1f + Math.Min(target_ring_scale - 1f, (target_ring_scale - 1f) * completion * 1.3f), 260, Easing.OutQuint);
if (numHits == HitObject.RequiredHits) if (numHits == HitObject.RequiredHits)
ApplyResult(r => r.Type = r.Judgement.MaxResult); ApplyResult(static r => r.Type = r.Judgement.MaxResult);
} }
else else
{ {
@ -227,7 +227,11 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
tick.TriggerResult(false); tick.TriggerResult(false);
} }
ApplyResult(r => r.Type = numHits == HitObject.RequiredHits ? r.Judgement.MaxResult : r.Judgement.MinResult); ApplyResult(static (r, state) =>
{
var (numHits, hitObject) = state;
r.Type = numHits == hitObject.RequiredHits ? r.Judgement.MaxResult : r.Judgement.MinResult;
}, (numHits, HitObject));
} }
} }

View File

@ -30,7 +30,10 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
public void TriggerResult(bool hit) public void TriggerResult(bool hit)
{ {
HitObject.StartTime = Time.Current; HitObject.StartTime = Time.Current;
ApplyResult(r => r.Type = hit ? r.Judgement.MaxResult : r.Judgement.MinResult); ApplyResult(static (r, hit) =>
{
r.Type = hit ? r.Judgement.MaxResult : r.Judgement.MinResult;
}, hit);
} }
protected override void CheckForResult(bool userTriggered, double timeOffset) protected override void CheckForResult(bool userTriggered, double timeOffset)

View File

@ -687,12 +687,14 @@ namespace osu.Game.Rulesets.Objects.Drawables
/// the <see cref="ScoreProcessor"/> of the <see cref="JudgementResult"/>. /// the <see cref="ScoreProcessor"/> of the <see cref="JudgementResult"/>.
/// </summary> /// </summary>
/// <param name="application">The callback that applies changes to the <see cref="JudgementResult"/>.</param> /// <param name="application">The callback that applies changes to the <see cref="JudgementResult"/>.</param>
protected void ApplyResult(Action<JudgementResult> application) /// <param name="state">The state passed to the callback.</param>
/// <typeparam name="TState">The type of the state information that is passed to the callback method.</typeparam>
protected void ApplyResult<TState>(Action<JudgementResult, TState> application, TState state)
{ {
if (Result.HasResult) if (Result.HasResult)
throw new InvalidOperationException("Cannot apply result on a hitobject that already has a result."); throw new InvalidOperationException("Cannot apply result on a hitobject that already has a result.");
application?.Invoke(Result); application?.Invoke(Result, state);
if (!Result.HasResult) if (!Result.HasResult)
throw new InvalidOperationException($"{GetType().ReadableName()} applied a {nameof(JudgementResult)} but did not update {nameof(JudgementResult.Type)}."); throw new InvalidOperationException($"{GetType().ReadableName()} applied a {nameof(JudgementResult)} but did not update {nameof(JudgementResult.Type)}.");
@ -714,6 +716,13 @@ namespace osu.Game.Rulesets.Objects.Drawables
OnNewResult?.Invoke(this, Result); OnNewResult?.Invoke(this, Result);
} }
/// <summary>
/// Applies the <see cref="Result"/> of this <see cref="DrawableHitObject"/>, notifying responders such as
/// the <see cref="ScoreProcessor"/> of the <see cref="JudgementResult"/>.
/// </summary>
/// <param name="application">The callback that applies changes to the <see cref="JudgementResult"/>.</param>
protected void ApplyResult(Action<JudgementResult> application) => ApplyResult<object>((r, _) => application?.Invoke(r), null);
/// <summary> /// <summary>
/// Processes this <see cref="DrawableHitObject"/>, checking if a scoring result has occurred. /// Processes this <see cref="DrawableHitObject"/>, checking if a scoring result has occurred.
/// </summary> /// </summary>