From b154cbf39d255d2e84a89efd55a5932d33e8480d Mon Sep 17 00:00:00 2001
From: sinaioutlander <49360850+sinaioutlander@users.noreply.github.com>
Date: Fri, 18 Sep 2020 18:03:17 +1000
Subject: [PATCH] Add support for generic methods, improved non-generic
dictionary output
---
README.md | 4 +-
src/CachedObjects/CacheObjectBase.cs | 82 +++++++++++++++++----
src/CachedObjects/Object/CacheDictionary.cs | 6 +-
src/CachedObjects/Other/CacheMethod.cs | 67 +++++++++++++++--
src/Menu/UIStyles.cs | 2 +
src/Tests/TestClass.cs | 13 ++++
6 files changed, 149 insertions(+), 25 deletions(-)
diff --git a/README.md b/README.md
index 3522dc8..b26ec67 100644
--- a/README.md
+++ b/README.md
@@ -99,9 +99,9 @@ CppExplorer has two main inspector modes: GameObject Inspector, and 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
diff --git a/src/CachedObjects/CacheObjectBase.cs b/src/CachedObjects/CacheObjectBase.cs
index a95a6f2..26d2fb6 100644
--- a/src/CachedObjects/CacheObjectBase.cs
+++ b/src/CachedObjects/CacheObjectBase.cs
@@ -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($"Generic Arguments:", null);
- var label = $"{type} ";
- label += $"{name}";
- if (m_arguments[i].HasDefaultValue)
+ for (int i = 0; i < cm.GenericArgs.Length; i++)
{
- label = $"[{label} = {m_arguments[i].DefaultValue ?? "null"}]";
+ var type = cm.GenericConstraints[i]?.FullName ?? "None";
+ var input = cm.GenericArgInput[i];
+ var label = $"{type}";
+
+ GUILayout.BeginHorizontal(null);
+
+ GUI.skin.label.alignment = TextAnchor.MiddleCenter;
+ GUILayout.Label($"{cm.GenericArgs[i].Name}", 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($"Arguments:", 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 = $"{type} ";
+ label += $"{name}";
+ if (m_arguments[i].HasDefaultValue)
+ {
+ label = $"[{label} = {m_arguments[i].DefaultValue ?? "null"}]";
+ }
- 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 += $"{MemInfo.Name}";
if (isStatic) m_richTextName += "";
+ // 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 += $"{cm.GenericArgs[i].Name}";
+ }
+ m_richTextName += args;
+
+ m_richTextName += ">";
+ }
+
+ // Method / Property arguments
+
//if (m_arguments.Length > 0 || this is CacheMethod)
//{
// m_richTextName += "(";
diff --git a/src/CachedObjects/Object/CacheDictionary.cs b/src/CachedObjects/Object/CacheDictionary.cs
index 6f2b4ce..a259220 100644
--- a/src/CachedObjects/Object/CacheDictionary.cs
+++ b/src/CachedObjects/Object/CacheDictionary.cs
@@ -133,14 +133,16 @@ namespace Explorer
var keys = new List();
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();
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);
}
diff --git a/src/CachedObjects/Other/CacheMethod.cs b/src/CachedObjects/Other/CacheMethod.cs
index 03f3bcf..2d53398 100644
--- a/src/CachedObjects/Other/CacheMethod.cs
+++ b/src/CachedObjects/Other/CacheMethod.cs
@@ -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();
+ 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]);
diff --git a/src/Menu/UIStyles.cs b/src/Menu/UIStyles.cs
index d2fc84e..f45278f 100644
--- a/src/Menu/UIStyles.cs
+++ b/src/Menu/UIStyles.cs
@@ -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);
diff --git a/src/Tests/TestClass.cs b/src/Tests/TestClass.cs
index f6e59e2..77f3153 100644
--- a/src/Tests/TestClass.cs
+++ b/src/Tests/TestClass.cs
@@ -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(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 obj) where T : Component
+ //{
+ // return obj;
+ //}
+
// test a non-generic dictionary
public Hashtable TestNonGenericDict()