Add support for generic methods, improved non-generic dictionary output

This commit is contained in:
sinaioutlander 2020-09-18 18:03:17 +10:00
parent db91968519
commit b154cbf39d
6 changed files with 149 additions and 25 deletions

View File

@ -99,9 +99,9 @@ CppExplorer has two main inspector modes: <b>GameObject Inspector</b>, and <b>Re
* Filter by name, type, etc.
* For GameObjects and Transforms you can filter which scene they are found in too.
### C# REPL console
### C# console
* A simple C# REPL console, allows you to execute a method body on the fly.
* A simple C# console, allows you to execute a method body on the fly.
### Inspect-under-mouse

View File

@ -20,7 +20,8 @@ namespace Explorer
public Type DeclaringType { get; set; }
public object DeclaringInstance { get; set; }
public bool HasParameters => m_arguments != null && m_arguments.Length > 0;
public virtual bool HasParameters => m_arguments != null && m_arguments.Length > 0;
public bool m_evaluated = false;
public bool m_isEvaluating;
public ParameterInfo[] m_arguments = new ParameterInfo[0];
@ -394,26 +395,54 @@ namespace Explorer
if (m_isEvaluating)
{
for (int i = 0; i < m_arguments.Length; i++)
if (cm != null && cm.GenericArgs.Length > 0)
{
var name = m_arguments[i].Name;
var input = m_argumentInput[i];
var type = m_arguments[i].ParameterType.Name;
GUILayout.Label($"<b><color=orange>Generic Arguments:</color></b>", null);
var label = $"<color={UIStyles.Syntax.Class_Instance}>{type}</color> ";
label += $"<color={UIStyles.Syntax.Local}>{name}</color>";
if (m_arguments[i].HasDefaultValue)
for (int i = 0; i < cm.GenericArgs.Length; i++)
{
label = $"<i>[{label} = {m_arguments[i].DefaultValue ?? "null"}]</i>";
var type = cm.GenericConstraints[i]?.FullName ?? "None";
var input = cm.GenericArgInput[i];
var label = $"<color={UIStyles.Syntax.Class_Instance}>{type}</color>";
GUILayout.BeginHorizontal(null);
GUI.skin.label.alignment = TextAnchor.MiddleCenter;
GUILayout.Label($"<color={UIStyles.Syntax.StructGreen}>{cm.GenericArgs[i].Name}</color>", new GUILayoutOption[] { GUILayout.Width(15) });
cm.GenericArgInput[i] = GUILayout.TextField(input, new GUILayoutOption[] { GUILayout.Width(150) });
GUI.skin.label.alignment = TextAnchor.MiddleLeft;
GUILayout.Label(label, null);
GUILayout.EndHorizontal();
}
}
GUILayout.BeginHorizontal(null);
if (m_arguments.Length > 0)
{
GUILayout.Label($"<b><color=orange>Arguments:</color></b>", null);
for (int i = 0; i < m_arguments.Length; i++)
{
var name = m_arguments[i].Name;
var input = m_argumentInput[i];
var type = m_arguments[i].ParameterType.Name;
GUILayout.Label(i.ToString(), new GUILayoutOption[] { GUILayout.Width(20) });
m_argumentInput[i] = GUILayout.TextField(input, new GUILayoutOption[] { GUILayout.Width(150) });
GUILayout.Label(label, null);
var label = $"<color={UIStyles.Syntax.Class_Instance}>{type}</color> ";
label += $"<color={UIStyles.Syntax.Local}>{name}</color>";
if (m_arguments[i].HasDefaultValue)
{
label = $"<i>[{label} = {m_arguments[i].DefaultValue ?? "null"}]</i>";
}
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUI.skin.label.alignment = TextAnchor.MiddleCenter;
GUILayout.Label(i.ToString(), new GUILayoutOption[] { GUILayout.Width(15) });
m_argumentInput[i] = GUILayout.TextField(input, new GUILayoutOption[] { GUILayout.Width(150) });
GUI.skin.label.alignment = TextAnchor.MiddleLeft;
GUILayout.Label(label, null);
GUILayout.EndHorizontal();
}
}
GUILayout.BeginHorizontal(null);
@ -436,7 +465,12 @@ namespace Explorer
}
else
{
if (GUILayout.Button($"Evaluate ({m_arguments.Length} params)", new GUILayoutOption[] { GUILayout.Width(150) }))
var lbl = $"Evaluate (";
int args = m_arguments.Length;
if (cm != null) args += cm.GenericArgs.Length;
lbl += args + " params)";
if (GUILayout.Button(lbl, new GUILayoutOption[] { GUILayout.Width(150) }))
{
m_isEvaluating = true;
}
@ -527,6 +561,24 @@ namespace Explorer
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={UIStyles.Syntax.StructGreen}>{cm.GenericArgs[i].Name}</color>";
}
m_richTextName += args;
m_richTextName += ">";
}
// Method / Property arguments
//if (m_arguments.Length > 0 || this is CacheMethod)
//{
// m_richTextName += "(";

