1
0
mirror of https://github.com/ppy/osu.git synced 2024-09-21 20:07:25 +08:00

Merge pull request #26723 from EVAST9919/progress-bar-improvements

Reduce allocation overhead of song progress bars
This commit is contained in:
Dean Herbert 2024-01-29 02:44:16 +09:00 committed by GitHub
commit 8922190055
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 131 additions and 162 deletions

View File

@ -114,12 +114,7 @@ namespace osu.Game.Screens.Play.HUD
protected override void UpdateProgress(double progress, bool isIntro) protected override void UpdateProgress(double progress, bool isIntro)
{ {
bar.TrackTime = GameplayClock.CurrentTime; bar.Progress = isIntro ? 0 : progress;
if (isIntro)
bar.CurrentTime = 0;
else
bar.CurrentTime = FrameStableClock.CurrentTime;
} }
} }
} }

View File

@ -3,96 +3,59 @@
using System; using System;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Framework.Threading;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Graphics; using osu.Game.Graphics;
using osuTK; using osuTK;
namespace osu.Game.Screens.Play.HUD namespace osu.Game.Screens.Play.HUD
{ {
public partial class ArgonSongProgressBar : SliderBar<double> public partial class ArgonSongProgressBar : SongProgressBar
{ {
public Action<double>? OnSeek { get; set; }
// Parent will handle restricting the area of valid input. // Parent will handle restricting the area of valid input.
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
private readonly float barHeight; private readonly float barHeight;
private readonly RoundedBar playfieldBar; private readonly RoundedBar playfieldBar;
private readonly RoundedBar catchupBar; private readonly RoundedBar audioBar;
private readonly Box background; private readonly Box background;
private readonly ColourInfo mainColour; private readonly ColourInfo mainColour;
private ColourInfo catchUpColour; private ColourInfo catchUpColour;
public double StartTime public double Progress { get; set; }
{
private get => CurrentNumber.MinValue;
set => CurrentNumber.MinValue = value;
}
public double EndTime private double trackTime => (EndTime - StartTime) * Progress;
{
private get => CurrentNumber.MaxValue;
set => CurrentNumber.MaxValue = value;
}
public double CurrentTime
{
private get => CurrentNumber.Value;
set => CurrentNumber.Value = value;
}
public double TrackTime
{
private get => currentTrackTime.Value;
set => currentTrackTime.Value = value;
}
private double length => EndTime - StartTime;
private readonly BindableNumber<double> currentTrackTime;
public bool Interactive { get; set; }
public ArgonSongProgressBar(float barHeight) public ArgonSongProgressBar(float barHeight)
{ {
currentTrackTime = new BindableDouble();
setupAlternateValue();
StartTime = 0;
EndTime = 1;
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
Height = this.barHeight = barHeight; Height = this.barHeight = barHeight;
CornerRadius = 5; CornerRadius = 5;
Masking = true; Masking = true;
Children = new Drawable[] InternalChildren = new Drawable[]
{ {
background = new Box background = new Box
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Alpha = 0, Alpha = 0,
Colour = OsuColour.Gray(0.2f), Colour = OsuColour.Gray(0.2f),
Depth = float.MaxValue,
}, },
catchupBar = new RoundedBar audioBar = new RoundedBar
{ {
Name = "Audio bar", Name = "Audio bar",
Anchor = Anchor.BottomLeft, Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft, Origin = Anchor.BottomLeft,
CornerRadius = 5, CornerRadius = 5,
AlwaysPresent = true,
RelativeSizeAxes = Axes.Both RelativeSizeAxes = Axes.Both
}, },
playfieldBar = new RoundedBar playfieldBar = new RoundedBar
@ -107,24 +70,6 @@ namespace osu.Game.Screens.Play.HUD
}; };
} }
private void setupAlternateValue()
{
CurrentNumber.MaxValueChanged += v => currentTrackTime.MaxValue = v;
CurrentNumber.MinValueChanged += v => currentTrackTime.MinValue = v;
CurrentNumber.PrecisionChanged += v => currentTrackTime.Precision = v;
}
private float normalizedReference
{
get
{
if (EndTime - StartTime == 0)
return 1;
return (float)((TrackTime - StartTime) / length);
}
}
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load(OsuColour colours)
{ {
@ -153,47 +98,28 @@ namespace osu.Game.Screens.Play.HUD
base.OnHoverLost(e); base.OnHoverLost(e);
} }
protected override void UpdateValue(float value)
{
// Handled in Update
}
protected override void Update() protected override void Update()
{ {
base.Update(); base.Update();
playfieldBar.Length = (float)Interpolation.Lerp(playfieldBar.Length, NormalizedValue, Math.Clamp(Time.Elapsed / 40, 0, 1)); playfieldBar.Length = (float)Interpolation.Lerp(playfieldBar.Length, Progress, Math.Clamp(Time.Elapsed / 40, 0, 1));
catchupBar.Length = (float)Interpolation.Lerp(catchupBar.Length, normalizedReference, Math.Clamp(Time.Elapsed / 40, 0, 1)); audioBar.Length = (float)Interpolation.Lerp(audioBar.Length, AudioProgress, Math.Clamp(Time.Elapsed / 40, 0, 1));
if (TrackTime < CurrentTime) if (trackTime > AudioTime)
ChangeChildDepth(catchupBar, -1); ChangeInternalChildDepth(audioBar, -1);
else else
ChangeChildDepth(catchupBar, 0); ChangeInternalChildDepth(audioBar, 1);
float timeDelta = (float)(Math.Abs(CurrentTime - TrackTime)); float timeDelta = (float)Math.Abs(AudioTime - trackTime);
const float colour_transition_threshold = 20000; const float colour_transition_threshold = 20000;
catchupBar.AccentColour = Interpolation.ValueAt( audioBar.AccentColour = Interpolation.ValueAt(
Math.Min(timeDelta, colour_transition_threshold), Math.Min(timeDelta, colour_transition_threshold),
mainColour, mainColour,
catchUpColour, catchUpColour,
0, colour_transition_threshold, 0, colour_transition_threshold,
Easing.OutQuint); Easing.OutQuint);
catchupBar.Alpha = Math.Max(1, catchupBar.Length);
}
private ScheduledDelegate? scheduledSeek;
protected override void OnUserChange(double value)
{
scheduledSeek?.Cancel();
scheduledSeek = Schedule(() =>
{
if (Interactive)
OnSeek?.Invoke(value);
});
} }
private partial class RoundedBar : Container private partial class RoundedBar : Container

View File

@ -98,12 +98,7 @@ namespace osu.Game.Screens.Play.HUD
protected override void UpdateProgress(double progress, bool isIntro) protected override void UpdateProgress(double progress, bool isIntro)
{ {
bar.CurrentTime = GameplayClock.CurrentTime; graph.Progress = isIntro ? 0 : (int)(graph.ColumnCount * progress);
if (isIntro)
graph.Progress = 0;
else
graph.Progress = (int)(graph.ColumnCount * progress);
} }
protected override void Update() protected override void Update()

View File

@ -7,71 +7,27 @@ using osuTK.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Framework.Threading;
namespace osu.Game.Screens.Play.HUD namespace osu.Game.Screens.Play.HUD
{ {
public partial class DefaultSongProgressBar : SliderBar<double> public partial class DefaultSongProgressBar : SongProgressBar
{ {
/// <summary>
/// Action which is invoked when a seek is requested, with the proposed millisecond value for the seek operation.
/// </summary>
public Action<double>? OnSeek { get; set; }
/// <summary>
/// Whether the progress bar should allow interaction, ie. to perform seek operations.
/// </summary>
public bool Interactive
{
get => showHandle;
set
{
if (value == showHandle)
return;
showHandle = value;
handleBase.FadeTo(showHandle ? 1 : 0, 200);
}
}
public Color4 FillColour public Color4 FillColour
{ {
set => fill.Colour = value; set => fill.Colour = value;
} }
public double StartTime
{
set => CurrentNumber.MinValue = value;
}
public double EndTime
{
set => CurrentNumber.MaxValue = value;
}
public double CurrentTime
{
set => CurrentNumber.Value = value;
}
private readonly Box fill; private readonly Box fill;
private readonly Container handleBase; private readonly Container handleBase;
private readonly Container handleContainer; private readonly Container handleContainer;
private bool showHandle;
public DefaultSongProgressBar(float barHeight, float handleBarHeight, Vector2 handleSize) public DefaultSongProgressBar(float barHeight, float handleBarHeight, Vector2 handleSize)
{ {
CurrentNumber.MinValue = 0;
CurrentNumber.MaxValue = 1;
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
Height = barHeight + handleBarHeight + handleSize.Y; Height = barHeight + handleBarHeight + handleSize.Y;
Children = new Drawable[] InternalChildren = new Drawable[]
{ {
new Box new Box
{ {
@ -130,9 +86,14 @@ namespace osu.Game.Screens.Play.HUD
}; };
} }
protected override void UpdateValue(float value) public override bool Interactive
{ {
// handled in update get => base.Interactive;
set
{
base.Interactive = value;
handleBase.FadeTo(value ? 1 : 0, 200);
}
} }
protected override void Update() protected override void Update()
@ -140,22 +101,10 @@ namespace osu.Game.Screens.Play.HUD
base.Update(); base.Update();
handleBase.Height = Height - handleContainer.Height; handleBase.Height = Height - handleContainer.Height;
float newX = (float)Interpolation.Lerp(handleBase.X, NormalizedValue * UsableWidth, Math.Clamp(Time.Elapsed / 40, 0, 1)); float newX = (float)Interpolation.Lerp(handleBase.X, AudioProgress * DrawWidth, Math.Clamp(Time.Elapsed / 40, 0, 1));
fill.Width = newX; fill.Width = newX;
handleBase.X = newX; handleBase.X = newX;
} }
private ScheduledDelegate? scheduledSeek;
protected override void OnUserChange(double value)
{
scheduledSeek?.Cancel();
scheduledSeek = Schedule(() =>
{
if (showHandle)
OnSeek?.Invoke(value);
});
}
} }
} }

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
@ -70,7 +71,13 @@ namespace osu.Game.Screens.Play.HUD
protected double LastHitTime { get; private set; } protected double LastHitTime { get; private set; }
/// <summary>
/// Called every update frame with current progress information.
/// </summary>
/// <param name="progress">Current (visual) progress through the beatmap (0..1).</param>
/// <param name="isIntro">If <c>true</c>, progress is (0..1) through the intro.</param>
protected abstract void UpdateProgress(double progress, bool isIntro); protected abstract void UpdateProgress(double progress, bool isIntro);
protected virtual void UpdateObjects(IEnumerable<HitObject> objects) { } protected virtual void UpdateObjects(IEnumerable<HitObject> objects) { }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
@ -96,7 +103,7 @@ namespace osu.Game.Screens.Play.HUD
if (objects == null) if (objects == null)
return; return;
double currentTime = FrameStableClock.CurrentTime; double currentTime = Math.Min(FrameStableClock.CurrentTime, LastHitTime);
bool isInIntro = currentTime < FirstHitTime; bool isInIntro = currentTime < FirstHitTime;

View File

@ -0,0 +1,97 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Allocation;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Events;
using osu.Framework.Threading;
using osuTK;
namespace osu.Game.Screens.Play.HUD
{
public abstract partial class SongProgressBar : CompositeDrawable
{
/// <summary>
/// The current seek position of the audio, on a (0..1) range.
/// This is generally the seek target, which will eventually match the gameplay clock when it catches up.
/// </summary>
protected double AudioProgress => length == 0 ? 1 : AudioTime / length;
/// <summary>
/// The current (non-frame-stable) audio time.
/// </summary>
protected double AudioTime => Math.Clamp(GameplayClock.CurrentTime - StartTime, 0.0, length);
[Resolved]
protected IGameplayClock GameplayClock { get; private set; } = null!;
/// <summary>
/// Action which is invoked when a seek is requested, with the proposed millisecond value for the seek operation.
/// </summary>
public Action<double>? OnSeek { get; set; }
/// <summary>
/// Whether the progress bar should allow interaction, ie. to perform seek operations.
/// </summary>
public virtual bool Interactive { get; set; }
public double StartTime { get; set; }
public double EndTime { get; set; } = 1.0;
private double length => EndTime - StartTime;
private bool handleClick;
protected override bool OnMouseDown(MouseDownEvent e)
{
handleClick = true;
return base.OnMouseDown(e);
}
protected override bool OnClick(ClickEvent e)
{
if (handleClick)
handleMouseInput(e);
return true;
}
protected override void OnDrag(DragEvent e)
{
handleMouseInput(e);
}
protected override bool OnDragStart(DragStartEvent e)
{
Vector2 posDiff = e.MouseDownPosition - e.MousePosition;
if (Math.Abs(posDiff.X) < Math.Abs(posDiff.Y))
{
handleClick = false;
return false;
}
handleMouseInput(e);
return true;
}
private void handleMouseInput(UIEvent e)
{
if (!Interactive)
return;
double relativeX = Math.Clamp(ToLocalSpace(e.ScreenSpaceMousePosition).X / DrawWidth, 0, 1);
onUserChange(StartTime + (EndTime - StartTime) * relativeX);
}
private ScheduledDelegate? scheduledSeek;
private void onUserChange(double value)
{
scheduledSeek?.Cancel();
scheduledSeek = Schedule(() => OnSeek?.Invoke(value));
}
}
}