some early work on Reflection Inspector

This commit is contained in:
sinaioutlander
2020-11-09 21:38:25 +11:00
parent 5e761e2379
commit 6766a8cf4c
26 changed files with 1178 additions and 929 deletions

View File

@ -0,0 +1,26 @@
//using System;
//using System.Collections;
//using System.Collections.Generic;
//using System.Linq;
//using System.Text;
//using UnityExplorer.UI;
//namespace UnityExplorer.Inspectors.Reflection
//{
// public class CacheEnumerated : CacheObjectBase
// {
// public int Index { get; set; }
// public IList RefIList { get; set; }
// public InteractiveEnumerable ParentEnumeration { get; set; }
// public override bool CanWrite => RefIList != null && ParentEnumeration.OwnerCacheObject.CanWrite;
// public override void SetValue()
// {
// RefIList[Index] = IValue.Value;
// ParentEnumeration.Value = RefIList;
// ParentEnumeration.OwnerCacheObject.SetValue();
// }
// }
//}

View File

@ -0,0 +1,75 @@
using System;
using System.Reflection;
using UnityEngine;
using UnityExplorer.Helpers;
namespace UnityExplorer.Inspectors.Reflection
{
public static class CacheFactory
{
// Don't think I need these with new structure.
// Will possibly need something for CacheEnumerated / InteractiveEnumeration though.
//public static CacheObjectBase GetCacheObject(object obj)
//{
// if (obj == null) return null;
// return GetCacheObject(obj, ReflectionHelpers.GetActualType(obj));
//}
//public static CacheObjectBase GetCacheObject(object obj, Type type)
//{
// var ret = new CacheObjectBase();
// ret.InitValue(obj, type);
// return ret;
//}
public static CacheMember GetCacheObject(MemberInfo member, object declaringInstance)
{
CacheMember ret;
if (member is MethodInfo mi && CanProcessArgs(mi.GetParameters()))
{
ret = new CacheMethod(mi, declaringInstance);
}
else if (member is PropertyInfo pi && CanProcessArgs(pi.GetIndexParameters()))
{
ret = new CacheProperty(pi, declaringInstance);
}
else if (member is FieldInfo fi)
{
ret = new CacheField(fi, declaringInstance);
}
else
{
return null;
}
return ret;
}
public static bool CanProcessArgs(ParameterInfo[] parameters)
{
foreach (var param in parameters)
{
var pType = param.ParameterType;
if (pType.IsByRef && pType.HasElementType)
{
pType = pType.GetElementType();
}
if (pType.IsPrimitive || pType == typeof(string))
{
continue;
}
else
{
return false;
}
}
return true;
}
}
}

View File

@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using UnityExplorer.UI;
using UnityExplorer.Helpers;
namespace UnityExplorer.Inspectors.Reflection
{
public class CacheField : CacheMember
{
public override bool IsStatic => (MemInfo as FieldInfo).IsStatic;
public CacheField(FieldInfo fieldInfo, object declaringInstance) : base(fieldInfo, declaringInstance)
{
base.InitValue(null, fieldInfo.FieldType);
UpdateValue();
}
public override void UpdateValue()
{
//if (IValue is InteractiveDictionary iDict)
//{
// if (!iDict.EnsureDictionaryIsSupported())
// {
// ReflectionException = "Not supported due to TypeInitializationException";
// return;
// }
//}
try
{
var fi = MemInfo as FieldInfo;
IValue.Value = fi.GetValue(fi.IsStatic ? null : DeclaringInstance);
base.UpdateValue();
}
catch (Exception e)
{
ReflectionException = ReflectionHelpers.ExceptionToString(e);
}
}
public override void SetValue()
{
var fi = MemInfo as FieldInfo;
fi.SetValue(fi.IsStatic ? null : DeclaringInstance, IValue.Value);
}
}
}

View File

