Initial commit: Unity WordConnect project
This commit is contained in:
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c9d3500c8eb84e07acfb1cb574d9f9e2
|
||||
timeCreated: 1632057810
|
||||
@ -0,0 +1,81 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace VContainer.Editor.Diagnostics
|
||||
{
|
||||
// reflection call of UnityEditor.SplitterGUILayout
|
||||
static class SplitterGUILayout
|
||||
{
|
||||
static BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static;
|
||||
|
||||
static readonly Lazy<Type> SplitterStateType = new Lazy<Type>(() =>
|
||||
{
|
||||
var type = typeof(EditorWindow).Assembly.GetTypes().First(x => x.FullName == "UnityEditor.SplitterState");
|
||||
return type;
|
||||
});
|
||||
|
||||
static readonly Lazy<ConstructorInfo> SplitterStateCtor = new Lazy<ConstructorInfo>(() =>
|
||||
{
|
||||
var type = SplitterStateType.Value;
|
||||
return type.GetConstructor(flags, null, new Type[] { typeof(float[]), typeof(int[]), typeof(int[]) }, null);
|
||||
});
|
||||
|
||||
static readonly Lazy<Type> SplitterGUILayoutType = new Lazy<Type>(() =>
|
||||
{
|
||||
var type = typeof(EditorWindow).Assembly.GetTypes().First(x => x.FullName == "UnityEditor.SplitterGUILayout");
|
||||
return type;
|
||||
});
|
||||
|
||||
static readonly Lazy<MethodInfo> BeginVerticalSplitInfo = new Lazy<MethodInfo>(() =>
|
||||
{
|
||||
var type = SplitterGUILayoutType.Value;
|
||||
return type.GetMethod("BeginVerticalSplit", flags, null, new Type[] { SplitterStateType.Value, typeof(GUILayoutOption[]) }, null);
|
||||
});
|
||||
|
||||
static readonly Lazy<MethodInfo> EndVerticalSplitInfo = new Lazy<MethodInfo>(() =>
|
||||
{
|
||||
var type = SplitterGUILayoutType.Value;
|
||||
return type.GetMethod("EndVerticalSplit", flags, null, Type.EmptyTypes, null);
|
||||
});
|
||||
|
||||
static readonly Lazy<MethodInfo> BeginHorizontalSplitInfo = new Lazy<MethodInfo>(() =>
|
||||
{
|
||||
var type = SplitterGUILayoutType.Value;
|
||||
return type.GetMethod("BeginHorizontalSplit", flags, null, new Type[] { SplitterStateType.Value, typeof(GUILayoutOption[]) }, null);
|
||||
});
|
||||
|
||||
static readonly Lazy<MethodInfo> EndHorizontalSplitInfo = new Lazy<MethodInfo>(() =>
|
||||
{
|
||||
var type = SplitterGUILayoutType.Value;
|
||||
return type.GetMethod("EndHorizontalSplit", flags, null, Type.EmptyTypes, null);
|
||||
});
|
||||
|
||||
public static object CreateSplitterState(float[] relativeSizes, int[] minSizes, int[] maxSizes)
|
||||
{
|
||||
return SplitterStateCtor.Value.Invoke(new object[] { relativeSizes, minSizes, maxSizes });
|
||||
}
|
||||
|
||||
public static void BeginVerticalSplit(object splitterState, params GUILayoutOption[] options)
|
||||
{
|
||||
BeginVerticalSplitInfo.Value.Invoke(null, new object[] { splitterState, options });
|
||||
}
|
||||
|
||||
public static void EndVerticalSplit()
|
||||
{
|
||||
EndVerticalSplitInfo.Value.Invoke(null, Array.Empty<object>());
|
||||
}
|
||||
|
||||
public static void BeginHorizontalSplit(object splitterState, params GUILayoutOption[] options)
|
||||
{
|
||||
BeginHorizontalSplitInfo.Value.Invoke(null, new object[] { splitterState, options });
|
||||
}
|
||||
|
||||
public static void EndHorizontalSplit()
|
||||
{
|
||||
EndHorizontalSplitInfo.Value.Invoke(null, Array.Empty<object>());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: af963129cd84433ba74949eeb1c00542
|
||||
timeCreated: 1633102883
|
||||
@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace VContainer.Editor.Diagnostics
|
||||
{
|
||||
static class TypeNameHelper
|
||||
{
|
||||
static readonly IReadOnlyDictionary<Type, string> TypeAlias = new Dictionary<Type, string>
|
||||
{
|
||||
{ typeof(bool), "bool" },
|
||||
{ typeof(byte), "byte" },
|
||||
{ typeof(char), "char" },
|
||||
{ typeof(decimal), "decimal" },
|
||||
{ typeof(double), "double" },
|
||||
{ typeof(float), "float" },
|
||||
{ typeof(int), "int" },
|
||||
{ typeof(long), "long" },
|
||||
{ typeof(object), "object" },
|
||||
{ typeof(sbyte), "sbyte" },
|
||||
{ typeof(short), "short" },
|
||||
{ typeof(string), "string" },
|
||||
{ typeof(uint), "uint" },
|
||||
{ typeof(ulong), "ulong" },
|
||||
{ typeof(void), "void" }
|
||||
};
|
||||
|
||||
public static string GetTypeAlias(Type type)
|
||||
{
|
||||
if (TypeAlias.TryGetValue(type, out var alias))
|
||||
{
|
||||
return alias;
|
||||
}
|
||||
if (type.IsGenericType)
|
||||
{
|
||||
var typeParameters = type.GetGenericArguments().Select(GetTypeAlias);
|
||||
var typeNameBase = type.Name.Split('`')[0];
|
||||
return $"{typeNameBase}<{string.Join(",", typeParameters)}>";
|
||||
}
|
||||
return type.Name;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 639563c4569244d8a54a150c42c8de33
|
||||
timeCreated: 1633621836
|
||||
@ -0,0 +1,323 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0d65b5790cc048599cb6e65bb859e6aa
|
||||
timeCreated: 1632058224
|
||||
@ -0,0 +1,216 @@
|
||||
using System;
|
||||
using UnityEditor;
|
||||
using UnityEditor.IMGUI.Controls;
|
||||
using UnityEngine;
|
||||
using VContainer.Diagnostics;
|
||||
using VContainer.Unity;
|
||||
|
||||
namespace VContainer.Editor.Diagnostics
|
||||
{
|
||||
public sealed class VContainerDiagnosticsWindow : EditorWindow
|
||||
{
|
||||
static VContainerDiagnosticsWindow window;
|
||||
|
||||
static readonly GUIContent FlattenHeadContent = EditorGUIUtility.TrTextContent("Flatten", "Flatten dependencies");
|
||||
static readonly GUIContent ReloadHeadContent = EditorGUIUtility.TrTextContent("Reload", "Reload View");
|
||||
|
||||
internal static bool EnableAutoReload;
|
||||
internal static bool EnableCaptureStackTrace;
|
||||
|
||||
[MenuItem("Window/VContainer Diagnostics")]
|
||||
public static void OpenWindow()
|
||||
{
|
||||
if (window != null)
|
||||
{
|
||||
window.Close();
|
||||
}
|
||||
GetWindow<VContainerDiagnosticsWindow>("VContainer Diagnostics").Show();
|
||||
}
|
||||
|
||||
GUIStyle TableListStyle
|
||||
{
|
||||
get
|
||||
{
|
||||
var style = new GUIStyle("CN Box");
|
||||
style.margin.top = 0;
|
||||
style.padding.left = 3;
|
||||
return style;
|
||||
}
|
||||
}
|
||||
|
||||
GUIStyle DetailsStyle
|
||||
{
|
||||
get
|
||||
{
|
||||
var detailsStyle = new GUIStyle("CN Message");
|
||||
detailsStyle.wordWrap = false;
|
||||
detailsStyle.stretchHeight = true;
|
||||
detailsStyle.margin.right = 15;
|
||||
return detailsStyle;
|
||||
}
|
||||
}
|
||||
|
||||
VContainerDiagnosticsInfoTreeView treeView;
|
||||
VContainerInstanceTreeView instanceTreeView;
|
||||
SearchField searchField;
|
||||
|
||||
object verticalSplitterState;
|
||||
object horizontalSplitterState;
|
||||
Vector2 tableScrollPosition;
|
||||
Vector2 detailsScrollPosition;
|
||||
Vector2 instanceScrollPosition;
|
||||
|
||||
public void Reload(IObjectResolver resolver)
|
||||
{
|
||||
treeView.ReloadAndSort();
|
||||
Repaint();
|
||||
}
|
||||
|
||||
void OnPlayModeStateChange(PlayModeStateChange state)
|
||||
{
|
||||
treeView.ReloadAndSort();
|
||||
Repaint();
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
window = this; // set singleton.
|
||||
verticalSplitterState = SplitterGUILayout.CreateSplitterState(new [] { 75f, 25f }, new [] { 32, 32 }, null);
|
||||
horizontalSplitterState = SplitterGUILayout.CreateSplitterState(new[] { 75, 25f }, new[] { 32, 32 }, null);
|
||||
treeView = new VContainerDiagnosticsInfoTreeView();
|
||||
instanceTreeView = new VContainerInstanceTreeView();
|
||||
searchField = new SearchField();
|
||||
|
||||
DiagnositcsContext.OnContainerBuilt += Reload;
|
||||
EditorApplication.playModeStateChanged += OnPlayModeStateChange;
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
DiagnositcsContext.OnContainerBuilt -= Reload;
|
||||
EditorApplication.playModeStateChanged -= OnPlayModeStateChange;
|
||||
}
|
||||
|
||||
void OnGUI()
|
||||
{
|
||||
RenderHeadPanel();
|
||||
|
||||
SplitterGUILayout.BeginVerticalSplit(verticalSplitterState, Array.Empty<GUILayoutOption>());
|
||||
{
|
||||
SplitterGUILayout.BeginHorizontalSplit(horizontalSplitterState);
|
||||
{
|
||||
RenderBuildPanel();
|
||||
RenderInstancePanel();
|
||||
}
|
||||
SplitterGUILayout.EndHorizontalSplit();
|
||||
|
||||
RenderStackTracePanel();
|
||||
}
|
||||
SplitterGUILayout.EndVerticalSplit();
|
||||
}
|
||||
|
||||
void RenderHeadPanel()
|
||||
{
|
||||
using (new EditorGUILayout.VerticalScope())
|
||||
using (new EditorGUILayout.HorizontalScope(EditorStyles.toolbar))
|
||||
{
|
||||
var flattenOn = GUILayout.Toggle(treeView.Flatten, FlattenHeadContent, EditorStyles.toolbarButton);
|
||||
if (flattenOn != treeView.Flatten)
|
||||
{
|
||||
treeView.Flatten = flattenOn;
|
||||
treeView.ReloadAndSort();
|
||||
Repaint();
|
||||
}
|
||||
|
||||
GUILayout.FlexibleSpace();
|
||||
|
||||
treeView.searchString = searchField.OnToolbarGUI(treeView.searchString);
|
||||
|
||||
if (GUILayout.Button(ReloadHeadContent, EditorStyles.toolbarButton))
|
||||
{
|
||||
treeView.ReloadAndSort();
|
||||
Repaint();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RenderBuildPanel()
|
||||
{
|
||||
using (new EditorGUILayout.VerticalScope(TableListStyle))
|
||||
using (var scrollViewScope = new EditorGUILayout.ScrollViewScope(tableScrollPosition,
|
||||
// true,
|
||||
// true,
|
||||
GUILayout.ExpandWidth(true),
|
||||
GUILayout.MaxWidth(2000f)))
|
||||
{
|
||||
tableScrollPosition = scrollViewScope.scrollPosition;
|
||||
|
||||
var controlRect = EditorGUILayout.GetControlRect(
|
||||
GUILayout.ExpandHeight(true),
|
||||
GUILayout.ExpandWidth(true));
|
||||
treeView?.OnGUI(controlRect);
|
||||
}
|
||||
}
|
||||
|
||||
void RenderInstancePanel()
|
||||
{
|
||||
if (!VContainerSettings.DiagnosticsEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var selectedItem = treeView.GetSelectedItem();
|
||||
if (selectedItem?.DiagnosticsInfo.ResolveInfo is ResolveInfo resolveInfo)
|
||||
{
|
||||
if (resolveInfo.Instances.Count > 0)
|
||||
{
|
||||
instanceTreeView.CurrentDiagnosticsInfo = selectedItem.DiagnosticsInfo;
|
||||
instanceTreeView.Reload();
|
||||
|
||||
using (var scrollViewScope = new EditorGUILayout.ScrollViewScope(instanceScrollPosition, GUILayout.ExpandHeight(true)))
|
||||
{
|
||||
instanceScrollPosition = scrollViewScope.scrollPosition;
|
||||
var controlRect = EditorGUILayout.GetControlRect(
|
||||
GUILayout.ExpandHeight(true),
|
||||
GUILayout.ExpandWidth(true));
|
||||
instanceTreeView?.OnGUI(controlRect);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUILayout.SelectableLabel("No instance reference");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RenderStackTracePanel()
|
||||
{
|
||||
var message = "";
|
||||
if (VContainerSettings.DiagnosticsEnabled)
|
||||
{
|
||||
var selectedItem = treeView.GetSelectedItem();
|
||||
if (selectedItem?.DiagnosticsInfo?.RegisterInfo is RegisterInfo registerInfo)
|
||||
{
|
||||
message = $"<a href=\"{registerInfo.GetScriptAssetPath()}\" line=\"{registerInfo.GetFileLineNumber()}\">Register at {registerInfo.GetHeadline()}</a>" +
|
||||
Environment.NewLine +
|
||||
Environment.NewLine +
|
||||
selectedItem.DiagnosticsInfo.RegisterInfo.StackTrace;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
message = "VContainer Diagnostics collector is disabled. To enable, please check VContainerSettings.";
|
||||
}
|
||||
using (var scrollViewScope = new EditorGUILayout.ScrollViewScope(detailsScrollPosition))
|
||||
{
|
||||
detailsScrollPosition = scrollViewScope.scrollPosition;
|
||||
var vector = DetailsStyle.CalcSize(new GUIContent(message));
|
||||
EditorGUILayout.SelectableLabel(message, DetailsStyle,
|
||||
GUILayout.ExpandHeight(true),
|
||||
GUILayout.ExpandWidth(true),
|
||||
GUILayout.MinWidth(vector.x),
|
||||
GUILayout.MinHeight(vector.y));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e3e4c1066bed4185a96639d5b1017922
|
||||
timeCreated: 1632057811
|
||||
@ -0,0 +1,115 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnityEditor.IMGUI.Controls;
|
||||
using UnityEngine;
|
||||
using VContainer.Diagnostics;
|
||||
|
||||
namespace VContainer.Editor.Diagnostics
|
||||
{
|
||||
public sealed class VContainerInstanceTreeView : TreeView
|
||||
{
|
||||
static int idSeed;
|
||||
static int NextId() => ++idSeed;
|
||||
|
||||
static string Stringify(object instance)
|
||||
{
|
||||
if (ReferenceEquals(instance, null))
|
||||
return "null";
|
||||
if (instance is UnityEngine.Object obj && obj == null)
|
||||
return "null or destroyed";
|
||||
return instance.ToString();
|
||||
}
|
||||
|
||||
public DiagnosticsInfo CurrentDiagnosticsInfo { get; set; }
|
||||
|
||||
public VContainerInstanceTreeView() : base(new TreeViewState())
|
||||
{
|
||||
Reload();
|
||||
}
|
||||
|
||||
protected override TreeViewItem BuildRoot()
|
||||
{
|
||||
var root = new TreeViewItem(NextId(), -1, "Root");
|
||||
var children = new List<TreeViewItem>();
|
||||
if (CurrentDiagnosticsInfo is DiagnosticsInfo info)
|
||||
{
|
||||
for (var i = 0; i < info.ResolveInfo.Instances.Count; i++)
|
||||
{
|
||||
var instance = info.ResolveInfo.Instances[i];
|
||||
var item = new TreeViewItem(NextId(), 0,
|
||||
$"({TypeNameHelper.GetTypeAlias(instance.GetType())}) {Stringify(instance)}");
|
||||
AddProperties(instance, item);
|
||||
children.Add(item);
|
||||
SetExpanded(item.id, true);
|
||||
}
|
||||
}
|
||||
|
||||
root.children = children;
|
||||
return root;
|
||||
}
|
||||
|
||||
void AddProperties(object instance, TreeViewItem parent)
|
||||
{
|
||||
if (instance == null) return;
|
||||
|
||||
var type = instance.GetType();
|
||||
var props = type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
var fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
|
||||
foreach (var prop in props)
|
||||
{
|
||||
if (prop.PropertyType.IsSubclassOf(typeof(UnityEngine.Object)) &&
|
||||
prop.IsDefined(typeof(ObsoleteAttribute), true))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (prop.CanRead)
|
||||
{
|
||||
var value = prop.GetValue(instance);
|
||||
var displayName = $"{prop.Name} = ({TypeNameHelper.GetTypeAlias(prop.PropertyType)}) {Stringify(value)}";
|
||||
parent.AddChild(new TreeViewItem(NextId(), parent.depth + 1, displayName));
|
||||
}
|
||||
else
|
||||
{
|
||||
var displayName = $"{prop.Name} = (write-only {TypeNameHelper.GetTypeAlias(prop.PropertyType)})";
|
||||
parent.AddChild(new TreeViewItem(NextId(), parent.depth + 1, displayName));
|
||||
}
|
||||
}
|
||||
catch (MissingReferenceException)
|
||||
{
|
||||
}
|
||||
catch (NotSupportedException)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var field in fields)
|
||||
{
|
||||
if ((field.FieldType.IsSubclassOf(typeof(UnityEngine.Object)) &&
|
||||
field.IsDefined(typeof(ObsoleteAttribute), true)) ||
|
||||
field.IsDefined(typeof(CompilerGeneratedAttribute), true))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var value = field.GetValue(instance);
|
||||
var displayName = $"{field.Name} = ({TypeNameHelper.GetTypeAlias(field.FieldType)}) {Stringify(value)}";
|
||||
parent.AddChild(new TreeViewItem(NextId(), parent.depth + 1, displayName));
|
||||
}
|
||||
catch (MissingReferenceException)
|
||||
{
|
||||
}
|
||||
catch (NotSupportedException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 538613b38d4a4facb28765eb06374863
|
||||
timeCreated: 1633569303
|
||||
@ -0,0 +1,64 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using VContainer.Unity;
|
||||
|
||||
namespace VContainer.Editor
|
||||
{
|
||||
[CustomPropertyDrawer(typeof(ParentReference))]
|
||||
public sealed class ParentReferencePropertyDrawer : PropertyDrawer
|
||||
{
|
||||
static string[] GetAllTypeNames()
|
||||
{
|
||||
return new List<string> { "None" }
|
||||
.Concat(TypeCache.GetTypesDerivedFrom<LifetimeScope>()
|
||||
.Select(type => type.FullName))
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
static string GetLabel(Type type) => $"{type.Namespace}/{type.Name}";
|
||||
|
||||
string[] names;
|
||||
|
||||
public override void OnGUI(Rect rect, SerializedProperty prop, GUIContent label)
|
||||
{
|
||||
if (names == null)
|
||||
{
|
||||
names = GetAllTypeNames();
|
||||
if (prop.serializedObject.targetObject is LifetimeScope lifetimeScope)
|
||||
{
|
||||
var lifetimeScopeName = lifetimeScope.GetType().FullName;
|
||||
names = names.Where(name => name != lifetimeScopeName).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
var typeNameProp = prop.FindPropertyRelative("TypeName");
|
||||
|
||||
using (new EditorGUI.PropertyScope(rect, label, prop))
|
||||
{
|
||||
var labelRect = new Rect(rect.x, rect.y, rect.width, 18f);
|
||||
var popupRect = new Rect(rect.x, rect.y + labelRect.height, rect.width , 18f);
|
||||
|
||||
var index = Array.IndexOf(names, typeNameProp.stringValue);
|
||||
if (index < 0) index = 0;
|
||||
|
||||
EditorGUI.LabelField(labelRect, "Parent");
|
||||
using (var check = new EditorGUI.ChangeCheckScope())
|
||||
{
|
||||
index = EditorGUI.Popup(popupRect, index, names);
|
||||
if (check.changed)
|
||||
{
|
||||
typeNameProp.stringValue = names[index];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
|
||||
{
|
||||
return 18f + 18f + 4f;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 51521f95bbd442b08bc2b53efcdeff29
|
||||
timeCreated: 1594556689
|
||||
@ -0,0 +1,113 @@
|
||||
using System.IO;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using VContainer.Unity;
|
||||
|
||||
#if UNITY_2020_2_OR_NEWER
|
||||
using UnityEditor.Compilation;
|
||||
#endif
|
||||
|
||||
namespace VContainer.Editor
|
||||
{
|
||||
public sealed class ScriptTemplateProcessor : UnityEditor.AssetModificationProcessor
|
||||
{
|
||||
#if UNITY_2020_2_OR_NEWER
|
||||
const string RootNamespaceBeginTag = "#ROOTNAMESPACEBEGIN#";
|
||||
const string RootNamespaceEndTag = "#ROOTNAMESPACEEND#";
|
||||
#endif
|
||||
|
||||
const string MonoInstallerTemplate =
|
||||
"using VContainer;\n" +
|
||||
"using VContainer.Unity;\n" +
|
||||
"\n" +
|
||||
#if UNITY_2020_2_OR_NEWER
|
||||
RootNamespaceBeginTag + "\n" +
|
||||
#endif
|
||||
"public class #SCRIPTNAME# : LifetimeScope\n" +
|
||||
"{\n" +
|
||||
" protected override void Configure(IContainerBuilder builder)\n" +
|
||||
" {\n" +
|
||||
" }\n" +
|
||||
"}\n" +
|
||||
#if UNITY_2020_2_OR_NEWER
|
||||
RootNamespaceEndTag + "\n" +
|
||||
#endif
|
||||
"";
|
||||
|
||||
public static void OnWillCreateAsset(string metaPath)
|
||||
{
|
||||
if (VContainerSettings.Instance != null && VContainerSettings.Instance.DisableScriptModifier)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var suffixIndex = metaPath.LastIndexOf(".meta");
|
||||
if (suffixIndex < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var scriptPath = metaPath.Substring(0, suffixIndex);
|
||||
var basename = Path.GetFileNameWithoutExtension(scriptPath);
|
||||
var extname = Path.GetExtension(scriptPath);
|
||||
if (extname != ".cs")
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!scriptPath.EndsWith("LifetimeScope.cs"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var content = MonoInstallerTemplate.Replace("#SCRIPTNAME#", basename);
|
||||
|
||||
#if UNITY_2020_2_OR_NEWER
|
||||
{
|
||||
var rootNamespace = CompilationPipeline.GetAssemblyRootNamespaceFromScriptPath(scriptPath);
|
||||
content = RemoveOrInsertNamespaceSimple(content, rootNamespace);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (scriptPath.StartsWith("Assets/"))
|
||||
{
|
||||
scriptPath = scriptPath.Substring("Assets/".Length);
|
||||
}
|
||||
|
||||
var fullPath = Path.Combine(Application.dataPath, scriptPath);
|
||||
File.WriteAllText(fullPath, content);
|
||||
AssetDatabase.Refresh();
|
||||
}
|
||||
|
||||
#if UNITY_2020_2_OR_NEWER
|
||||
// https://github.com/Unity-Technologies/UnityCsReference/blob/2020.2/Editor/Mono/ProjectWindow/ProjectWindowUtil.cs#L495-L550
|
||||
static string RemoveOrInsertNamespaceSimple(string content, string rootNamespace)
|
||||
{
|
||||
const char eol = '\n';
|
||||
|
||||
if (string.IsNullOrWhiteSpace(rootNamespace))
|
||||
{
|
||||
return content
|
||||
.Replace(RootNamespaceBeginTag + eol, "")
|
||||
.Replace(RootNamespaceEndTag + eol, "");
|
||||
}
|
||||
|
||||
var lines = content.Split(eol);
|
||||
|
||||
var startAt = ArrayUtility.IndexOf(lines, RootNamespaceBeginTag);
|
||||
var endAt = ArrayUtility.IndexOf(lines, RootNamespaceEndTag);
|
||||
|
||||
lines[startAt] = $"namespace {rootNamespace}\n{{";
|
||||
{
|
||||
for (var i = startAt + 1; i < endAt; ++i)
|
||||
{
|
||||
lines[i] = $" {lines[i]}";
|
||||
}
|
||||
}
|
||||
lines[endAt] = "}";
|
||||
|
||||
return string.Join(eol.ToString(), lines);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e4beca40a4df74e7787b464769f15620
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "VContainer.Editor",
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"VContainer"
|
||||
],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": true,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7bce08a00b4d54cd694096abadbb897e
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user