Files
RobustToolbox/Robust.Client/UserInterface/DevWindow/ProfTree.xaml.cs
T

297 lines
8.9 KiB
C#

using System.Collections.Generic;
using System.Runtime.InteropServices;
using Robust.Client.AutoGenerated;
using Robust.Client.Profiling;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Collections;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Maths;
using Robust.Shared.Profiling;
namespace Robust.Client.UserInterface;
[GenerateTypedNameReferences]
internal sealed partial class ProfTree : Control
{
public const float TreeLevelMargin = 16;
[Dependency] private readonly ProfManager _prof = default!;
[Dependency] private readonly ProfViewManager _profViewMgr = default!;
private ProfViewManager.Snapshot? _snapshot;
public long Frame;
private readonly TreeExpand _treeExpand = new();
public ProfTree()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
Header.Text.Text = "Thing";
Header.SideLabel.Text = "Misc";
Header.TimeLabel.Text = "Time";
Header.PercentTimeLabel.Text = "Time%";
Header.AllocLabel.Text = "Alloc";
Header.PercentAllocLabel.Text = "Alloc%";
}
public void LoadSnapshot(ProfViewManager.Snapshot snapshot)
{
_snapshot = snapshot;
}
public void RebuildTree()
{
if (_snapshot == null)
return;
TreeRoot.RemoveAllChildren();
ref var buf = ref _snapshot.Buffer;
var indexIdx = _profViewMgr.GetIndexOfFrame(Frame, _snapshot);
if (indexIdx == 0)
{
Logger.WarningS("prof.ui", $"Unable to find index for frame: {Frame}");
return;
}
// Index for this frame's data.
ref var index = ref _snapshot.Buffer.Index(indexIdx);
// The frame must be wrapped in a whole group. We display this group.
var i = index.EndPos - 1;
ref var logEnd = ref buf.Log(i);
var controls = new ValueList<Control>();
// Create child controls.
var countDict = new Dictionary<int, int>();
var totalFrameTime = ProfGraphView.GetFrameTime(buf, index);
var (totalCounts, _) = GetTotalGroupCounts(buf, i, logEnd.GroupEnd.StartIndex);
BuildData data = default;
data.Buffer = buf;
data.Index = index;
data.TotalFrameTime = totalFrameTime;
// We traverse the log backwards because we can use GroupEnd nodes to skip over data entirely.
for (; i >= index.StartPos; i--)
{
// Recursively build the tree.
ref var log = ref buf.Log(i);
RebuildTreeAddControls(
in data,
ref controls,
ref i,
in log,
totalCounts,
countDict,
_treeExpand,
2);
}
// Because we traversed backwards, the control list is backwards too.
// Insert backwards again to fix that.
for (var c = controls.Count - 1; c >= 0; c--)
{
TreeRoot.AddChild(controls[c]);
}
// Do a final pass to assign the alternating background colors.
// Easier to do here than to do it while we're creating the controls.
var alt = false;
foreach (var child in TreeRoot.Children)
{
DoAltBackgrounds(child, ref alt);
}
}
private static void DoAltBackgrounds(Control control, ref bool alt)
{
if (control is not ProfAltBackground altBg)
return;
altBg.IsAltBackground = alt;
alt = !alt;
}
private void RebuildTreeAddControls(
in BuildData data,
ref ValueList<Control> insertInto,
ref long i,
in ProfLog log,
Dictionary<int, int> totalCounts,
Dictionary<int, int> countDict,
TreeExpand expandParent,
float margin)
{
switch (log.Type)
{
case ProfLogType.Value:
TreeInsertSample(in data, ref insertInto, log.Value.StringId, log.Value.Value, margin);
break;
case ProfLogType.GroupEnd:
var stringId = log.GroupEnd.StringId;
ref var count = ref CollectionsMarshal.GetValueRefOrAddDefault(countDict, stringId, out _);
count += 1;
var totalCount = totalCounts[stringId];
var stringI = totalCount - count;
TreeInsertGroup(
in data,
ref insertInto,
ref i,
in log.GroupEnd,
(stringId, stringI),
expandParent,
margin);
break;
}
}
private void TreeInsertSample(
in BuildData data,
ref ValueList<Control> insertInto,
int stringId,
in ProfValue value,
float margin)
{
var treeLine = new ProfTreeLine();
treeLine.Margin = new Thickness(treeLine.Margin.Left + margin + 12 + 2, 0, 0, 0);
FillTreeLine(in data, treeLine, stringId, value);
insertInto.Add(new ProfAltBackground { Children = { treeLine } });
}
private void TreeInsertGroup(
in BuildData data,
ref ValueList<Control> insertInto,
ref long i,
in ProfLogGroupEnd logEnd,
(int str, int i) expandId,
TreeExpand expandParent,
float margin)
{
i -= 1;
var (totalCounts, anyChildren) = GetTotalGroupCounts(data.Buffer, i, logEnd.StartIndex);
if (!anyChildren)
{
// Node has no children we can display. Just insert it as if it's a sample and move along.
TreeInsertSample(in data, ref insertInto, logEnd.StringId, logEnd.Value, margin);
i = logEnd.StartIndex;
return;
}
var groupControl = new ProfTreeEntry(this, expandParent, expandId, margin);
var altBg = new ProfAltBackground { Children = { groupControl } };
FillTreeLine(in data, groupControl.TextLine, logEnd.StringId, logEnd.Value);
// Look up in the parent's expand data whether we're expanded.
var expand = expandParent.ExpandedItems.GetValueOrDefault(expandId);
if (expand is not { Enabled: true })
{
// Note expanded. Skip!
i = logEnd.StartIndex;
insertInto.Add(altBg);
return;
}
groupControl.Arrow.Rotated = true;
// Create child controls.
var countDict = new Dictionary<int, int>();
for (; i >= data.Index.StartPos; i--)
{
ref var log = ref data.Buffer.Log(i);
if (log.Type == ProfLogType.GroupStart)
break;
RebuildTreeAddControls(
in data,
ref insertInto,
ref i,
in log,
totalCounts,
countDict,
expand,
margin + TreeLevelMargin);
}
insertInto.Add(altBg);
}
private static (Dictionary<int, int> counts, bool anyChildren) GetTotalGroupCounts(
in ProfBuffer buffer,
long i,
long startIdx)
{
var anyChildren = false;
var dict = new Dictionary<int, int>();
for (; i > startIdx; i--)
{
ref var log = ref buffer.Log(i);
anyChildren |= log.Type is ProfLogType.GroupEnd or ProfLogType.Value;
if (log.Type != ProfLogType.GroupEnd)
continue;
i = log.GroupEnd.StartIndex;
ref var val = ref CollectionsMarshal.GetValueRefOrAddDefault(dict, log.GroupEnd.StringId, out _);
val += 1;
}
return (dict, anyChildren);
}
private void FillTreeLine(
in BuildData data,
ProfTreeLine line,
int stringId,
in ProfValue value)
{
line.Text.Text = _prof.GetString(stringId);
switch (value.Type)
{
case ProfValueType.TimeAllocSample:
line.TimeLabel.Text = $"{value.TimeAllocSample.Time * 1000:N2} ms";
line.PercentTimeLabel.Text = $"{value.TimeAllocSample.Time / data.TotalFrameTime.Time:P2}";
line.AllocLabel.Text = $"{value.TimeAllocSample.Alloc} B";
line.PercentAllocLabel.Text = $"{value.TimeAllocSample.Alloc / (float)data.TotalFrameTime.Alloc:P2}";
break;
case ProfValueType.Int32:
line.SideLabel.Text = value.Int32.ToString();
break;
case ProfValueType.Int64:
line.SideLabel.Text = value.Int64.ToString();
break;
default:
line.SideLabel.Text = "???";
break;
}
}
internal sealed class TreeExpand
{
public bool Enabled = true;
public readonly Dictionary<(int str, int i), TreeExpand> ExpandedItems = new();
}
private struct BuildData
{
public ProfBuffer Buffer;
public ProfIndex Index;
public TimeAndAllocSample TotalFrameTime;
}
}