rokojori_action_library/Tools/object-inspector/ObjectGraphInspector.cs

122 lines
2.8 KiB
C#
Raw Normal View History

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);
}
}