Skip to content

Remove bindable overheads of health displays #26455

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Jan 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions osu.Game.Tests/Visual/Gameplay/TestSceneArgonHealthDisplay.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Testing;
using osu.Framework.Threading;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Judgements;
Expand Down Expand Up @@ -159,5 +160,15 @@ private void applyPerfectHit()
Type = HitResult.Perfect
});
}

[Test]
public void TestSimulateDrain()
{
ScheduledDelegate del = null!;

AddStep("simulate drain", () => del = Scheduler.AddDelayed(() => healthProcessor.Health.Value -= 0.00025f * Time.Elapsed, 0, true));
AddUntilStep("wait until zero", () => healthProcessor.Health.Value == 0);
AddStep("cancel drain", () => del.Cancel());
}
}
}
18 changes: 10 additions & 8 deletions osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,16 @@ public partial class TestSceneFailingLayer : OsuTestScene

private readonly Bindable<bool> showHealth = new Bindable<bool>();

private HealthProcessor healthProcessor;

[Resolved]
private OsuConfigManager config { get; set; }

private void create(HealthProcessor healthProcessor)
{
AddStep("create layer", () =>
{
Child = new HealthProcessorContainer(healthProcessor)
Child = new HealthProcessorContainer(this.healthProcessor = healthProcessor)
{
RelativeSizeAxes = Axes.Both,
Child = layer = new FailingLayer()
Expand All @@ -50,12 +52,12 @@ public void TestLayerFading()
AddSliderStep("current health", 0.0, 1.0, 1.0, val =>
{
if (layer != null)
layer.Current.Value = val;
healthProcessor.Health.Value = val;
});

AddStep("set health to 0.10", () => layer.Current.Value = 0.1);
AddStep("set health to 0.10", () => healthProcessor.Health.Value = 0.1);
AddUntilStep("layer fade is visible", () => layer.ChildrenOfType<Container>().First().Alpha > 0.1f);
AddStep("set health to 1", () => layer.Current.Value = 1f);
AddStep("set health to 1", () => healthProcessor.Health.Value = 1f);
AddUntilStep("layer fade is invisible", () => !layer.ChildrenOfType<Container>().First().IsPresent);
}

Expand All @@ -65,7 +67,7 @@ public void TestLayerDisabledViaConfig()
create(new DrainingHealthProcessor(0));
AddUntilStep("layer is visible", () => layer.IsPresent);
AddStep("disable layer", () => config.SetValue(OsuSetting.FadePlayfieldWhenHealthLow, false));
AddStep("set health to 0.10", () => layer.Current.Value = 0.1);
AddStep("set health to 0.10", () => healthProcessor.Health.Value = 0.1);
AddUntilStep("layer is not visible", () => !layer.IsPresent);
}

Expand All @@ -74,15 +76,15 @@ public void TestLayerVisibilityWithAccumulatingProcessor()
{
create(new AccumulatingHealthProcessor(1));
AddUntilStep("layer is not visible", () => !layer.IsPresent);
AddStep("set health to 0.10", () => layer.Current.Value = 0.1);
AddStep("set health to 0.10", () => healthProcessor.Health.Value = 0.1);
AddUntilStep("layer is not visible", () => !layer.IsPresent);
}

[Test]
public void TestLayerVisibilityWithDrainingProcessor()
{
create(new DrainingHealthProcessor(0));
AddStep("set health to 0.10", () => layer.Current.Value = 0.1);
AddStep("set health to 0.10", () => healthProcessor.Health.Value = 0.1);
AddWaitStep("wait for potential fade", 10);
AddAssert("layer is still visible", () => layer.IsPresent);
}
Expand All @@ -92,7 +94,7 @@ public void TestLayerVisibilityWithDifferentOptions()
{
create(new DrainingHealthProcessor(0));

AddStep("set health to 0.10", () => layer.Current.Value = 0.1);
AddStep("set health to 0.10", () => healthProcessor.Health.Value = 0.1);

AddStep("don't show health", () => showHealth.Value = false);
AddStep("disable FadePlayfieldWhenHealthLow", () => config.SetValue(OsuSetting.FadePlayfieldWhenHealthLow, false));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public partial class TestSceneSkinnableHealthDisplay : SkinnableHUDComponentTest
[Cached(typeof(HealthProcessor))]
private HealthProcessor healthProcessor = new DrainingHealthProcessor(0);

protected override Drawable CreateArgonImplementation() => new ArgonHealthDisplay { Scale = new Vector2(0.6f), Width = 1f };
protected override Drawable CreateArgonImplementation() => new ArgonHealthDisplay { Scale = new Vector2(0.6f), Width = 600, UseRelativeSize = { Value = false } };
protected override Drawable CreateDefaultImplementation() => new DefaultHealthDisplay { Scale = new Vector2(0.6f) };
protected override Drawable CreateLegacyImplementation() => new LegacyHealthDisplay { Scale = new Vector2(0.6f) };

Expand All @@ -35,6 +35,13 @@ public void SetUpSteps()
});
}

protected override void Update()
{
base.Update();

healthProcessor.Health.Value -= 0.0001f * Time.Elapsed;
}

