324 lines
12 KiB
C#
324 lines
12 KiB
C#
|
|
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);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|