Skip to content

Commit

Permalink
feat: 💄 Enhance metrics display
Browse files Browse the repository at this point in the history
  • Loading branch information
esnya committed Jan 5, 2025
1 parent cc5641d commit 34bde4f
Show file tree
Hide file tree
Showing 6 changed files with 305 additions and 104 deletions.
2 changes: 1 addition & 1 deletion ResoniteMetricsCounter/Patch/Metric_Profiler_Patch.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using FrooxEngine;
using FrooxEngine;
using FrooxEngine.ProtoFlux;
using HarmonyLib;
using ResoniteModLoader;
Expand Down
63 changes: 29 additions & 34 deletions ResoniteMetricsCounter/UIX/Item/MetricItemBase.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
using Elements.Core;
using FrooxEngine;
using FrooxEngine.UIX;
using System.Diagnostics;
using ResoniteMetricsCounter.UIX.Pages;
using System.Collections.Generic;
using System.Runtime.CompilerServices;

namespace ResoniteMetricsCounter.UIX.Item;
Expand All @@ -12,18 +13,21 @@ internal abstract class MetricItemBase<T>
private const float DEFAULT_PADDING = 4;

private readonly Slot slot;
private readonly Sync<string> labelField, timeField;
private readonly Sync<colorX> metricTint;
private readonly ReferenceProxySource referenceProxySource;
private readonly RectTransform metricRect;
private readonly Sync<string> percentageField;

protected readonly List<Sync<string>> LabelFields = new();
protected abstract List<IMetricsPage.ColumnDefinition> Columns { get; }

public MetricItemBase(Slot container)
{
var uiBuilder = new UIBuilder(container);

uiBuilder.Style.MinHeight = DEFAULT_ITEM_SIZE;
uiBuilder.Style.TextAutoSizeMin = 0;
uiBuilder.Style.TextAutoSizeMax = 24;
uiBuilder.Style.TextColor = RadiantUI_Constants.TEXT_COLOR;

slot = uiBuilder.Panel(RadiantUI_Constants.Neutrals.DARK).Slot;

Expand All @@ -35,42 +39,39 @@ public MetricItemBase(Slot container)
metricTint = metricImage.Tint;

uiBuilder.HorizontalLayout(DEFAULT_PADDING);
uiBuilder.Style.FlexibleWidth = 1.0f;

var labelText = uiBuilder.Text(null, bestFit: true, alignment: Alignment.MiddleLeft);
labelText.RectTransform.AnchorMax.Value = new float2(0.8f, 1.0f);
labelText.Color.Value = RadiantUI_Constants.TEXT_COLOR;
labelField = labelText.Content;
uiBuilder.Style.ForceExpandWidth = uiBuilder.Style.ForceExpandHeight = false;

uiBuilder.Style.PreferredWidth = uiBuilder.Style.MinWidth = DEFAULT_ITEM_SIZE * 3;
uiBuilder.Style.FlexibleWidth = -1.0f;
var timeText = uiBuilder.Text(null, bestFit: false, size: 24, alignment: Alignment.MiddleRight);
timeText.ParseRichText.Value = false;
timeText.Color.Value = RadiantUI_Constants.TEXT_COLOR;
timeField = timeText.Content;
LabelFields.Capacity = Columns.Count;
for (int i = 0; i < Columns.Count; i++)
{
var column = Columns[i];
uiBuilder.Style.FlexibleWidth = column.FlexWidth;
uiBuilder.Style.MinWidth = column.MinWidth;
LabelFields.Add(uiBuilder.Text(column.Label, alignment: column.Alignment).Content);
}

var percentageText = uiBuilder.Text(null, bestFit: false, size: 24, alignment: Alignment.MiddleRight);
percentageText.ParseRichText.Value = false;
percentageText.Color.Value = RadiantUI_Constants.TEXT_COLOR;
percentageField = percentageText.Content;
//uiBuilder.PushStyle();
//uiBuilder.Style.FlexibleWidth = -1;
//uiBuilder.Style.PreferredWidth = uiBuilder.Style.MinWidth = DEFAULT_ITEM_SIZE;

uiBuilder.Style.PreferredWidth = uiBuilder.Style.MinWidth = DEFAULT_ITEM_SIZE;
//var deactivateButton = uiBuilder.Button(OfficialAssets.Common.Icons.Cross, RadiantUI_Constants.Hero.RED);
//deactivateButton.LocalPressed += (_, _) => slot.Destroy();

var deactivateButton = uiBuilder.Button(OfficialAssets.Common.Icons.Cross, RadiantUI_Constants.Hero.RED);
deactivateButton.LocalPressed += (_, _) => slot.Destroy();
//uiBuilder.PopStyle();
}


