mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-09-02 18:17:09 +02:00
297 lines
8.9 KiB
C#
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;
|
|
}
|
|
}
|