Files
83c2a1be11 [Dependency] source generator part 2 (#6550)
* [Dependency] source generator

No more reflection, no more codegen at runtime

Also various changes to Roslyn helpers to make this easier to write.

Requires all types with dependencies to be partial and not have readonly dependency fields. An analyzer enforces this at warning level, the previous injection strategies have remained in the code *for now* as a fallback.

No fallback is available for [field: Dependency] properties, due to a Roslyn bug.

Code Fixes exist. We love Roslyn

* Apply dependencies generator changes to all code

* Release notes

* Preprocessor got hands

* Handle nullable dependencies

These are bad but gotta deal with it.

* Apply suggestions from code review

Co-authored-by: Moony <moony@hellomouse.net>

* Fine, let's not use collection expressions

---------

Co-authored-by: Moony <moony@hellomouse.net>
2026-05-08 12:38:33 +02:00

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 ProfManager _prof = default!;
[Dependency] private 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;
}
}