@ -0,0 +1,222 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEngine;
using UnityExplorer.UI;
using UnityExplorer.UI.Shared;
namespace UnityExplorer.Inspectors.Reflection
{
public class CacheMember : CacheObjectBase
{
public MemberInfo MemInfo { get; set; }
public Type DeclaringType { get; set; }
public object DeclaringInstance { get; set; }
public virtual bool IsStatic { get; private set; }
public override bool HasParameters => m_arguments != null && m_arguments.Length > 0;
public override bool IsMember => true;
public string RichTextName => m_richTextName ?? GetRichTextName();
private string m_richTextName;
public override bool CanWrite => m_canWrite ?? GetCanWrite();
private bool? m_canWrite;
public string ReflectionException { get; set; }
public bool m_evaluated = false;
public bool m_isEvaluating;
public ParameterInfo[] m_arguments = new ParameterInfo[0];
public string[] m_argumentInput = new string[0];
public CacheMember(MemberInfo memberInfo, object declaringInstance)
{
MemInfo = memberInfo;
DeclaringType = memberInfo.DeclaringType;
DeclaringInstance = declaringInstance;
}
//public virtual void InitMember(MemberInfo member, object declaringInstance)
//{
// MemInfo = member;
// DeclaringInstance = declaringInstance;
// DeclaringType = member.DeclaringType;
//}
public override void UpdateValue()
{
base.UpdateValue();
}
public override void SetValue()
{
// ...
}
public object[] ParseArguments()
{
if (m_arguments.Length < 1)
{
return new object[0];
}
var parsedArgs = new List<object>();
for (int i = 0; i < m_arguments.Length; i++)
{
var input = m_argumentInput[i];
var type = m_arguments[i].ParameterType;
if (type.IsByRef)
{
type = type.GetElementType();
}
if (!string.IsNullOrEmpty(input))
{
if (type == typeof(string))
{
parsedArgs.Add(input);
continue;
}
else
{
try
{
var arg = type.GetMethod("Parse", new Type[] { typeof(string) })
.Invoke(null, new object[] { input });
parsedArgs.Add(arg);
continue;
}
catch
{
ExplorerCore.Log($"Argument #{i} '{m_arguments[i].Name}' ({type.Name}), could not parse input '{input}'.");
}
}
}
// No input, see if there is a default value.
if (HasDefaultValue(m_arguments[i]))
{
parsedArgs.Add(m_arguments[i].DefaultValue);
continue;
}
// Try add a null arg I guess
parsedArgs.Add(null);
}
return parsedArgs.ToArray();
}
public static bool HasDefaultValue(ParameterInfo arg) => arg.DefaultValue != DBNull.Value;
//public void DrawArgsInput()
//{
// for (int i = 0; i < this.m_arguments.Length; i++)
// {
// var name = this.m_arguments[i].Name;
// var input = this.m_argumentInput[i];
// var type = this.m_arguments[i].ParameterType.Name;
// var label = $"<color={SyntaxColors.Class_Instance}>{type}</color> ";
// label += $"<color={SyntaxColors.Local}>{name}</color>";
// if (HasDefaultValue(this.m_arguments[i]))
// {
// label = $"<i>[{label} = {this.m_arguments[i].DefaultValue ?? "null"}]</i>";
// }
// }
//}
private bool GetCanWrite()
{
if (MemInfo is FieldInfo fi)
m_canWrite = !(fi.IsLiteral && !fi.IsInitOnly);
else if (MemInfo is PropertyInfo pi)
m_canWrite = pi.CanWrite;
else
m_canWrite = false;
return (bool)m_canWrite;
}
private string GetRichTextName()
{
string memberColor = "";
bool isStatic = false;
if (MemInfo is FieldInfo fi)
{
if (fi.IsStatic)
{
isStatic = true;
memberColor = SyntaxColors.Field_Static;
}
else
memberColor = SyntaxColors.Field_Instance;
}
else if (MemInfo is MethodInfo mi)
{
if (mi.IsStatic)
{
isStatic = true;
memberColor = SyntaxColors.Method_Static;
}
else
memberColor = SyntaxColors.Method_Instance;
}
else if (MemInfo is PropertyInfo pi)
{
if (pi.GetAccessors()[0].IsStatic)
{
isStatic = true;
memberColor = SyntaxColors.Prop_Static;
}
else
memberColor = SyntaxColors.Prop_Instance;
}
string classColor;
if (MemInfo.DeclaringType.IsValueType)
{
classColor = SyntaxColors.StructGreen;
}
else if (MemInfo.DeclaringType.IsAbstract && MemInfo.DeclaringType.IsSealed)
{
classColor = SyntaxColors.Class_Static;
}
else
{
classColor = SyntaxColors.Class_Instance;
}
m_richTextName = $"<color={classColor}>{MemInfo.DeclaringType.Name}</color>.";
if (isStatic) m_richTextName += "<i>";
m_richTextName += $"<color={memberColor}>{MemInfo.Name}</color>";
if (isStatic) m_richTextName += "</i>";
// generic method args
if (this is CacheMethod cm && cm.GenericArgs.Length > 0)
{
m_richTextName += "<";
var args = "";
for (int i = 0; i < cm.GenericArgs.Length; i++)
{
if (args != "") args += ", ";
args += $"<color={SyntaxColors.StructGreen}>{cm.GenericArgs[i].Name}</color>";
}
m_richTextName += args;
m_richTextName += ">";
}
return m_richTextName;
}
}
}

