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()