From b5cd06b9ae2c25f8965eded3c19a7c238d2eeaed Mon Sep 17 00:00:00 2001 From: DE-YU_H14 Date: Thu, 15 Feb 2024 22:34:02 +0800 Subject: [PATCH] C#: Implement proper generic type name printing for Godot Editor Co-authored-by: Raul Santos --- .../Core/Bridge/ScriptManagerBridge.cs | 30 +-- .../GodotSharp/Core/ReflectionUtils.cs | 179 ++++++++++++++++++ 2 files changed, 181 insertions(+), 28 deletions(-) diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs index eef26cdd4e1..01d91032212 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs @@ -725,15 +725,7 @@ namespace Godot.Bridge { Type native = GodotObject.InternalGetClassNativeBase(scriptType); - string typeName = scriptType.Name; - if (scriptType.IsGenericType) - { - var sb = new StringBuilder(); - AppendTypeName(sb, scriptType); - typeName = sb.ToString(); - } - - godot_string className = Marshaling.ConvertStringToNative(typeName); + godot_string className = Marshaling.ConvertStringToNative(ReflectionUtils.ConstructTypeName(scriptType)); bool isTool = scriptType.IsDefined(typeof(ToolAttribute), inherit: false); @@ -766,24 +758,6 @@ namespace Godot.Bridge outTypeInfo->IsGenericTypeDefinition = scriptType.IsGenericTypeDefinition.ToGodotBool(); outTypeInfo->IsConstructedGenericType = scriptType.IsConstructedGenericType.ToGodotBool(); - static void AppendTypeName(StringBuilder sb, Type type) - { - sb.Append(type.Name); - if (type.IsGenericType) - { - sb.Append('<'); - for (int i = 0; i < type.GenericTypeArguments.Length; i++) - { - Type typeArg = type.GenericTypeArguments[i]; - AppendTypeName(sb, typeArg); - if (i != type.GenericTypeArguments.Length - 1) - { - sb.Append(", "); - } - } - sb.Append('>'); - } - } } [UnmanagedCallersOnly] @@ -1097,7 +1071,7 @@ namespace Godot.Bridge interopProperties[i] = interopProperty; } - using godot_string currentClassName = Marshaling.ConvertStringToNative(type.Name); + using godot_string currentClassName = Marshaling.ConvertStringToNative(ReflectionUtils.ConstructTypeName(type)); addPropInfoFunc(scriptPtr, ¤tClassName, interopProperties, length); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/ReflectionUtils.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/ReflectionUtils.cs index ee605f8d8f2..27989b5c813 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/ReflectionUtils.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/ReflectionUtils.cs @@ -1,5 +1,8 @@ using System; +using System.Collections.Generic; +using System.Diagnostics; using System.Linq; +using System.Text; #nullable enable @@ -7,10 +10,186 @@ namespace Godot; internal class ReflectionUtils { + private static readonly HashSet? _tupleTypeSet; + private static readonly Dictionary? _builtinTypeNameDictionary; + private static readonly bool _isEditorHintCached; + + static ReflectionUtils() + { + _isEditorHintCached = Engine.IsEditorHint(); + if (!_isEditorHintCached) + { + return; + } + + _tupleTypeSet = new HashSet + { + // ValueTuple with only one element should be treated as normal generic type. + //typeof(ValueTuple<>), + typeof(ValueTuple<,>), + typeof(ValueTuple<,,>), + typeof(ValueTuple<,,,>), + typeof(ValueTuple<,,,,>), + typeof(ValueTuple<,,,,,>), + typeof(ValueTuple<,,,,,,>), + typeof(ValueTuple<,,,,,,,>), + }; + + _builtinTypeNameDictionary ??= new Dictionary + { + { typeof(sbyte), "sbyte" }, + { typeof(byte), "byte" }, + { typeof(short), "short" }, + { typeof(ushort), "ushort" }, + { typeof(int), "int" }, + { typeof(uint), "uint" }, + { typeof(long), "long" }, + { typeof(ulong), "ulong" }, + { typeof(nint), "nint" }, + { typeof(nuint), "nuint" }, + { typeof(float), "float" }, + { typeof(double), "double" }, + { typeof(decimal), "decimal" }, + { typeof(bool), "bool" }, + { typeof(char), "char" }, + { typeof(string), "string" }, + { typeof(object), "object" }, + }; + } + public static Type? FindTypeInLoadedAssemblies(string assemblyName, string typeFullName) { return AppDomain.CurrentDomain.GetAssemblies() .FirstOrDefault(a => a.GetName().Name == assemblyName)? .GetType(typeFullName); } + + public static string ConstructTypeName(Type type) + { + if (!_isEditorHintCached) + { + return type.Name; + } + + if (type is { IsArray: false, IsGenericType: false }) + { + return GetSimpleTypeName(type); + } + + var typeNameBuilder = new StringBuilder(); + AppendType(typeNameBuilder, type); + return typeNameBuilder.ToString(); + + static void AppendType(StringBuilder sb, Type type) + { + if (type.IsArray) + { + AppendArray(sb, type); + } + else if (type.IsGenericType) + { + AppendGeneric(sb, type); + } + else + { + sb.Append(GetSimpleTypeName(type)); + } + } + + static void AppendArray(StringBuilder sb, Type type) + { + // Append inner most non-array element. + var elementType = type.GetElementType()!; + while (elementType.IsArray) + { + elementType = elementType.GetElementType()!; + } + + AppendType(sb, elementType); + // Append brackets. + AppendArrayBrackets(sb, type); + + static void AppendArrayBrackets(StringBuilder sb, Type type) + { + while (type != null && type.IsArray) + { + int rank = type.GetArrayRank(); + sb.Append('['); + sb.Append(',', rank - 1); + sb.Append(']'); + type = type.GetElementType(); + } + } + } + + static void AppendGeneric(StringBuilder sb, Type type) + { + var genericArgs = type.GenericTypeArguments; + var genericDefinition = type.GetGenericTypeDefinition(); + + // Nullable + if (genericDefinition == typeof(Nullable<>)) + { + AppendType(sb, genericArgs[0]); + sb.Append('?'); + return; + } + + // ValueTuple + Debug.Assert(_tupleTypeSet != null); + if (_tupleTypeSet.Contains(genericDefinition)) + { + sb.Append('('); + while (true) + { + // We assume that ValueTuple has 1~8 elements. + // And the 8th element (TRest) is always another ValueTuple. + + // This is a hard coded tuple element length check. + if (genericArgs.Length != 8) + { + AppendParamTypes(sb, genericArgs); + break; + } + else + { + AppendParamTypes(sb, genericArgs.AsSpan(0, 7)); + sb.Append(", "); + + // TRest should be a ValueTuple! + var nextTuple = genericArgs[7]; + + genericArgs = nextTuple.GenericTypeArguments; + } + } + sb.Append(')'); + return; + } + + // Normal generic + var typeName = type.Name.AsSpan(); + sb.Append(typeName[..typeName.LastIndexOf('`')]); + sb.Append('<'); + AppendParamTypes(sb, genericArgs); + sb.Append('>'); + + static void AppendParamTypes(StringBuilder sb, ReadOnlySpan genericArgs) + { + int n = genericArgs.Length - 1; + for (int i = 0; i < n; i += 1) + { + AppendType(sb, genericArgs[i]); + sb.Append(", "); + } + + AppendType(sb, genericArgs[n]); + } + } + + static string GetSimpleTypeName(Type type) + { + Debug.Assert(_builtinTypeNameDictionary != null); + return _builtinTypeNameDictionary.TryGetValue(type, out string? name) ? name : type.Name; + } + } }