View File

@ -0,0 +1,198 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEngine;
using UnityExplorer.UI.Shared;
using UnityExplorer.Helpers;
namespace UnityExplorer.Inspectors.Reflection
{
public class CacheMethod : CacheMember
{
private CacheObjectBase m_cachedReturnValue;
public override bool HasParameters => base.HasParameters || GenericArgs.Length > 0;
public override bool IsStatic => (MemInfo as MethodInfo).IsStatic;
public Type[] GenericArgs { get; private set; }
public Type[][] GenericConstraints { get; private set; }
public string[] GenericArgInput = new string[0];
public CacheMethod(MethodInfo methodInfo, object declaringInstance) : base(methodInfo, declaringInstance)
{
GenericArgs = methodInfo.GetGenericArguments();
GenericConstraints = GenericArgs.Select(x => x.GetGenericParameterConstraints())
.ToArray();
GenericArgInput = new string[GenericArgs.Length];
m_arguments = methodInfo.GetParameters();
m_argumentInput = new string[m_arguments.Length];
base.InitValue(null, methodInfo.ReturnType);
}
public override void UpdateValue()
{
// CacheMethod cannot UpdateValue directly. Need to Evaluate.
}
public void Evaluate()
{
MethodInfo mi;
if (GenericArgs.Length > 0)
{
mi = MakeGenericMethodFromInput();
if (mi == null) return;
}
else
{
mi = MemInfo as MethodInfo;
}
object ret = null;
try
{
ret = mi.Invoke(mi.IsStatic ? null : DeclaringInstance, ParseArguments());
m_evaluated = true;
m_isEvaluating = false;
}
catch (Exception e)
{
ExplorerCore.LogWarning($"Exception evaluating: {e.GetType()}, {e.Message}");
ReflectionException = ReflectionHelpers.ExceptionToString(e);
}
if (ret != null)
{
//m_cachedReturnValue = CacheFactory.GetTypeAndCacheObject(ret);
//m_cachedReturnValue = CacheFactory.GetCacheObject(ret);
m_cachedReturnValue.UpdateValue();
}
else
{
m_cachedReturnValue = null;
}
}
private MethodInfo MakeGenericMethodFromInput()
{
var mi = MemInfo as MethodInfo;
var list = new List<Type>();
for (int i = 0; i < GenericArgs.Length; i++)
{
var input = GenericArgInput[i];
if (ReflectionHelpers.GetTypeByName(input) is Type t)
{
if (GenericConstraints[i].Length == 0)
{
list.Add(t);
}
else
{
foreach (var constraint in GenericConstraints[i].Where(x => x != null))
{
if (!constraint.IsAssignableFrom(t))
{
ExplorerCore.LogWarning($"Generic argument #{i}, '{input}' is not assignable from the constraint '{constraint}'!");
return null;
}
}
list.Add(t);
}
}
else
{
ExplorerCore.LogWarning($"Generic argument #{i}, could not get any type by the name of '{input}'!" +
$" Make sure you use the full name, including the NameSpace.");
return null;
}
}
// make into a generic with type list
mi = mi.MakeGenericMethod(list.ToArray());
return mi;
}
// ==== GUI DRAW ====
//public override void Draw(Rect window, float width)
//{
// base.Draw(window, width);
//}
public void DrawValue(Rect window, float width)
{
string typeLabel = $"<color={SyntaxColors.Class_Instance}>{IValue.ValueType.FullName}</color>";
if (m_evaluated)
{
if (m_cachedReturnValue != null)
{
//m_cachedReturnValue.IValue.DrawValue(window, width);
}
else
{
GUILayout.Label($"null ({typeLabel})", new GUILayoutOption[0]);
}
}
else
{
GUILayout.Label($"<color=grey><i>Not yet evaluated</i></color> ({typeLabel})", new GUILayoutOption[0]);
}
}
//public void DrawGenericArgsInput()
//{
// GUILayout.Label($"<b><color=orange>Generic Arguments:</color></b>", new GUILayoutOption[0]);
// for (int i = 0; i < this.GenericArgs.Length; i++)
// {
// string types = "";
// if (this.GenericConstraints[i].Length > 0)
// {
// foreach (var constraint in this.GenericConstraints[i])
// {
// if (types != "") types += ", ";
// string type;
// if (constraint == null)
// type = "Any";
// else
// type = constraint.ToString();
// types += $"<color={Syntax.Class_Instance}>{type}</color>";
// }
// }
// else
// {
// types = $"<color={Syntax.Class_Instance}>Any</color>";
// }
// var input = this.GenericArgInput[i];
// GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
// GUI.skin.label.alignment = TextAnchor.MiddleCenter;
// GUILayout.Label(
// $"<color={Syntax.StructGreen}>{this.GenericArgs[i].Name}</color>",
// new GUILayoutOption[] { GUILayout.Width(15) }
// );
// this.GenericArgInput[i] = GUIHelper.TextField(input, new GUILayoutOption[] { GUILayout.Width(150) });
// GUI.skin.label.alignment = TextAnchor.MiddleLeft;
// GUILayout.Label(types, new GUILayoutOption[0]);
// GUILayout.EndHorizontal();
// }
//}
}
}

