324 lines
12 KiB
C#
Raw Normal View History

2025-09-19 19:43:49 +05:00
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using System;
using System.Linq;
using UnityEditor.IMGUI.Controls;
using VContainer.Diagnostics;
using VContainer.Unity;
namespace VContainer.Editor.Diagnostics
{
public sealed class DiagnosticsInfoTreeViewItem : TreeViewItem
{
public string ScopeName { get; set; }
public DiagnosticsInfo DiagnosticsInfo { get; }
public RegistrationBuilder RegistrationBuilder => DiagnosticsInfo.RegisterInfo.RegistrationBuilder;
public Registration Registration => DiagnosticsInfo.ResolveInfo.Registration;
public int? RefCount => DiagnosticsInfo.ResolveInfo.RefCount;
public long ResolveTime => DiagnosticsInfo.ResolveInfo.ResolveTime;
public string TypeSummary => TypeNameHelper.GetTypeAlias(Registration.ImplementationType);
public string ContractTypesSummary
{
get
{
if (Registration.InterfaceTypes != null)
{
var values = Registration.InterfaceTypes.Select(TypeNameHelper.GetTypeAlias);
return string.Join(", ", values);
}
return "";
}
}
public string RegisterSummary
{
get
{
if (RegistrationBuilder == null)
return "";
var type = RegistrationBuilder.GetType();
if (type == typeof(RegistrationBuilder))
{
return "";
}
var typeName = type.Name;
var suffixIndex = typeName.IndexOf("Builder");
if (suffixIndex > 0)
{
typeName = typeName.Substring(0, suffixIndex);
}
suffixIndex = typeName.IndexOf("Registration");
if (suffixIndex > 0)
{
typeName = typeName.Substring(0, suffixIndex);
}
if (typeName.StartsWith("Instance") && TypeSummary.StartsWith("Func<"))
{
return "FuncFactory";
}
return typeName;
}
}
public DiagnosticsInfoTreeViewItem(DiagnosticsInfo info)
{
ScopeName = info.ScopeName;
DiagnosticsInfo = info;
displayName = TypeSummary;
}
}
public sealed class VContainerDiagnosticsInfoTreeView : TreeView
{
static readonly MultiColumnHeaderState.Column[] Columns =
{
new MultiColumnHeaderState.Column { headerContent = new GUIContent("Type") },
new MultiColumnHeaderState.Column { headerContent = new GUIContent("ContractTypes"), canSort = false },
new MultiColumnHeaderState.Column { headerContent = new GUIContent("Lifetime"), width = 15f },
new MultiColumnHeaderState.Column { headerContent = new GUIContent("Register"), width = 15f },
new MultiColumnHeaderState.Column { headerContent = new GUIContent("RefCount"), width = 5f },
new MultiColumnHeaderState.Column { headerContent = new GUIContent("Scope"), width = 20f },
new MultiColumnHeaderState.Column { headerContent = new GUIContent("Time"), width = 20f },
};
static int idSeed;
static int NextId() => ++idSeed;
const string SessionStateKeySortedColumnIndex = "VContainer.Editor.DiagnosticsInfoTreeView:sortedColumnIndex";
public bool Flatten
{
get => flatten;
set
{
flatten = value;
multiColumnHeader.ResizeToFit();
}
}
bool flatten;
public VContainerDiagnosticsInfoTreeView()
: this(new TreeViewState(), new MultiColumnHeader(new MultiColumnHeaderState(Columns)))
{
}
VContainerDiagnosticsInfoTreeView(TreeViewState state, MultiColumnHeader header)
: base(state, header)
{
rowHeight = 20;
showAlternatingRowBackgrounds = true;
showBorder = true;
header.sortingChanged += OnSortedChanged;
header.ResizeToFit();
Reload();
header.sortedColumnIndex = Math.Min(
header.state.columns.Length - 1,
SessionState.GetInt(SessionStateKeySortedColumnIndex, 0));
}
public DiagnosticsInfoTreeViewItem GetSelectedItem()
{
if (state.selectedIDs.Count <= 0) return null;
var selectedId = state.selectedIDs[0];
return GetRows().FirstOrDefault(x => x.id == selectedId) as DiagnosticsInfoTreeViewItem;
}
public void ReloadAndSort()
{
var currentSelected = state.selectedIDs;
Reload();
OnSortedChanged(multiColumnHeader);
state.selectedIDs = currentSelected;
}
void OnSortedChanged(MultiColumnHeader multiColumnHeader)
{
var columnIndex = multiColumnHeader.sortedColumnIndex;
if (columnIndex < 0) return;
SessionState.SetInt(SessionStateKeySortedColumnIndex, columnIndex);
var ascending = multiColumnHeader.IsSortedAscending(columnIndex);
if (Flatten)
{
var items = rootItem.children.Cast<DiagnosticsInfoTreeViewItem>();
rootItem.children = new List<TreeViewItem>(Sort(items, columnIndex, ascending));
}
else
{
foreach (var sectionHeaderItem in rootItem.children)
{
var items = sectionHeaderItem.children.Cast<DiagnosticsInfoTreeViewItem>();
sectionHeaderItem.children = new List<TreeViewItem>(Sort(items, columnIndex, ascending));
}
}
BuildRows(rootItem);
}
protected override TreeViewItem BuildRoot()
{
var root = new TreeViewItem { depth = -1 };
var children = new List<TreeViewItem>();
if (VContainerSettings.DiagnosticsEnabled)
{
if (Flatten)
{
var infos = DiagnositcsContext.GetDiagnosticsInfos();
foreach (var info in infos)
{
children.Add(new DiagnosticsInfoTreeViewItem(info)
{
id = NextId(),
depth = 0,
ScopeName = info.ScopeName,
});
}
}
else
{
var grouped = DiagnositcsContext.GetGroupedDiagnosticsInfos();
foreach (var scope in grouped)
{
var sectionHeaderItem = new TreeViewItem(NextId(), 0, scope.Key);
children.Add(sectionHeaderItem);
SetExpanded(sectionHeaderItem.id, true);
foreach (var info in scope)
{
AddDependencyItemsRecursive(info, sectionHeaderItem);
}
}
}
}
root.children = children;
return root;
}
protected override bool CanMultiSelect(TreeViewItem item) => false;
protected override void RowGUI(RowGUIArgs args)
{
var item = args.item as DiagnosticsInfoTreeViewItem;
if (item is null)
{
var cellRect = args.GetCellRect(0);
GUI.BeginGroup(cellRect);
{
args.rowRect = new Rect(0, 0, cellRect.width, cellRect.height);
base.RowGUI(args);
}
GUI.EndGroup();
return;
}
for (var visibleColumnIndex = 0; visibleColumnIndex < args.GetNumVisibleColumns(); visibleColumnIndex++)
{
var cellRect = args.GetCellRect(visibleColumnIndex);
// CenterRectUsingSingleLineHeight(ref cellRect);
var columnIndex = args.GetColumn(visibleColumnIndex);
var labelStyle = args.selected ? EditorStyles.whiteLabel : EditorStyles.label;
labelStyle.alignment = TextAnchor.MiddleLeft;
switch (columnIndex)
{
case 0:
GUI.BeginGroup(cellRect);
{
args.rowRect = new Rect(0, 0, cellRect.width, cellRect.height);
base.RowGUI(args);
}
GUI.EndGroup();
break;
case 1:
EditorGUI.LabelField(cellRect, item.ContractTypesSummary, labelStyle);
break;
case 2:
EditorGUI.LabelField(cellRect, item.Registration.Lifetime.ToString(), labelStyle);
break;
case 3:
EditorGUI.LabelField(cellRect, item.RegisterSummary, labelStyle);
break;
case 4:
EditorGUI.LabelField(cellRect, item.RefCount.ToString(), labelStyle);
break;
case 5:
EditorGUI.LabelField(cellRect, item.ScopeName, labelStyle);
break;
case 6:
EditorGUI.LabelField(cellRect, item.ResolveTime.ToString(), labelStyle);
break;
default:
throw new ArgumentOutOfRangeException(nameof(columnIndex), columnIndex, null);
}
}
}
void AddDependencyItemsRecursive(DiagnosticsInfo info, TreeViewItem parent)
{
var item = new DiagnosticsInfoTreeViewItem(info)
{
id = NextId(),
depth = parent.depth + 1
};
parent.AddChild(item);
SetExpanded(item.id, item.depth <= 1);
foreach (var dependency in info.Dependencies)
{
AddDependencyItemsRecursive(dependency, item);
}
}
IEnumerable<DiagnosticsInfoTreeViewItem> Sort(
IEnumerable<DiagnosticsInfoTreeViewItem> items,
int sortedColumnIndex,
bool ascending)
{
switch (sortedColumnIndex)
{
case 0:
return ascending
? items.OrderBy(x => x.TypeSummary)
: items.OrderByDescending(x => x.TypeSummary);
case 2:
return ascending
? items.OrderBy(x => x.Registration.Lifetime)
: items.OrderByDescending(x => x.Registration.Lifetime);
case 3:
return ascending
? items.OrderBy(x => x.RegisterSummary)
: items.OrderByDescending(x => x.RegisterSummary);
case 4:
return ascending
? items.OrderBy(x => x.RefCount)
: items.OrderByDescending(x => x.RefCount);
case 5:
return ascending
? items.OrderBy(x => x.ScopeName)
: items.OrderByDescending(x => x.ScopeName);
case 6:
return ascending
? items.OrderBy(x => x.ResolveTime)
: items.OrderByDescending(x => x.ResolveTime);
default:
throw new ArgumentOutOfRangeException(nameof(sortedColumnIndex), sortedColumnIndex, null);
}
}
}
}