Browse Source

Replace Reflection-Based implementation with Generated one

Co-authored-by: Raul Santos <[email protected]>
Co-authored-by: A Thousand Ships <[email protected]>
DE YU 11 months ago
parent
commit
307224927c

+ 18 - 0
modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs

@@ -7,6 +7,7 @@ using System.Collections.Generic;
 using System.Globalization;
 using System.IO;
 using System.Linq;
+using System.Reflection;
 using GodotTools.Build;
 using GodotTools.Ides;
 using GodotTools.Ides.Rider;
@@ -700,6 +701,23 @@ namespace GodotTools
         private static IntPtr InternalCreateInstance(IntPtr unmanagedCallbacks, int unmanagedCallbacksSize)
         {
             Internal.Initialize(unmanagedCallbacks, unmanagedCallbacksSize);
+
+            var populateConstructorMethod =
+                AppDomain.CurrentDomain
+                    .GetAssemblies()
+                    .First(x => x.GetName().Name == "GodotSharpEditor")
+                    .GetType("Godot.EditorConstructors")?
+                    .GetMethod("AddEditorConstructors",
+                        BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
+
+            if (populateConstructorMethod == null)
+            {
+                throw new MissingMethodException("Godot.EditorConstructors",
+                    "AddEditorConstructors");
+            }
+
+            populateConstructorMethod.Invoke(null, null);
+
             return new GodotSharpEditor().NativeInstance;
         }
     }

+ 127 - 0
modules/mono/editor/bindings_generator.cpp

@@ -77,6 +77,10 @@ StringBuilder &operator<<(StringBuilder &r_sb, const char *p_cstring) {
 #define BINDINGS_GLOBAL_SCOPE_CLASS "GD"
 #define BINDINGS_NATIVE_NAME_FIELD "NativeName"
 
+#define BINDINGS_CLASS_CONSTRUCTOR "Constructors"
+#define BINDINGS_CLASS_CONSTRUCTOR_EDITOR "EditorConstructors"
+#define BINDINGS_CLASS_CONSTRUCTOR_DICTIONARY "BuiltInMethodConstructors"
+
 #define CS_PARAM_MEMORYOWN "memoryOwn"
 #define CS_PARAM_METHODBIND "method"
 #define CS_PARAM_INSTANCE "ptr"
@@ -1737,6 +1741,69 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_proj_dir) {
 		compile_items.push_back(output_file);
 	}
 
+	// Generate source file for built-in type constructor dictionary.
+
+	{
+		StringBuilder cs_built_in_ctors_content;
+
+		cs_built_in_ctors_content.append("namespace " BINDINGS_NAMESPACE ";\n\n");
+		cs_built_in_ctors_content.append("using System;\n"
+										 "using System.Collections.Generic;\n"
+										 "\n");
+		cs_built_in_ctors_content.append("internal static class " BINDINGS_CLASS_CONSTRUCTOR "\n{");
+
+		cs_built_in_ctors_content.append(MEMBER_BEGIN "internal static readonly Dictionary<string, Func<IntPtr, GodotObject>> " BINDINGS_CLASS_CONSTRUCTOR_DICTIONARY ";\n");
+
+		cs_built_in_ctors_content.append(MEMBER_BEGIN "public static GodotObject Invoke(string nativeTypeNameStr, IntPtr nativeObjectPtr)\n");
+		cs_built_in_ctors_content.append(INDENT1 OPEN_BLOCK);
+		cs_built_in_ctors_content.append(INDENT2 "if (!" BINDINGS_CLASS_CONSTRUCTOR_DICTIONARY ".TryGetValue(nativeTypeNameStr, out var constructor))\n");
+		cs_built_in_ctors_content.append(INDENT3 "throw new InvalidOperationException(\"Wrapper class not found for type: \" + nativeTypeNameStr);\n");
+		cs_built_in_ctors_content.append(INDENT2 "return constructor(nativeObjectPtr);\n");
+		cs_built_in_ctors_content.append(INDENT1 CLOSE_BLOCK);
+
+		cs_built_in_ctors_content.append(MEMBER_BEGIN "static " BINDINGS_CLASS_CONSTRUCTOR "()\n");
+		cs_built_in_ctors_content.append(INDENT1 OPEN_BLOCK);
+		cs_built_in_ctors_content.append(INDENT2 BINDINGS_CLASS_CONSTRUCTOR_DICTIONARY " = new();\n");
+
+		for (const KeyValue<StringName, TypeInterface> &E : obj_types) {
+			const TypeInterface &itype = E.value;
+
+			if (itype.api_type != ClassDB::API_CORE || itype.is_singleton_instance) {
+				continue;
+			}
+
+			if (itype.is_deprecated) {
+				cs_built_in_ctors_content.append("#pragma warning disable CS0618\n");
+			}
+
+			cs_built_in_ctors_content.append(INDENT2 BINDINGS_CLASS_CONSTRUCTOR_DICTIONARY ".Add(\"");
+			cs_built_in_ctors_content.append(itype.name);
+			cs_built_in_ctors_content.append("\", " CS_PARAM_INSTANCE " => new ");
+			cs_built_in_ctors_content.append(itype.proxy_name);
+			if (itype.is_singleton && !itype.is_compat_singleton) {
+				cs_built_in_ctors_content.append("Instance");
+			}
+			cs_built_in_ctors_content.append("(" CS_PARAM_INSTANCE "));\n");
+
+			if (itype.is_deprecated) {
+				cs_built_in_ctors_content.append("#pragma warning restore CS0618\n");
+			}
+		}
+
+		cs_built_in_ctors_content.append(INDENT1 CLOSE_BLOCK);
+
+		cs_built_in_ctors_content.append(CLOSE_BLOCK);
+
+		String constructors_file = path::join(base_gen_dir, BINDINGS_CLASS_CONSTRUCTOR ".cs");
+		Error err = _save_file(constructors_file, cs_built_in_ctors_content);
+
+		if (err != OK) {
+			return err;
+		}
+
+		compile_items.push_back(constructors_file);
+	}
+
 	// Generate native calls
 
 	StringBuilder cs_icalls_content;
