1
0
Fork 0
mirror of https://github.com/Ellpeck/MLEM.git synced 2024-11-26 06:28:35 +01:00

Added UiMetrics

This commit is contained in:
Ell 2021-12-12 12:32:09 +01:00
parent 55fae16768
commit 60dfbb1ec5
5 changed files with 164 additions and 8 deletions

View file

@ -38,6 +38,7 @@ Additions
- Added a multiline editing mode to TextField - Added a multiline editing mode to TextField
- Added a formatting code to allow for inline font changes - Added a formatting code to allow for inline font changes
- Added a SquishingGroup element - Added a SquishingGroup element
- Added UiMetrics
Improvements Improvements
- **Made Image ScaleToImage take ui scale into account** - **Made Image ScaleToImage take ui scale into account**

View file

@ -18,6 +18,8 @@ namespace Demos {
private double fpsTime; private double fpsTime;
private int lastFps; private int lastFps;
private int fpsCounter; private int fpsCounter;
private UiMetrics cumulativeMetrics;
private TimeSpan secondCounter;
static GameImpl() { static GameImpl() {
Demos.Add("Ui", ("An in-depth demonstration of the MLEM.Ui package and its abilities", game => new UiDemo(game))); Demos.Add("Ui", ("An in-depth demonstration of the MLEM.Ui package and its abilities", game => new UiDemo(game)));
@ -29,6 +31,16 @@ namespace Demos {
public GameImpl() { public GameImpl() {
this.IsMouseVisible = true; this.IsMouseVisible = true;
// print out ui metrics every second
this.OnDraw += (g, time) => {
this.cumulativeMetrics += this.UiSystem.Metrics;
this.secondCounter += time.ElapsedGameTime;
if (this.secondCounter.TotalSeconds >= 1) {
this.secondCounter -= TimeSpan.FromSeconds(1);
Console.WriteLine($"Metrics/s: {this.cumulativeMetrics}");
this.cumulativeMetrics = new UiMetrics();
}
};
} }
protected override void LoadContent() { protected override void LoadContent() {

View file

@ -546,6 +546,7 @@ namespace MLEM.Ui.Elements {
// which would cause our ForceUpdateArea code to be run twice, so we only update our parent instead // which would cause our ForceUpdateArea code to be run twice, so we only update our parent instead
if (this.Parent != null && this.Parent.UpdateAreaIfDirty()) if (this.Parent != null && this.Parent.UpdateAreaIfDirty())
return; return;
this.System.Stopwatch.Restart();
var parentArea = this.Parent != null ? this.Parent.ChildPaddedArea : (RectangleF) this.system.Viewport; var parentArea = this.Parent != null ? this.Parent.ChildPaddedArea : (RectangleF) this.system.Viewport;
var parentCenterX = parentArea.X + parentArea.Width / 2; var parentCenterX = parentArea.X + parentArea.Width / 2;
@ -555,6 +556,10 @@ namespace MLEM.Ui.Elements {
var recursion = 0; var recursion = 0;
UpdateDisplayArea(actualSize); UpdateDisplayArea(actualSize);
this.System.Stopwatch.Stop();
this.System.Metrics.ForceAreaUpdateTime += this.System.Stopwatch.Elapsed;
this.System.Metrics.ForceAreaUpdates++;
void UpdateDisplayArea(Vector2 newSize) { void UpdateDisplayArea(Vector2 newSize) {
var pos = new Vector2(); var pos = new Vector2();
switch (this.anchor) { switch (this.anchor) {
@ -706,6 +711,7 @@ namespace MLEM.Ui.Elements {
this.System.InvokeOnElementAreaUpdated(this); this.System.InvokeOnElementAreaUpdated(this);
foreach (var child in this.Children) foreach (var child in this.Children)
child.ForceUpdateArea(); child.ForceUpdateArea();
this.System.Metrics.ActualAreaUpdates++;
} }
/// <summary> /// <summary>
@ -885,6 +891,8 @@ namespace MLEM.Ui.Elements {
foreach (var child in this.GetRelevantChildren()) foreach (var child in this.GetRelevantChildren())
if (child.System != null) if (child.System != null)
child.Update(time); child.Update(time);
this.System.Metrics.Updates++;
} }
/// <summary> /// <summary>
@ -914,6 +922,7 @@ namespace MLEM.Ui.Elements {
} }
// draw content in custom begin call // draw content in custom begin call
this.Draw(time, batch, alpha, blendState, samplerState, depthStencilState, effect, mat); this.Draw(time, batch, alpha, blendState, samplerState, depthStencilState, effect, mat);
this.System.Metrics.Draws++;
if (customDraw) { if (customDraw) {
// end our draw // end our draw
batch.End(); batch.End();

107
MLEM.Ui/UiMetrics.cs Normal file
View file

@ -0,0 +1,107 @@
using System;
using Microsoft.Xna.Framework.Graphics;
using MLEM.Ui.Elements;
namespace MLEM.Ui {
/// <summary>
/// A snapshot of update and rendering statistics from <see cref="UiSystem.Metrics"/> to be used for runtime debugging and profiling.
/// This metrics struct works similarly to <see cref="GraphicsMetrics"/>.
/// </summary>
public struct UiMetrics {
/// <summary>
/// The amount of time that <see cref="Element.ForceUpdateArea"/> took.
/// Can be divided by <see cref="ForceAreaUpdates"/> to get an average per area update.
/// </summary>
public TimeSpan ForceAreaUpdateTime { get; internal set; }
/// <summary>
/// The amount of time that <see cref="Element.Update"/> took.
/// Can be divided by <see cref="Updates"/> to get an average per update.
/// </summary>
public TimeSpan UpdateTime { get; internal set; }
/// <summary>
/// The amount of times that <see cref="Element.ForceUpdateArea"/> was called.
/// </summary>
public uint ForceAreaUpdates { get; internal set; }
/// <summary>
/// The amount of times that <see cref="Element.SetAreaAndUpdateChildren"/> was called.
/// </summary>
public uint ActualAreaUpdates { get; internal set; }
/// <summary>
/// The amount of times that <see cref="Element.Update"/> was called.
/// </summary>
public uint Updates { get; internal set; }
/// <summary>
/// The amount of time that <see cref="Element.Draw"/> took.
/// Can be divided by <see cref="Draws"/> to get an average per draw.
/// </summary>
public TimeSpan DrawTime { get; internal set; }
/// <summary>
/// The amount of times that <see cref="Element.Draw"/> was called.
/// </summary>
public uint Draws { get; internal set; }
/// <summary>
/// Resets all update-related metrics to 0.
/// </summary>
public void ResetUpdates() {
this.ForceAreaUpdateTime = TimeSpan.Zero;
this.UpdateTime = TimeSpan.Zero;
this.ForceAreaUpdates = 0;
this.ActualAreaUpdates = 0;
this.Updates = 0;
}
/// <summary>
/// Resets all rendering-related metrics to 0.
/// </summary>
public void ResetDraws() {
this.DrawTime = TimeSpan.Zero;
this.Draws = 0;
}
/// <summary>Returns the fully qualified type name of this instance.</summary>
/// <returns>The fully qualified type name.</returns>
public override string ToString() {
return $"{nameof(this.ForceAreaUpdateTime)}: {this.ForceAreaUpdateTime}, {nameof(this.UpdateTime)}: {this.UpdateTime}, {nameof(this.ForceAreaUpdates)}: {this.ForceAreaUpdates}, {nameof(this.ActualAreaUpdates)}: {this.ActualAreaUpdates}, {nameof(this.Updates)}: {this.Updates}, {nameof(this.DrawTime)}: {this.DrawTime}, {nameof(this.Draws)}: {this.Draws}";
}
/// <summary>
/// Adds two ui metrics together, causing all of their values to be combined.
/// </summary>
/// <param name="left">The left metrics</param>
/// <param name="right">The right metrics</param>
/// <returns>The sum of both metrics</returns>
public static UiMetrics operator +(UiMetrics left, UiMetrics right) {
return new UiMetrics {
ForceAreaUpdateTime = left.ForceAreaUpdateTime + right.ForceAreaUpdateTime,
UpdateTime = left.UpdateTime + right.UpdateTime,
ForceAreaUpdates = left.ForceAreaUpdates + right.ForceAreaUpdates,
ActualAreaUpdates = left.ActualAreaUpdates + right.ActualAreaUpdates,
Updates = left.Updates + right.Updates,
DrawTime = left.DrawTime + right.DrawTime,
Draws = left.Draws + right.Draws
};
}
/// <summary>
/// Subtracts two ui metrics from each other, causing their values to be subtracted.
/// </summary>
/// <param name="left">The left metrics</param>
/// <param name="right">The right metrics</param>
/// <returns>The difference of both metrics</returns>
public static UiMetrics operator -(UiMetrics left, UiMetrics right) {
return new UiMetrics {
ForceAreaUpdateTime = left.ForceAreaUpdateTime - right.ForceAreaUpdateTime,
UpdateTime = left.UpdateTime - right.UpdateTime,
ForceAreaUpdates = left.ForceAreaUpdates - right.ForceAreaUpdates,
ActualAreaUpdates = left.ActualAreaUpdates - right.ActualAreaUpdates,
Updates = left.Updates - right.Updates,
DrawTime = left.DrawTime - right.DrawTime,
Draws = left.Draws - right.Draws
};
}
}
}

View file

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
@ -20,8 +21,6 @@ namespace MLEM.Ui {
/// </summary> /// </summary>
public class UiSystem : GameComponent { public class UiSystem : GameComponent {
private readonly List<RootElement> rootElements = new List<RootElement>();
/// <summary> /// <summary>
/// The viewport that this ui system is rendering inside of. /// The viewport that this ui system is rendering inside of.
/// This is automatically updated during <see cref="GameWindow.ClientSizeChanged"/> /// This is automatically updated during <see cref="GameWindow.ClientSizeChanged"/>
@ -36,7 +35,6 @@ namespace MLEM.Ui {
/// If <see cref="AutoScaleWithScreen"/> is true, this is used as the screen size that uses the default <see cref="GlobalScale"/> /// If <see cref="AutoScaleWithScreen"/> is true, this is used as the screen size that uses the default <see cref="GlobalScale"/>
/// </summary> /// </summary>
public Point AutoScaleReferenceSize; public Point AutoScaleReferenceSize;
private float globalScale = 1;
/// <summary> /// <summary>
/// The global rendering scale of this ui system and all of its child elements. /// The global rendering scale of this ui system and all of its child elements.
/// If <see cref="AutoScaleWithScreen"/> is true, this scale will be different based on the window size. /// If <see cref="AutoScaleWithScreen"/> is true, this scale will be different based on the window size.
@ -53,8 +51,6 @@ namespace MLEM.Ui {
root.Element.ForceUpdateArea(); root.Element.ForceUpdateArea();
} }
} }
private UiStyle style;
/// <summary> /// <summary>
/// The style options that this ui system and all of its elements use. /// The style options that this ui system and all of its elements use.
/// To set the default, untextured style, use <see cref="UntexturedStyle"/>. /// To set the default, untextured style, use <see cref="UntexturedStyle"/>.
@ -102,6 +98,11 @@ namespace MLEM.Ui {
/// The ui controls are also the place to change bindings for controller and keyboard input. /// The ui controls are also the place to change bindings for controller and keyboard input.
/// </summary> /// </summary>
public UiControls Controls; public UiControls Controls;
/// <summary>
/// The update and rendering statistics to be used for runtime debugging and profiling.
/// The metrics are reset accordingly every frame: <see cref="UiMetrics.ResetUpdates"/> is called at the start of <see cref="Update"/>, and <see cref="UiMetrics.ResetDraws"/> is called at the start of <see cref="DrawEarly"/>, or at the start of <see cref="Draw"/> if <see cref="DrawEarly"/> was not called.
/// </summary>
public UiMetrics Metrics;
/// <summary> /// <summary>
/// Event that is invoked after an <see cref="Element"/> is drawn, but before its children are drawn. /// Event that is invoked after an <see cref="Element"/> is drawn, but before its children are drawn.
@ -172,6 +173,13 @@ namespace MLEM.Ui {
/// </summary> /// </summary>
public event RootCallback OnRootRemoved; public event RootCallback OnRootRemoved;
internal readonly Stopwatch Stopwatch = new Stopwatch();
private readonly List<RootElement> rootElements = new List<RootElement>();
private float globalScale = 1;
private bool drewEarly;
private UiStyle style;
/// <summary> /// <summary>
/// Creates a new ui system with the given settings. /// Creates a new ui system with the given settings.
/// </summary> /// </summary>
@ -232,11 +240,15 @@ namespace MLEM.Ui {
/// </summary> /// </summary>
/// <param name="time">The game's time</param> /// <param name="time">The game's time</param>
public override void Update(GameTime time) { public override void Update(GameTime time) {
this.Controls.Update(); this.Metrics.ResetUpdates();
this.Stopwatch.Restart();
for (var i = this.rootElements.Count - 1; i >= 0; i--) { this.Controls.Update();
for (var i = this.rootElements.Count - 1; i >= 0; i--)
this.rootElements[i].Element.Update(time); this.rootElements[i].Element.Update(time);
}
this.Stopwatch.Stop();
this.Metrics.UpdateTime += this.Stopwatch.Elapsed;
} }
/// <summary> /// <summary>
@ -246,10 +258,17 @@ namespace MLEM.Ui {
/// <param name="time">The game's time</param> /// <param name="time">The game's time</param>
/// <param name="batch">The sprite batch to use for drawing</param> /// <param name="batch">The sprite batch to use for drawing</param>
public void DrawEarly(GameTime time, SpriteBatch batch) { public void DrawEarly(GameTime time, SpriteBatch batch) {
this.Metrics.ResetDraws();
this.Stopwatch.Restart();
foreach (var root in this.rootElements) { foreach (var root in this.rootElements) {
if (!root.Element.IsHidden) if (!root.Element.IsHidden)
root.Element.DrawEarly(time, batch, this.DrawAlpha * root.Element.DrawAlpha, this.BlendState, this.SamplerState, this.DepthStencilState, this.Effect, root.Transform); root.Element.DrawEarly(time, batch, this.DrawAlpha * root.Element.DrawAlpha, this.BlendState, this.SamplerState, this.DepthStencilState, this.Effect, root.Transform);
} }
this.Stopwatch.Stop();
this.Metrics.DrawTime += this.Stopwatch.Elapsed;
this.drewEarly = true;
} }
/// <summary> /// <summary>
@ -259,6 +278,10 @@ namespace MLEM.Ui {
/// <param name="time">The game's time</param> /// <param name="time">The game's time</param>
/// <param name="batch">The sprite batch to use for drawing</param> /// <param name="batch">The sprite batch to use for drawing</param>
public void Draw(GameTime time, SpriteBatch batch) { public void Draw(GameTime time, SpriteBatch batch) {
if (!this.drewEarly)
this.Metrics.ResetDraws();
this.Stopwatch.Restart();
foreach (var root in this.rootElements) { foreach (var root in this.rootElements) {
if (root.Element.IsHidden) if (root.Element.IsHidden)
continue; continue;
@ -267,6 +290,10 @@ namespace MLEM.Ui {
root.Element.DrawTransformed(time, batch, alpha, this.BlendState, this.SamplerState, this.DepthStencilState, this.Effect, root.Transform); root.Element.DrawTransformed(time, batch, alpha, this.BlendState, this.SamplerState, this.DepthStencilState, this.Effect, root.Transform);
batch.End(); batch.End();
} }
this.Stopwatch.Stop();
this.Metrics.DrawTime += this.Stopwatch.Elapsed;
this.drewEarly = false;
} }
/// <summary> /// <summary>