[Test]
public void TestHealthDisplayIncrementing()
{
Expand Down
135 changes: 51 additions & 84 deletions osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Caching;
Expand Down Expand Up @@ -59,39 +58,12 @@ public partial class ArgonHealthDisplay : HealthDisplay, ISerialisableDrawable

private bool displayingMiss => resetMissBarDelegate != null;

private readonly List<Vector2> missBarVertices = new List<Vector2>();
private readonly List<Vector2> healthBarVertices = new List<Vector2>();
private readonly List<Vector2> vertices = new List<Vector2>();

private double glowBarValue;

public double GlowBarValue
{
get => glowBarValue;
set
{
if (Precision.AlmostEquals(glowBarValue, value, 0.0001))
return;

glowBarValue = value;
pathVerticesCache.Invalidate();
}
}

private double healthBarValue;

public double HealthBarValue
{
get => healthBarValue;
set
{
if (Precision.AlmostEquals(healthBarValue, value, 0.0001))
return;

healthBarValue = value;
pathVerticesCache.Invalidate();
}
}

public const float MAIN_PATH_RADIUS = 10f;

private const float curve_start_offset = 70;
Expand Down Expand Up @@ -161,7 +133,6 @@ protected override void LoadComplete()
base.LoadComplete();

HealthProcessor.NewJudgement += onNewJudgement;
Current.BindValueChanged(onCurrentChanged, true);

// we're about to set `RelativeSizeAxes` depending on the value of `UseRelativeSize`.
// setting `RelativeSizeAxes` internally transforms absolute sizing to relative and back to keep the size the same,
Expand All @@ -176,31 +147,6 @@ protected override void LoadComplete()

private void onNewJudgement(JudgementResult result) => pendingMissAnimation |= !result.IsHit;

private void onCurrentChanged(ValueChangedEvent<double> valueChangedEvent)
// schedule display updates one frame later to ensure we know the judgement result causing this change (if there is one).
=> Scheduler.AddOnce(updateDisplay);

private void updateDisplay()
{
double newHealth = Current.Value;

if (newHealth >= GlowBarValue)
finishMissDisplay();

double time = newHealth > GlowBarValue ? 500 : 250;

// TODO: this should probably use interpolation in update.
this.TransformTo(nameof(HealthBarValue), newHealth, time, Easing.OutQuint);

if (pendingMissAnimation && newHealth < GlowBarValue)
triggerMissDisplay();

pendingMissAnimation = false;

if (!displayingMiss)
this.TransformTo(nameof(GlowBarValue), newHealth, time, Easing.OutQuint);
}

protected override void Update()
{
base.Update();
Expand All @@ -211,33 +157,44 @@ protected override void Update()
drawSizeLayout.Validate();
}

if (!pathVerticesCache.IsValid)
updatePathVertices();
healthBarValue = Interpolation.DampContinuously(healthBarValue, Current.Value, 50, Time.Elapsed);
if (!displayingMiss)
glowBarValue = Interpolation.DampContinuously(glowBarValue, Current.Value, 50, Time.Elapsed);

mainBar.Alpha = (float)Interpolation.DampContinuously(mainBar.Alpha, Current.Value > 0 ? 1 : 0, 40, Time.Elapsed);
glowBar.Alpha = (float)Interpolation.DampContinuously(glowBar.Alpha, GlowBarValue > 0 ? 1 : 0, 40, Time.Elapsed);
glowBar.Alpha = (float)Interpolation.DampContinuously(glowBar.Alpha, glowBarValue > 0 ? 1 : 0, 40, Time.Elapsed);

updatePathVertices();
}

protected override void HealthChanged(bool increase)
{
if (Current.Value >= glowBarValue)
finishMissDisplay();

if (pendingMissAnimation)
{
triggerMissDisplay();
pendingMissAnimation = false;
}

base.HealthChanged(increase);
}

protected override void FinishInitialAnimation(double value)
{
base.FinishInitialAnimation(value);
this.TransformTo(nameof(HealthBarValue), value, 500, Easing.OutQuint);
this.TransformTo(nameof(GlowBarValue), value, 250, Easing.OutQuint);
this.TransformTo(nameof(healthBarValue), value, 500, Easing.OutQuint);
this.TransformTo(nameof(glowBarValue), value, 250, Easing.OutQuint);
}

