122 lines
2.8 KiB
C#
122 lines
2.8 KiB
C#
|
|
using System;
|
||
|
|
using System.Collections;
|
||
|
|
using System.Collections.Generic;
|
||
|
|
using System.Reflection;
|
||
|
|
using Godot;
|
||
|
|
|
||
|
|
namespace Rokojori.Tools;
|
||
|
|
|
||
|
|
public static class ObjectGraphInspector
|
||
|
|
{
|
||
|
|
public class Stats
|
||
|
|
{
|
||
|
|
public int totalObjects;
|
||
|
|
public int maxDepth;
|
||
|
|
public Dictionary<Type, int> typeCounts = new();
|
||
|
|
public Dictionary<string, int> memberInfos = new();
|
||
|
|
|
||
|
|
public override string ToString()
|
||
|
|
{
|
||
|
|
var info =
|
||
|
|
"Total Objects:" + totalObjects + " MaxDepth: " + maxDepth;
|
||
|
|
|
||
|
|
foreach ( var t in typeCounts )
|
||
|
|
{
|
||
|
|
info += "\n";
|
||
|
|
info += t.Key + ": " + t.Value;
|
||
|
|
}
|
||
|
|
|
||
|
|
foreach ( var t in memberInfos )
|
||
|
|
{
|
||
|
|
info += "\n";
|
||
|
|
info += t.Key + ": " + t.Value;
|
||
|
|
}
|
||
|
|
|
||
|
|
return info;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public static Stats Inspect( object root, int maxDepth = 64 )
|
||
|
|
{
|
||
|
|
var visited = new HashSet<object>(ReferenceEqualityComparer.Instance);
|
||
|
|
var stats = new Stats();
|
||
|
|
|
||
|
|
Walk( root, 0, maxDepth, visited, stats, "<root>" );
|
||
|
|
stats.totalObjects = visited.Count;
|
||
|
|
|
||
|
|
return stats;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void Walk( object obj, int depth, int maxDepth, HashSet<object> visited, Stats stats, string memberInfo )
|
||
|
|
{
|
||
|
|
if ( obj == null )
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
var type = obj.GetType();
|
||
|
|
|
||
|
|
if ( type.IsValueType || obj is string )
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if ( ! visited.Add(obj) )
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
stats.maxDepth = Math.Max( stats.maxDepth, depth );
|
||
|
|
|
||
|
|
stats.typeCounts.TryGetValue( type, out int count );
|
||
|
|
stats.typeCounts[ type ] = count + 1;
|
||
|
|
|
||
|
|
var combinedInfo = memberInfo + "(" + type.Name + ")";
|
||
|
|
|
||
|
|
stats.memberInfos.TryGetValue( combinedInfo, out int memberCount );
|
||
|
|
stats.memberInfos[ combinedInfo ] = memberCount + 1;
|
||
|
|
|
||
|
|
if ( depth >= maxDepth )
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if ( obj is IEnumerable enumerable )
|
||
|
|
{
|
||
|
|
foreach ( var item in enumerable )
|
||
|
|
{
|
||
|
|
Walk( item, depth + 1, maxDepth, visited, stats, type.Name + "." + item );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
var flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
|
||
|
|
|
||
|
|
foreach ( var field in type.GetFields( flags ) )
|
||
|
|
{
|
||
|
|
if ( field.FieldType.IsValueType || field.FieldType == typeof(string))
|
||
|
|
{
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
try
|
||
|
|
{
|
||
|
|
var value = field.GetValue( obj );
|
||
|
|
Walk( value, depth + 1, maxDepth, visited, stats, type.Name + "." + field.Name );
|
||
|
|
}
|
||
|
|
catch
|
||
|
|
{
|
||
|
|
// Ignore reflection failures
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
class ReferenceEqualityComparer : IEqualityComparer<object>
|
||
|
|
{
|
||
|
|
public static readonly ReferenceEqualityComparer Instance = new();
|
||
|
|
|
||
|
|
public new bool Equals(object x, object y) => ReferenceEquals(x, y);
|
||
|
|
|
||
|
|
public int GetHashCode(object obj) =>
|
||
|
|
System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(obj);
|
||
|
|
}
|
||
|
|
}
|