[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected abstract long GetTicks(in T metric);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected abstract string? GetLabel(in T metric);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected abstract IWorldElement? GetReference(in T metric);

public bool Update(in T metric, long maxTicks, long elapsedTicks)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected abstract void UpdateColumn(in T metric, Sync<string> column, int i, long maxTicks, long elapsedTicks, long frameCount);

public bool Update(in T metric, long maxTicks, long elapsedTicks, long frameCount)
{
if (slot.IsDisposed)
{
Expand All @@ -79,19 +80,13 @@ public bool Update(in T metric, long maxTicks, long elapsedTicks)

var ticks = GetTicks(metric);
var maxRatio = (float)ticks / maxTicks;

slot.OrderOffset = -ticks;
var label = GetLabel(metric);
if (label is null)

for (int i = 0; i < LabelFields.Count; i++)
{
return false;
UpdateColumn(metric, LabelFields[i], i, maxTicks, elapsedTicks, frameCount);
}

labelField.Value = label;

var doubleTicks = (double)ticks;
timeField.Value = $"{1000.0 * doubleTicks / Stopwatch.Frequency:0.0}ms";
percentageField.Value = $"{doubleTicks / elapsedTicks:P3}";
metricTint.Value = MathX.Lerp(RadiantUI_Constants.DarkLight.GREEN, RadiantUI_Constants.DarkLight.RED, maxRatio);
metricRect.AnchorMax.Value = new float2(maxRatio, 1.0f);

Expand Down
164 changes: 149 additions & 15 deletions ResoniteMetricsCounter/UIX/MetricsPanel.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Elements.Core;
using Elements.Core;
using FrooxEngine;
using FrooxEngine.UIX;
using ResoniteMetricsCounter.Metrics;
Expand All @@ -10,7 +10,6 @@
namespace ResoniteMetricsCounter.UIX;
public sealed class MetricsPanel
{

private readonly List<KeyValuePair<string, IMetricsPage>> pages = new()
{
new("Detailed", new DetailedMetricsPanelPage()),
Expand All @@ -19,14 +18,24 @@ public sealed class MetricsPanel

public const float DEFAULTITEMSIZE = 32;
public const float PADDING = 4;
public const float DEFAULTSEPARATION = 0.1f;

private readonly MetricsCounter metricsCounter;
private readonly Slot slot;
private readonly Sync<string> statisticsField;
private readonly int maxItems;
private readonly Slot? pagesButtonContainer;
private readonly Slot? pagesContainer;

private Sync<string>? framesField;
private Sync<string>? elapsedTimeField;
private Sync<string>? totalTimeField;
private Sync<string>? maxTimeField;
private Sync<string>? countField;
private Sync<string>? frameIntervalField;
private Sync<string>? avgTotalTimeField;
private Sync<string>? avgMaxTimeField;
private Sync<string>? fpsField;

public MetricsPanel(Slot slot, MetricsCounter metricsCounter, in float2 size, int maxItems)
{
if (slot is null)
Expand All @@ -46,10 +55,16 @@ public MetricsPanel(Slot slot, MetricsCounter metricsCounter, in float2 size, in
this.slot = uiBuilder.Root;
metricsCounter.IgnoreHierarchy(slot);

uiBuilder.Style.MinHeight = DEFAULTITEMSIZE;
uiBuilder.Style.ForceExpandHeight = false;
uiBuilder.Style.TextColor = RadiantUI_Constants.TEXT_COLOR;
uiBuilder.Style.TextAutoSizeMin = 0;
uiBuilder.Style.TextAutoSizeMax = 24;

uiBuilder.VerticalLayout(PADDING, forceExpandHeight: false);

BuildStopButtonUI(uiBuilder);
statisticsField = BuildStatisticsUI(uiBuilder);
BuildHeaderUI(uiBuilder);

var activePage = pages[0].Key;
pagesButtonContainer = uiBuilder.HorizontalLayout(PADDING).Slot;
Expand Down Expand Up @@ -84,9 +99,6 @@ private static UIBuilder CreatePanel(in Slot slot, in float2 size)
slot.LocalScale = float3.One * 0.00075f;

var uiBuilder = RadiantUI_Panel.SetupPanel(slot, "Metrics", size, pinButton: true);
uiBuilder.Style.TextAutoSizeMin = 0;
uiBuilder.Style.MinHeight = DEFAULTITEMSIZE;
uiBuilder.Style.ForceExpandHeight = false;

return uiBuilder;
}
Expand All @@ -105,12 +117,84 @@ private void BuildStopButtonUI(in UIBuilder uiBuilder)
};
}

private static Sync<string> BuildStatisticsUI(in UIBuilder uiBuilder)
private void BuildHeaderUI(UIBuilder uiBuilder)
{
var statisticsText = uiBuilder.Text("Profiling", bestFit: true, alignment: Alignment.MiddleLeft);
statisticsText.Color.Value = RadiantUI_Constants.TEXT_COLOR;
statisticsText.Size.Value = 24;
return statisticsText.Content;
uiBuilder.PushStyle();
uiBuilder.Style.TextAlignment = Alignment.MiddleLeft;

const float spacing = 0.25f;

uiBuilder.HorizontalLayout();
uiBuilder.HorizontalElementWithLabel("Frames:", spacing, () =>
{
var text = uiBuilder.Text("0");
framesField = text.Content;
return text;
});
uiBuilder.HorizontalElementWithLabel("Avg:", spacing, () =>
{
var text = uiBuilder.Text("0.00FPS");
fpsField = text.Content;
return text;
});
uiBuilder.NestOut();

uiBuilder.HorizontalLayout();
uiBuilder.HorizontalElementWithLabel("Elapsed:", spacing, () =>
{
var text = uiBuilder.Text("0.00ms");
elapsedTimeField = text.Content;
return text;
});
uiBuilder.HorizontalElementWithLabel("Avg:", spacing, () =>
{
var text = uiBuilder.Text("0.00ms");
frameIntervalField = text.Content;
return text;
});
uiBuilder.NestOut();

uiBuilder.HorizontalLayout();
uiBuilder.HorizontalElementWithLabel("Sum:", spacing, () =>
{
var text = uiBuilder.Text("0.00ms");
totalTimeField = text.Content;
return text;
});
uiBuilder.HorizontalElementWithLabel("Avg:", spacing, () =>
{
var text = uiBuilder.Text("0.00ms");
avgTotalTimeField = text.Content;
return text;
});
uiBuilder.NestOut();

uiBuilder.HorizontalLayout();
uiBuilder.HorizontalElementWithLabel("Max:", spacing, () =>
{
var text = uiBuilder.Text("0.00ms");
maxTimeField = text.Content;
return text;
});
uiBuilder.HorizontalElementWithLabel("Avg:", spacing, () =>
{
var text = uiBuilder.Text("0.00ms");
avgMaxTimeField = text.Content;
return text;
});
uiBuilder.NestOut();

uiBuilder.HorizontalLayout();
uiBuilder.HorizontalElementWithLabel("Count:", spacing, () =>
{
var text = uiBuilder.Text("0");
countField = text.Content;
return text;
});
uiBuilder.Spacer(0.5f);
uiBuilder.NestOut();

uiBuilder.PopStyle();
}

private void BuildPageButtonUI(in UIBuilder uiBuilder, string label, bool defaultActive)
Expand Down Expand Up @@ -171,12 +255,62 @@ public void Update()
return;
}

if (statisticsField.IsDisposed)
metricsCounter.OnUpdate();

var frames = metricsCounter.FrameCount;

if (framesField is not null && !framesField.IsDisposed)
{
return;
framesField.Value = $"{frames}";
}

var elapsedTime = metricsCounter.ElapsedMilliseconds;

if (fpsField is not null && !fpsField.IsDisposed)
{
fpsField.Value = $"{1000 * frames / elapsedTime:0.0}FPS";
}

if (elapsedTimeField is not null && !elapsedTimeField.IsDisposed)
{
elapsedTimeField.Value = $"{elapsedTime}ms";
}

if (frameIntervalField is not null && !frameIntervalField.IsDisposed)
{
frameIntervalField.Value = $"{elapsedTime / frames}ms";
}

statisticsField.Value = $"Elapsed:\t{metricsCounter.ElapsedMilliseconds:0.00}ms<br>Total:\t{1000.0 * metricsCounter.ByElement.Total / Stopwatch.Frequency:0.00}ms<br>Max:\t{1000.0 * metricsCounter.ByElement.Max / Stopwatch.Frequency:0.00}ms<br>Entities:\t{metricsCounter.ByElement.Count}";
var totalTime = 1000.0 * metricsCounter.ByElement.Total / Stopwatch.Frequency;

if (totalTimeField is not null && !totalTimeField.IsDisposed)
{
totalTimeField.Value = $"{totalTime:0.00}ms";
}

if (avgTotalTimeField is not null && !avgTotalTimeField.IsDisposed)
{
avgTotalTimeField.Value = $"{totalTime / frames:0.00}ms";
}


var maxTime = 1000.0 * metricsCounter.ByElement.Max / Stopwatch.Frequency;

if (avgMaxTimeField is not null && !avgMaxTimeField.IsDisposed)
{
avgMaxTimeField.Value = $"{maxTime / frames:0.00}ms";
}

if (maxTimeField is not null && !maxTimeField.IsDisposed)
{
maxTimeField.Value = $"{maxTime:0.00}ms";
}


if (countField is not null && !countField.IsDisposed)
{
countField.Value = $"{metricsCounter.ByElement.Count}";
}

foreach (var page in pages)
{
Expand Down
Loading

0 comments on commit 34bde4f

Please sign in to comment.