View File

@ -133,14 +133,16 @@ namespace Explorer
var keys = new List<CacheObjectBase>();
foreach (var key in IDict.Keys)
{
var cache = GetCacheObject(key, TypeOfKeys);
Type t = ReflectionHelpers.GetActualType(key) ?? TypeOfKeys;
var cache = GetCacheObject(key, t);
keys.Add(cache);
}
var values = new List<CacheObjectBase>();
foreach (var val in IDict.Values)
{
var cache = GetCacheObject(val, TypeOfValues);
Type t = ReflectionHelpers.GetActualType(val) ?? TypeOfValues;
var cache = GetCacheObject(val, t);
values.Add(cache);
}

View File

@ -13,18 +13,34 @@ namespace Explorer
{
private CacheObjectBase m_cachedReturnValue;
public override bool HasParameters => base.HasParameters || GenericArgs.Length > 0;
public Type[] GenericArgs { get; private set; }
public Type[] GenericConstraints { get; private set; }
public string[] GenericArgInput = new string[0];
public static bool CanEvaluate(MethodInfo mi)
{
// TODO generic args
if (mi.GetGenericArguments().Length > 0)
{
return false;
}
// primitive and string args supported
return CanProcessArgs(mi.GetParameters());
}
public override void Init()
{
var mi = (MemInfo as MethodInfo);
GenericArgs = mi.GetGenericArguments();
GenericConstraints = GenericArgs.Select(x => x.GetGenericParameterConstraints()
.FirstOrDefault())
.ToArray();
GenericArgInput = new string[GenericArgs.Length];
ValueType = mi.ReturnType;
ValueTypeName = ValueType.FullName;
}
public override void UpdateValue()
{
//base.UpdateValue();
@ -37,6 +53,45 @@ namespace Explorer
var mi = MemInfo as MethodInfo;
object ret = null;
// Parse generic arguments
if (GenericArgs.Length > 0)
{
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] == null)
{
list.Add(t);
}
else
{
if (GenericConstraints[i].IsAssignableFrom(t))
{
list.Add(t);
}
else
{
MelonLogger.Log($"Generic argument #{i} '{input}', is not assignable from the generic constraint!");
return;
}
}
}
else
{
MelonLogger.Log($"Generic argument #{i}, could not get any type by the name of '{input}'!" +
$" Make sure you use the full name, including the NameSpace.");
return;
}
}
// make into a generic with type list
mi = mi.MakeGenericMethod(list.ToArray());
}
// Parse arguments
if (!HasParameters)
{
ret = mi.Invoke(mi.IsStatic ? null : DeclaringInstance, new object[0]);

View File

@ -26,6 +26,8 @@ namespace Explorer
public const string Class_Instance = "#2df7b2";
public const string Local = "#a6e9e9";
public const string StructGreen = "#b8d7a3";
}
public static Color LightGreen = new Color(Color.green.r - 0.3f, Color.green.g - 0.3f, Color.green.b - 0.3f);

View File

@ -5,6 +5,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MelonLoader;
using Mono.CSharp.Linq;
using UnityEngine;
namespace Explorer.Tests
@ -26,6 +27,18 @@ namespace Explorer.Tests
public static int StaticField = 5;
public int NonStaticField;
// test a generic method
public static string TestGeneric<C, T>(string arg0) where C : Component
{
return "C: " + typeof(C).FullName + ", T: " + typeof(T).FullName + ", arg0: " + arg0;
}
//// this type of generic is not supported, due to requiring a non-primitive argument.
//public static T TestDifferentGeneric<T>(T obj) where T : Component
//{
// return obj;
//}
// test a non-generic dictionary
public Hashtable TestNonGenericDict()