@@ -1844,6 +1911,57 @@ Error BindingsGenerator::generate_cs_editor_project(const String &p_proj_dir) {
 		compile_items.push_back(output_file);
 	}
 
+	// Generate source file for editor type constructor dictionary.
+
+	{
+		StringBuilder cs_built_in_ctors_content;
+
+		cs_built_in_ctors_content.append("namespace " BINDINGS_NAMESPACE ";\n\n");
+		cs_built_in_ctors_content.append("internal static class " BINDINGS_CLASS_CONSTRUCTOR_EDITOR "\n{");
+
+		cs_built_in_ctors_content.append(MEMBER_BEGIN "private static void AddEditorConstructors()\n");
+		cs_built_in_ctors_content.append(INDENT1 OPEN_BLOCK);
+		cs_built_in_ctors_content.append(INDENT2 "var builtInMethodConstructors = " BINDINGS_CLASS_CONSTRUCTOR "." BINDINGS_CLASS_CONSTRUCTOR_DICTIONARY ";\n");
+
+		for (const KeyValue<StringName, TypeInterface> &E : obj_types) {
+			const TypeInterface &itype = E.value;
+
+			if (itype.api_type != ClassDB::API_EDITOR || itype.is_singleton_instance) {
+				continue;
+			}
+
+			if (itype.is_deprecated) {
+				cs_built_in_ctors_content.append("#pragma warning disable CS0618\n");
+			}
+
+			cs_built_in_ctors_content.append(INDENT2 "builtInMethodConstructors.Add(\"");
+			cs_built_in_ctors_content.append(itype.name);
+			cs_built_in_ctors_content.append("\", " CS_PARAM_INSTANCE " => new ");
+			cs_built_in_ctors_content.append(itype.proxy_name);
+			if (itype.is_singleton && !itype.is_compat_singleton) {
+				cs_built_in_ctors_content.append("Instance");
+			}
+			cs_built_in_ctors_content.append("(" CS_PARAM_INSTANCE "));\n");
+
+			if (itype.is_deprecated) {
+				cs_built_in_ctors_content.append("#pragma warning restore CS0618\n");
+			}
+		}
+
+		cs_built_in_ctors_content.append(INDENT1 CLOSE_BLOCK);
+
+		cs_built_in_ctors_content.append(CLOSE_BLOCK);
+
+		String constructors_file = path::join(base_gen_dir, BINDINGS_CLASS_CONSTRUCTOR_EDITOR ".cs");
+		Error err = _save_file(constructors_file, cs_built_in_ctors_content);
+
+		if (err != OK) {
+			return err;
+		}
+
+		compile_items.push_back(constructors_file);
+	}
+
 	// Generate native calls
 
 	StringBuilder cs_icalls_content;
@@ -2210,6 +2328,15 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str
 					   << CLOSE_BLOCK_L2 CLOSE_BLOCK_L1;
 			}
 
+			output << MEMBER_BEGIN "internal " << itype.proxy_name << "(IntPtr " CS_PARAM_INSTANCE ") : this("
+				   << (itype.memory_own ? "true" : "false") << ")\n" OPEN_BLOCK_L1
+				   << INDENT2 "NativePtr = " CS_PARAM_INSTANCE ";\n"
+				   << INDENT2 "unsafe\n" INDENT2 OPEN_BLOCK
+				   << INDENT3 "ConstructAndInitialize(null, "
+				   << BINDINGS_NATIVE_NAME_FIELD ", CachedType, refCounted: "
+				   << (itype.is_ref_counted ? "true" : "false") << ");\n"
+				   << CLOSE_BLOCK_L2 CLOSE_BLOCK_L1;
+
 			// Add.. em.. trick constructor. Sort of.
 			output.append(MEMBER_BEGIN "internal ");
 			output.append(itype.proxy_name);