protected override void Flash()
{
base.Flash();

mainBar.TransformTo(nameof(BarPath.GlowColour), main_bar_glow_colour.Opacity(0.8f))
.TransformTo(nameof(BarPath.GlowColour), main_bar_glow_colour, 300, Easing.OutQuint);

if (!displayingMiss)
{
glowBar.TransformTo(nameof(BarPath.BarColour), Colour4.White, 30, Easing.OutQuint)
.Then()
.TransformTo(nameof(BarPath.BarColour), main_bar_colour, 1000, Easing.OutQuint);

// TODO: REMOVE THIS. It's recreating textures.
glowBar.TransformTo(nameof(BarPath.GlowColour), Colour4.White, 30, Easing.OutQuint)
.Then()
.TransformTo(nameof(BarPath.GlowColour), main_bar_glow_colour, 300, Easing.OutQuint);
Expand All @@ -251,13 +208,15 @@ private void triggerMissDisplay()

this.Delay(500).Schedule(() =>
{
this.TransformTo(nameof(GlowBarValue), Current.Value, 300, Easing.OutQuint);
this.TransformTo(nameof(glowBarValue), Current.Value, 300, Easing.OutQuint);
finishMissDisplay();
}, out resetMissBarDelegate);

// TODO: REMOVE THIS. It's recreating textures.
glowBar.TransformTo(nameof(BarPath.BarColour), new Colour4(255, 147, 147, 255), 100, Easing.OutQuint).Then()
.TransformTo(nameof(BarPath.BarColour), new Colour4(255, 93, 93, 255), 800, Easing.OutQuint);

// TODO: REMOVE THIS. It's recreating textures.
glowBar.TransformTo(nameof(BarPath.GlowColour), new Colour4(253, 0, 0, 255).Lighten(0.2f))
.TransformTo(nameof(BarPath.GlowColour), new Colour4(253, 0, 0, 255), 800, Easing.OutQuint);
}
Expand All @@ -269,6 +228,7 @@ private void finishMissDisplay()

if (Current.Value > 0)
{
// TODO: REMOVE THIS. It's recreating textures.
glowBar.TransformTo(nameof(BarPath.BarColour), main_bar_colour, 300, Easing.In);
glowBar.TransformTo(nameof(BarPath.GlowColour), main_bar_glow_colour, 300, Easing.In);
}
Expand Down Expand Up @@ -308,7 +268,6 @@ private void updatePath()
if (DrawWidth - padding < rescale_cutoff)
rescalePathProportionally();

List<Vector2> vertices = new List<Vector2>();
barPath.GetPathToProgress(vertices, 0.0, 1.0);

background.Vertices = vertices;
Expand Down Expand Up @@ -338,20 +297,23 @@ void rescalePathProportionally()

private void updatePathVertices()
{
barPath.GetPathToProgress(healthBarVertices, 0.0, healthBarValue);
barPath.GetPathToProgress(missBarVertices, healthBarValue, Math.Max(glowBarValue, healthBarValue));
barPath.GetPathToProgress(vertices, 0.0, healthBarValue);
if (vertices.Count == 0) vertices.Add(Vector2.Zero);
Vector2 initialVertex = vertices[0];
for (int i = 0; i < vertices.Count; i++)
vertices[i] -= initialVertex;

if (healthBarVertices.Count == 0)
healthBarVertices.Add(Vector2.Zero);

if (missBarVertices.Count == 0)
missBarVertices.Add(Vector2.Zero);
mainBar.Vertices = vertices;
mainBar.Position = initialVertex;

glowBar.Vertices = missBarVertices.Select(v => v - missBarVertices[0]).ToList();
glowBar.Position = missBarVertices[0];
barPath.GetPathToProgress(vertices, healthBarValue, Math.Max(glowBarValue, healthBarValue));
if (vertices.Count == 0) vertices.Add(Vector2.Zero);
initialVertex = vertices[0];
for (int i = 0; i < vertices.Count; i++)
vertices[i] -= initialVertex;

mainBar.Vertices = healthBarVertices.Select(v => v - healthBarVertices[0]).ToList();
mainBar.Position = healthBarVertices[0];
glowBar.Vertices = vertices;
glowBar.Position = initialVertex;

pathVerticesCache.Validate();
}
Expand All @@ -366,14 +328,17 @@ protected override void Dispose(bool isDisposing)

private partial class BackgroundPath : SmoothPath
{
private static readonly Color4 colour_white = Color4.White.Opacity(0.8f);
private static readonly Color4 colour_black = Color4.Black.Opacity(0.2f);

protected override Color4 ColourAt(float position)
{
if (position <= 0.16f)
return Color4.White.Opacity(0.8f);
return colour_white;

return Interpolation.ValueAt(position,
Color4.White.Opacity(0.8f),
Color4.Black.Opacity(0.2f),
colour_white,
colour_black,
-0.5f, 1f, Easing.OutQuint);
}
}
Expand Down Expand Up @@ -412,12 +377,14 @@ public Colour4 GlowColour

public float GlowPortion { get; init; }

private static readonly Colour4 transparent_black = Colour4.Black.Opacity(0.0f);

protected override Color4 ColourAt(float position)
{
if (position >= GlowPortion)
return BarColour;

return Interpolation.ValueAt(position, Colour4.Black.Opacity(0.0f), GlowColour, 0.0, GlowPortion, Easing.InQuint);
return Interpolation.ValueAt(position, transparent_black, GlowColour, 0.0, GlowPortion, Easing.InQuint);
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions osu.Game/Screens/Play/HUD/FailingLayer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,11 @@ private void updateState()

protected override void Update()
{
base.Update();

double target = Math.Clamp(max_alpha * (1 - Current.Value / low_health_threshold), 0, max_alpha);

boxes.Alpha = (float)Interpolation.Lerp(boxes.Alpha, target, Clock.ElapsedFrameTime * 0.01f);

base.Update();
}
}
}
Loading