From 60dfbb1ec5b2ad6ccd1c4cf44f6ebb8691af088d Mon Sep 17 00:00:00 2001 From: Ellpeck Date: Sun, 12 Dec 2021 12:32:09 +0100 Subject: [PATCH] Added UiMetrics --- CHANGELOG.md | 1 + Demos/GameImpl.cs | 12 ++++ MLEM.Ui/Elements/Element.cs | 9 +++ MLEM.Ui/UiMetrics.cs | 107 ++++++++++++++++++++++++++++++++++++ MLEM.Ui/UiSystem.cs | 43 ++++++++++++--- 5 files changed, 164 insertions(+), 8 deletions(-) create mode 100644 MLEM.Ui/UiMetrics.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index b2a5f8f..0ec005e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,7 @@ Additions - Added a multiline editing mode to TextField - Added a formatting code to allow for inline font changes - Added a SquishingGroup element +- Added UiMetrics Improvements - **Made Image ScaleToImage take ui scale into account** diff --git a/Demos/GameImpl.cs b/Demos/GameImpl.cs index f2c0315..52fd436 100644 --- a/Demos/GameImpl.cs +++ b/Demos/GameImpl.cs @@ -18,6 +18,8 @@ namespace Demos { private double fpsTime; private int lastFps; private int fpsCounter; + private UiMetrics cumulativeMetrics; + private TimeSpan secondCounter; static GameImpl() { 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() { 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() { diff --git a/MLEM.Ui/Elements/Element.cs b/MLEM.Ui/Elements/Element.cs index a0e8e29..0b7dcd5 100644 --- a/MLEM.Ui/Elements/Element.cs +++ b/MLEM.Ui/Elements/Element.cs @@ -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 if (this.Parent != null && this.Parent.UpdateAreaIfDirty()) return; + this.System.Stopwatch.Restart(); var parentArea = this.Parent != null ? this.Parent.ChildPaddedArea : (RectangleF) this.system.Viewport; var parentCenterX = parentArea.X + parentArea.Width / 2; @@ -555,6 +556,10 @@ namespace MLEM.Ui.Elements { var recursion = 0; UpdateDisplayArea(actualSize); + this.System.Stopwatch.Stop(); + this.System.Metrics.ForceAreaUpdateTime += this.System.Stopwatch.Elapsed; + this.System.Metrics.ForceAreaUpdates++; + void UpdateDisplayArea(Vector2 newSize) { var pos = new Vector2(); switch (this.anchor) { @@ -706,6 +711,7 @@ namespace MLEM.Ui.Elements { this.System.InvokeOnElementAreaUpdated(this); foreach (var child in this.Children) child.ForceUpdateArea(); + this.System.Metrics.ActualAreaUpdates++; } /// @@ -885,6 +891,8 @@ namespace MLEM.Ui.Elements { foreach (var child in this.GetRelevantChildren()) if (child.System != null) child.Update(time); + + this.System.Metrics.Updates++; } /// @@ -914,6 +922,7 @@ namespace MLEM.Ui.Elements { } // draw content in custom begin call this.Draw(time, batch, alpha, blendState, samplerState, depthStencilState, effect, mat); + this.System.Metrics.Draws++; if (customDraw) { // end our draw batch.End(); diff --git a/MLEM.Ui/UiMetrics.cs b/MLEM.Ui/UiMetrics.cs new file mode 100644 index 0000000..7ee9314 --- /dev/null +++ b/MLEM.Ui/UiMetrics.cs @@ -0,0 +1,107 @@ +using System; +using Microsoft.Xna.Framework.Graphics; +using MLEM.Ui.Elements; + +namespace MLEM.Ui { + /// + /// A snapshot of update and rendering statistics from to be used for runtime debugging and profiling. + /// This metrics struct works similarly to . + /// + public struct UiMetrics { + + /// + /// The amount of time that took. + /// Can be divided by to get an average per area update. + /// + public TimeSpan ForceAreaUpdateTime { get; internal set; } + /// + /// The amount of time that took. + /// Can be divided by to get an average per update. + /// + public TimeSpan UpdateTime { get; internal set; } + /// + /// The amount of times that was called. + /// + public uint ForceAreaUpdates { get; internal set; } + /// + /// The amount of times that was called. + /// + public uint ActualAreaUpdates { get; internal set; } + /// + /// The amount of times that was called. + /// + public uint Updates { get; internal set; } + + /// + /// The amount of time that took. + /// Can be divided by to get an average per draw. + /// + public TimeSpan DrawTime { get; internal set; } + /// + /// The amount of times that was called. + /// + public uint Draws { get; internal set; } + + /// + /// Resets all update-related metrics to 0. + /// + public void ResetUpdates() { + this.ForceAreaUpdateTime = TimeSpan.Zero; + this.UpdateTime = TimeSpan.Zero; + this.ForceAreaUpdates = 0; + this.ActualAreaUpdates = 0; + this.Updates = 0; + } + + /// + /// Resets all rendering-related metrics to 0. + /// + public void ResetDraws() { + this.DrawTime = TimeSpan.Zero; + this.Draws = 0; + } + + /// Returns the fully qualified type name of this instance. + /// The fully qualified type name. + 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}"; + } + + /// + /// Adds two ui metrics together, causing all of their values to be combined. + /// + /// The left metrics + /// The right metrics + /// The sum of both metrics + 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 + }; + } + + /// + /// Subtracts two ui metrics from each other, causing their values to be subtracted. + /// + /// The left metrics + /// The right metrics + /// The difference of both metrics + 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 + }; + } + + } +} \ No newline at end of file diff --git a/MLEM.Ui/UiSystem.cs b/MLEM.Ui/UiSystem.cs index 77e56c0..aee05d9 100644 --- a/MLEM.Ui/UiSystem.cs +++ b/MLEM.Ui/UiSystem.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Text.RegularExpressions; using Microsoft.Xna.Framework; @@ -20,8 +21,6 @@ namespace MLEM.Ui { /// public class UiSystem : GameComponent { - private readonly List rootElements = new List(); - /// /// The viewport that this ui system is rendering inside of. /// This is automatically updated during @@ -36,7 +35,6 @@ namespace MLEM.Ui { /// If is true, this is used as the screen size that uses the default /// public Point AutoScaleReferenceSize; - private float globalScale = 1; /// /// The global rendering scale of this ui system and all of its child elements. /// If is true, this scale will be different based on the window size. @@ -53,8 +51,6 @@ namespace MLEM.Ui { root.Element.ForceUpdateArea(); } } - - private UiStyle style; /// /// The style options that this ui system and all of its elements use. /// To set the default, untextured style, use . @@ -102,6 +98,11 @@ namespace MLEM.Ui { /// The ui controls are also the place to change bindings for controller and keyboard input. /// public UiControls Controls; + /// + /// The update and rendering statistics to be used for runtime debugging and profiling. + /// The metrics are reset accordingly every frame: is called at the start of , and is called at the start of , or at the start of if was not called. + /// + public UiMetrics Metrics; /// /// Event that is invoked after an is drawn, but before its children are drawn. @@ -172,6 +173,13 @@ namespace MLEM.Ui { /// public event RootCallback OnRootRemoved; + internal readonly Stopwatch Stopwatch = new Stopwatch(); + + private readonly List rootElements = new List(); + private float globalScale = 1; + private bool drewEarly; + private UiStyle style; + /// /// Creates a new ui system with the given settings. /// @@ -232,11 +240,15 @@ namespace MLEM.Ui { /// /// The game's 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.Stopwatch.Stop(); + this.Metrics.UpdateTime += this.Stopwatch.Elapsed; } /// @@ -246,10 +258,17 @@ namespace MLEM.Ui { /// The game's time /// The sprite batch to use for drawing public void DrawEarly(GameTime time, SpriteBatch batch) { + this.Metrics.ResetDraws(); + this.Stopwatch.Restart(); + foreach (var root in this.rootElements) { if (!root.Element.IsHidden) 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; } /// @@ -259,6 +278,10 @@ namespace MLEM.Ui { /// The game's time /// The sprite batch to use for drawing public void Draw(GameTime time, SpriteBatch batch) { + if (!this.drewEarly) + this.Metrics.ResetDraws(); + this.Stopwatch.Restart(); + foreach (var root in this.rootElements) { if (root.Element.IsHidden) continue; @@ -267,6 +290,10 @@ namespace MLEM.Ui { root.Element.DrawTransformed(time, batch, alpha, this.BlendState, this.SamplerState, this.DepthStencilState, this.Effect, root.Transform); batch.End(); } + + this.Stopwatch.Stop(); + this.Metrics.DrawTime += this.Stopwatch.Elapsed; + this.drewEarly = false; } ///