+ 2 - 74
modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs

@@ -93,27 +93,15 @@ namespace Godot.Bridge
         internal static unsafe IntPtr CreateManagedForGodotObjectBinding(godot_string_name* nativeTypeName,
             IntPtr godotObject)
         {
-            // TODO: Optimize with source generators and delegate pointers.
-
             try
             {
                 using var stringName = StringName.CreateTakingOwnershipOfDisposableValue(
                     NativeFuncs.godotsharp_string_name_new_copy(CustomUnsafe.AsRef(nativeTypeName)));
                 string nativeTypeNameStr = stringName.ToString();
 
-                Type nativeType = TypeGetProxyClass(nativeTypeNameStr) ?? throw new InvalidOperationException(
-                    "Wrapper class not found for type: " + nativeTypeNameStr);
-                var obj = (GodotObject)FormatterServices.GetUninitializedObject(nativeType);
-
-                var ctor = nativeType.GetConstructor(
-                    BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance,
-                    null, Type.EmptyTypes, null);
-
-                obj.NativePtr = godotObject;
-
-                _ = ctor!.Invoke(obj, null);
+                var instance = Constructors.Invoke(nativeTypeNameStr, godotObject);
 
-                return GCHandle.ToIntPtr(CustomGCHandle.AllocStrong(obj));
+                return GCHandle.ToIntPtr(CustomGCHandle.AllocStrong(instance));
             }
             catch (Exception e)
             {
@@ -308,66 +296,6 @@ namespace Godot.Bridge
             }
         }
 
-        private static Type? TypeGetProxyClass(string nativeTypeNameStr)
-        {
-            // Performance is not critical here as this will be replaced with a generated dictionary.
-
-            if (nativeTypeNameStr[0] == '_')
-                nativeTypeNameStr = nativeTypeNameStr.Substring(1);
-
-            Type? wrapperType = typeof(GodotObject).Assembly.GetType("Godot." + nativeTypeNameStr);
-
-            if (wrapperType == null)
-            {
-                wrapperType = GetTypeByGodotClassAttr(typeof(GodotObject).Assembly, nativeTypeNameStr);
-            }
-
-            if (wrapperType == null)
-            {
-                var editorAssembly = AppDomain.CurrentDomain.GetAssemblies()
-                    .FirstOrDefault(a => a.GetName().Name == "GodotSharpEditor");
-
-                if (editorAssembly != null)
-                {
-                    wrapperType = editorAssembly.GetType("Godot." + nativeTypeNameStr);
-
-                    if (wrapperType == null)
-                    {
-                        wrapperType = GetTypeByGodotClassAttr(editorAssembly, nativeTypeNameStr);
-                    }
-                }
-            }
-
-            static Type? GetTypeByGodotClassAttr(Assembly assembly, string nativeTypeNameStr)
-            {
-                var types = assembly.GetTypes();
-                foreach (var type in types)
-                {
-                    var attr = type.GetCustomAttribute<GodotClassNameAttribute>();
-                    if (attr?.Name == nativeTypeNameStr)
-                    {
-                        return type;
-                    }
-                }
-                return null;
-            }
-
-            static bool IsStatic(Type type) => type.IsAbstract && type.IsSealed;
-
-            if (wrapperType != null && IsStatic(wrapperType))
-            {
-                // A static class means this is a Godot singleton class. Try to get the Instance proxy type.
-                wrapperType = TypeGetProxyClass($"{wrapperType.Name}Instance");
-                if (wrapperType == null)
-                {
-                    // Otherwise, fallback to GodotObject.
-                    return typeof(GodotObject);
-                }
-            }
-
-            return wrapperType;
-        }
-
         // Called from GodotPlugins
         // ReSharper disable once UnusedMember.Local
         public static void LookupScriptsInAssembly(Assembly assembly)

+ 11 - 0
modules/mono/glue/GodotSharp/GodotSharp/Core/GodotObject.base.cs

@@ -29,6 +29,17 @@ namespace Godot
             }
         }
 
+        internal GodotObject(IntPtr nativePtr) : this(false)
+        {
+            // NativePtr must be non-zero before calling ConstructAndInitialize to avoid invoking the constructor NativeCtor.
+            // We don't want to invoke the constructor, because we already have a constructed instance in nativePtr.
+            NativePtr = nativePtr;
+            unsafe
+            {
+                ConstructAndInitialize(NativeCtor, NativeName, _cachedType, refCounted: false);
+            }
+        }
+
         internal unsafe void ConstructAndInitialize(
             delegate* unmanaged<godot_bool, IntPtr> nativeCtor,
             StringName nativeName,