View File

@ -0,0 +1,119 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEngine;
using UnityExplorer.UI;
using UnityExplorer.UI.Shared;
using UnityExplorer.Helpers;
namespace UnityExplorer.Inspectors.Reflection
{
public class CacheObjectBase
{
public InteractiveValue IValue;
public virtual bool CanWrite => false;
public virtual bool HasParameters => false;
public virtual bool IsMember => false;
//public bool IsStaticClassSearchResult { get; set; }
// TODO
public virtual void InitValue(object value, Type valueType)
{
if (valueType == null && value == null)
{
return;
}
//ExplorerCore.Log("Initializing InteractiveValue of type " + valueType.FullName);
// InteractiveValue interactive;
//if (valueType == typeof(GameObject) || valueType == typeof(Transform))
//{
// interactive = new InteractiveGameObject();
//}
//else if (valueType == typeof(Texture2D))
//{
// interactive = new InteractiveTexture2D();
//}
//else if (valueType == typeof(Texture))
//{
// interactive = new InteractiveTexture();
//}
//else if (valueType == typeof(Sprite))
//{
// interactive = new InteractiveSprite();
//}
//else if (valueType.IsPrimitive || valueType == typeof(string))
//{
// interactive = new InteractivePrimitive();
//}
//else if (valueType.IsEnum)
//{
// if (valueType.GetCustomAttributes(typeof(FlagsAttribute), true) is object[] attributes && attributes.Length > 0)
// {
// interactive = new InteractiveFlags();
// }
// else
// {
// interactive = new InteractiveEnum();
// }
//}
//else if (valueType == typeof(Vector2) || valueType == typeof(Vector3) || valueType == typeof(Vector4))
//{
// interactive = new InteractiveVector();
//}
//else if (valueType == typeof(Quaternion))
//{
// interactive = new InteractiveQuaternion();
//}
//else if (valueType == typeof(Color))
//{
// interactive = new InteractiveColor();
//}
//else if (valueType == typeof(Rect))
//{
// interactive = new InteractiveRect();
//}
//// must check this before IsEnumerable
//else if (ReflectionHelpers.IsDictionary(valueType))
//{
// interactive = new InteractiveDictionary();
//}
//else if (ReflectionHelpers.IsEnumerable(valueType))
//{
// interactive = new InteractiveEnumerable();
//}
//else
//{
// interactive = new InteractiveValue();
//}
//interactive.Value = obj;
//interactive.ValueType = valueType;
//this.IValue = interactive;
//this.IValue.OwnerCacheObject = this;
//UpdateValue();
//this.IValue.Init();
}
public virtual void Draw(Rect window, float width)
{
// IValue.Draw(window, width);
}
public virtual void UpdateValue()
{
IValue.UpdateValue();
}
public virtual void SetValue() => throw new NotImplementedException();
}
}

View File

@ -0,0 +1,80 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using UnityExplorer.UI;
using UnityExplorer.Helpers;
namespace UnityExplorer.Inspectors.Reflection
{
public class CacheProperty : CacheMember
{
public override bool IsStatic => (MemInfo as PropertyInfo).GetAccessors()[0].IsStatic;
public CacheProperty(PropertyInfo propertyInfo, object declaringInstance) : base(propertyInfo, declaringInstance)
{
this.m_arguments = propertyInfo.GetIndexParameters();
this.m_argumentInput = new string[m_arguments.Length];
base.InitValue(null, propertyInfo.PropertyType);
UpdateValue();
}
public override void UpdateValue()
{
if (HasParameters && !m_isEvaluating)
{
// Need to enter parameters first.
return;
}
//if (IValue is InteractiveDictionary iDict)
//{
// if (!iDict.EnsureDictionaryIsSupported())
// {
// ReflectionException = "Not supported due to TypeInitializationException";
// return;
// }
//}
try
{
var pi = MemInfo as PropertyInfo;
if (pi.CanRead)
{
var target = pi.GetAccessors()[0].IsStatic ? null : DeclaringInstance;
IValue.Value = pi.GetValue(target, ParseArguments());
base.UpdateValue();
}
else // create a dummy value for Write-Only properties.
{
if (IValue.ValueType == typeof(string))
{
IValue.Value = "";
}
else
{
IValue.Value = Activator.CreateInstance(IValue.ValueType);
}
}
}
catch (Exception e)
{
ReflectionException = ReflectionHelpers.ExceptionToString(e);
}
}
public override void SetValue()
{
var pi = MemInfo as PropertyInfo;
var target = pi.GetAccessors()[0].IsStatic ? null : DeclaringInstance;
pi.SetValue(target, IValue.Value, ParseArguments());
}
}
}

View File

@ -0,0 +1,26 @@
using System;
using UnityExplorer.Helpers;
namespace UnityExplorer.Inspectors.Reflection
{
public class InstanceInspector : ReflectionInspector
{
public override string TabLabel => $" <color=cyan>[R]</color> {base.TabLabel}";
public InstanceInspector(object target) : base(target)
{
}
public override void Update()
{
base.Update();
if (m_pendingDestroy || InspectorManager.Instance.m_activeInspector != this)
{
return;
}
// todo
}
}
}

View File

@ -0,0 +1,104 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEngine;
using UnityExplorer.Helpers;
using UnityExplorer.UI.Shared;
namespace UnityExplorer.Inspectors.Reflection
{
public class InteractiveValue
{
public CacheObjectBase OwnerCacheObject;
public object Value { get; set; }
public Type ValueType;
public string ButtonLabel => m_btnLabel ?? GetButtonLabel();
private string m_btnLabel;
public MethodInfo ToStringMethod => m_toStringMethod ?? GetToStringMethod();
private MethodInfo m_toStringMethod;
public virtual void Init()
{
UpdateValue();
}
public virtual void UpdateValue()
{
GetButtonLabel();
}
private MethodInfo GetToStringMethod()
{
try
{
m_toStringMethod = ReflectionHelpers.GetActualType(Value).GetMethod("ToString", new Type[0])
?? typeof(object).GetMethod("ToString", new Type[0]);
// test invoke
m_toStringMethod.Invoke(Value, null);
}
catch
{
m_toStringMethod = typeof(object).GetMethod("ToString", new Type[0]);
}
return m_toStringMethod;
}
public string GetButtonLabel()
{
if (Value == null) return null;
var valueType = ReflectionHelpers.GetActualType(Value);
string label;
if (valueType == typeof(TextAsset))
{
var textAsset = Value as TextAsset;
label = textAsset.text;
if (label.Length > 10)
{
label = $"{label.Substring(0, 10)}...";
}
label = $"\"{label}\" {textAsset.name} (<color={SyntaxColors.Class_Instance}>UnityEngine.TextAsset</color>)";
}
else
{
label = (string)ToStringMethod?.Invoke(Value, null) ?? Value.ToString();
var classColor = valueType.IsAbstract && valueType.IsSealed
? SyntaxColors.Class_Static
: SyntaxColors.Class_Instance;
string typeLabel = $"<color={classColor}>{valueType.FullName}</color>";
if (Value is UnityEngine.Object)
{
label = label.Replace($"({valueType.FullName})", $"({typeLabel})");
}
else
{
if (!label.Contains(valueType.FullName))
{
label += $" ({typeLabel})";
}
else
{
label = label.Replace(valueType.FullName, typeLabel);
}
}
}
return m_btnLabel = label;
}
}
}

View File

@ -0,0 +1,26 @@
using System;
namespace UnityExplorer.Inspectors.Reflection
{
public class StaticInspector : ReflectionInspector
{
public override string TabLabel => $" <color=cyan>[S]</color> {base.TabLabel}";
public StaticInspector(Type type) : base(type)
{
// TODO
}
public override void Update()
{
base.Update();
if (m_pendingDestroy || InspectorManager.Instance.m_activeInspector != this)
{
return;
}
// todo
}
}
}