Browse Source

C#: Add source generator for method list

Ignacio Roldán Etcheverry 3 years ago
parent
commit
a9892f2571

+ 58 - 51
modules/mono/csharp_script.cpp

@@ -1618,29 +1618,18 @@ bool CSharpInstance::property_get_revert(const StringName &p_name, Variant &r_re
 }
 
 void CSharpInstance::get_method_list(List<MethodInfo> *p_list) const {
-#warning TODO
-#if 0
-	if (!script->is_valid() || !script->script_class) {
+	if (!script->is_valid() || !script->valid) {
 		return;
 	}
 
-	GD_MONO_SCOPE_THREAD_ATTACH;
-
-	// TODO: We're filtering out constructors but there may be other methods unsuitable for explicit calls.
-	GDMonoClass *top = script->script_class;
-
-	while (top && top != script->native) {
-		const Vector<GDMonoMethod *> &methods = top->get_all_methods();
-		for (int i = 0; i < methods.size(); ++i) {
-			MethodInfo minfo = methods[i]->get_method_info();
-			if (minfo.name != CACHED_STRING_NAME(dotctor)) {
-				p_list->push_back(minfo);
-			}
+	const CSharpScript *top = script.ptr();
+	while (top != nullptr) {
+		for (const CSharpScript::CSharpMethodInfo &E : top->methods) {
+			p_list->push_back(E.method_info);
 		}
 
-		top = top->get_parent_class();
+		top = top->base_script.ptr();
 	}
-#endif
 }
 
 bool CSharpInstance::has_method(const StringName &p_method) const {
@@ -2183,25 +2172,52 @@ void CSharpScript::reload_registered_script(Ref<CSharpScript> p_script) {
 void CSharpScript::update_script_class_info(Ref<CSharpScript> p_script) {
 	bool tool = false;
 
+	// TODO: Use GDNative godot_dictionary
+	Array methods_array;
+	methods_array.~Array();
 	Dictionary rpc_functions_dict;
-	// Destructor won't be called from C#, and I don't want to include the GDNative header
-	// only for this, so need to call the destructor manually before passing this to C#.
 	rpc_functions_dict.~Dictionary();
-
 	Dictionary signals_dict;
-	// Destructor won't be called from C#, and I don't want to include the GDNative header
-	// only for this, so need to call the destructor manually before passing this to C#.
 	signals_dict.~Dictionary();
 
 	Ref<CSharpScript> base_script;
 	GDMonoCache::managed_callbacks.ScriptManagerBridge_UpdateScriptClassInfo(
-			p_script.ptr(), &tool, &rpc_functions_dict, &signals_dict, &base_script);
+			p_script.ptr(), &tool, &methods_array, &rpc_functions_dict, &signals_dict, &base_script);
 
 	p_script->tool = tool;
 
 	p_script->rpc_config.clear();
 	p_script->rpc_config = rpc_functions_dict;
 
+	// Methods
+
+	p_script->methods.clear();
+
+	p_script->methods.resize(methods_array.size());
+	int push_index = 0;
+
+	for (int i = 0; i < methods_array.size(); i++) {
+		Dictionary method_info_dict = methods_array[i];
+
+		StringName name = method_info_dict["name"];
+
+		MethodInfo mi;
+		mi.name = name;
+
+		Array params = method_info_dict["params"];
+
+		for (int j = 0; j < params.size(); j++) {
+			Dictionary param = params[j];
+
+			Variant::Type param_type = (Variant::Type)(int)param["type"];
+			PropertyInfo arg_info = PropertyInfo(param_type, (String)param["name"]);
+			arg_info.usage = (uint32_t)param["usage"];
+			mi.arguments.push_back(arg_info);
+		}
+
+		p_script->methods.set(push_index++, CSharpMethodInfo{ name, mi });
+	}
+
 	// Event signals
 
 	// Performance is not critical here as this will be replaced with source generators.
@@ -2210,7 +2226,7 @@ void CSharpScript::update_script_class_info(Ref<CSharpScript> p_script) {
 
 	// Sigh... can't we just have capacity?
 	p_script->event_signals.resize(signals_dict.size());
-	int push_index = 0;
+	push_index = 0;
 
 	for (const Variant *s = signals_dict.next(nullptr); s != nullptr; s = signals_dict.next(s)) {
 		StringName name = *s;
@@ -2407,28 +2423,27 @@ void CSharpScript::get_script_method_list(List<MethodInfo> *p_list) const {
 		return;
 	}
 
-#warning TODO
-#if 0
-	// TODO: We're filtering out constructors but there may be other methods unsuitable for explicit calls.
-	GDMonoClass *top = script_class;
-
-	while (top && top != native) {
-		const Vector<GDMonoMethod *> &methods = top->get_all_methods();
-		for (int i = 0; i < methods.size(); ++i) {
-			MethodInfo minfo = methods[i]->get_method_info();
-			if (minfo.name != CACHED_STRING_NAME(dotctor)) {
-				p_list->push_back(methods[i]->get_method_info());
-			}
+	const CSharpScript *top = this;
+	while (top != nullptr) {
+		for (const CSharpMethodInfo &E : top->methods) {
+			p_list->push_back(E.method_info);
 		}
 
-		top = top->get_parent_class();
+		top = top->base_script.ptr();
 	}
-#endif
 }
 
 bool CSharpScript::has_method(const StringName &p_method) const {
-	// The equivalent of this will be implemented once we switch to the GDExtension system
-	ERR_PRINT_ONCE("CSharpScript::has_method is not implemented");
+	if (!valid) {
+		return false;
+	}
+
+	for (const CSharpMethodInfo &E : methods) {
+		if (E.name == p_method) {
+			return true;
+		}
+	}
+
 	return false;
 }
 
@@ -2437,19 +2452,11 @@ MethodInfo CSharpScript::get_method_info(const StringName &p_method) const {
 		return MethodInfo();
 	}
 
-#warning TODO
-#if 0
-	GDMonoClass *top = script_class;
-
-	while (top && top != native) {
-		GDMonoMethod *params = top->get_method_unknown_params(p_method);
-		if (params) {
-			return params->get_method_info();
+	for (const CSharpMethodInfo &E : methods) {
+		if (E.name == p_method) {
+			return E.method_info;
 		}
-
-		top = top->get_parent_class();
 	}
-#endif
 
 	return MethodInfo();
 }

+ 6 - 0
modules/mono/csharp_script.h

@@ -103,7 +103,13 @@ class CSharpScript : public Script {
 		MethodInfo method_info;
 	};
 
+	struct CSharpMethodInfo {
+		StringName name; // MethodInfo stores a string...
+		MethodInfo method_info;
+	};
+
 	Vector<EventSignalInfo> event_signals;
+	Vector<CSharpMethodInfo> methods;
 
 #ifdef TOOLS_ENABLED
 	List<PropertyInfo> exported_members_cache; // members_cache

+ 31 - 0
modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Methods.cs

@@ -0,0 +1,31 @@
+using System.Diagnostics.CodeAnalysis;
+
+namespace Godot.SourceGenerators.Sample;
+
+[SuppressMessage("ReSharper", "RedundantNameQualifier")]
+public partial class Methods : Godot.Object
+{
+    private void MethodWithOverload()
+    {
+    }
+
+    private void MethodWithOverload(int a)
+    {
+    }
+
+    private void MethodWithOverload(int a, int b)
+    {
+    }
+
+    // Should be ignored. The previous one is picked.
+    // ReSharper disable once UnusedMember.Local
+    private void MethodWithOverload(float a, float b)
+    {
+    }
+
+    // Generic methods should be ignored.
+    // ReSharper disable once UnusedMember.Local
+    private void GenericMethod<T>(T t)
+    {
+    }
+}

+ 12 - 0
modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalUtils.cs

@@ -400,6 +400,12 @@ namespace Godot.SourceGenerators
                     source.Append(VariantUtils, ".ConvertToQuaternion(", inputExpr, ")"),
                 MarshalType.Transform3D =>
                     source.Append(VariantUtils, ".ConvertToTransform3D(", inputExpr, ")"),
+                MarshalType.Vector4 =>
+                    source.Append(VariantUtils, ".ConvertToVector4(", inputExpr, ")"),
+                MarshalType.Vector4i =>
+                    source.Append(VariantUtils, ".ConvertToVector4i(", inputExpr, ")"),
+                MarshalType.Projection =>
+                    source.Append(VariantUtils, ".ConvertToProjection(", inputExpr, ")"),
                 MarshalType.AABB =>
                     source.Append(VariantUtils, ".ConvertToAABB(", inputExpr, ")"),
                 MarshalType.Color =>
@@ -535,6 +541,12 @@ namespace Godot.SourceGenerators
                     source.Append(VariantUtils, ".CreateFromQuaternion(", inputExpr, ")"),
                 MarshalType.Transform3D =>
                     source.Append(VariantUtils, ".CreateFromTransform3D(", inputExpr, ")"),
+                MarshalType.Vector4 =>
+                    source.Append(VariantUtils, ".CreateFromVector4(", inputExpr, ")"),
+                MarshalType.Vector4i =>
+                    source.Append(VariantUtils, ".CreateFromVector4i(", inputExpr, ")"),
+                MarshalType.Projection =>
+                    source.Append(VariantUtils, ".CreateFromProjection(", inputExpr, ")"),
                 MarshalType.AABB =>
                     source.Append(VariantUtils, ".CreateFromAABB(", inputExpr, ")"),
                 MarshalType.Color =>

+ 164 - 219
modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMemberInvokerGenerator.cs → modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs

@@ -8,8 +8,12 @@ using Microsoft.CodeAnalysis.Text;
 namespace Godot.SourceGenerators
 {
     [Generator]
-    public class ScriptMemberInvokerGenerator : ISourceGenerator
+    public class ScriptMethodsGenerator : ISourceGenerator
     {
+        public void Initialize(GeneratorInitializationContext context)
+        {
+        }
+
         public void Execute(GeneratorExecutionContext context)
         {
             if (context.AreGodotSourceGeneratorsDisabled())
@@ -54,6 +58,20 @@ namespace Godot.SourceGenerators
             }
         }
 
+        private class MethodOverloadEqualityComparer : IEqualityComparer<GodotMethodData>
+        {
+            public bool Equals(GodotMethodData x, GodotMethodData y)
+                => x.ParamTypes.Length == y.ParamTypes.Length && x.Method.Name == y.Method.Name;
+
+            public int GetHashCode(GodotMethodData obj)
+            {
+                unchecked
+                {
+                    return (obj.ParamTypes.Length.GetHashCode() * 397) ^ obj.Method.Name.GetHashCode();
+                }
+            }
+        }
+
         private static void VisitGodotScriptClass(
             GeneratorExecutionContext context,
             MarshalUtils.TypeCache typeCache,
@@ -69,7 +87,7 @@ namespace Godot.SourceGenerators
             bool isInnerClass = symbol.ContainingType != null;
 
             string uniqueHint = symbol.FullQualifiedName().SanitizeQualifiedNameForUniqueHint()
-                                + "_ScriptMemberInvoker_Generated";
+                                + "_ScriptMethods_Generated";
 
             var source = new StringBuilder();
 
@@ -111,54 +129,21 @@ namespace Godot.SourceGenerators
                 .Cast<IMethodSymbol>()
                 .Where(m => m.MethodKind == MethodKind.Ordinary);
 
-            var propertySymbols = members
-                .Where(s => !s.IsStatic && s.Kind == SymbolKind.Property)
-                .Cast<IPropertySymbol>();
-
-            var fieldSymbols = members
-                .Where(s => !s.IsStatic && s.Kind == SymbolKind.Field && !s.IsImplicitlyDeclared)
-                .Cast<IFieldSymbol>();
-
-            var godotClassMethods = methodSymbols.WhereHasGodotCompatibleSignature(typeCache).ToArray();
-            var godotClassProperties = propertySymbols.WhereIsGodotCompatibleType(typeCache).ToArray();
-            var godotClassFields = fieldSymbols.WhereIsGodotCompatibleType(typeCache).ToArray();
-
-            var signalDelegateSymbols = members
-                .Where(s => s.Kind == SymbolKind.NamedType)
-                .Cast<INamedTypeSymbol>()
-                .Where(namedTypeSymbol => namedTypeSymbol.TypeKind == TypeKind.Delegate)
-                .Where(s => s.GetAttributes()
-                    .Any(a => a.AttributeClass?.IsGodotSignalAttribute() ?? false));
-
-            List<GodotSignalDelegateData> godotSignalDelegates = new();
-
-            foreach (var signalDelegateSymbol in signalDelegateSymbols)
-            {
-                if (!signalDelegateSymbol.Name.EndsWith(ScriptSignalsGenerator.SignalDelegateSuffix))
-                    continue;
-
-                string signalName = signalDelegateSymbol.Name;
-                signalName = signalName.Substring(0,
-                    signalName.Length - ScriptSignalsGenerator.SignalDelegateSuffix.Length);
-
-                var invokeMethodData = signalDelegateSymbol
-                    .DelegateInvokeMethod?.HasGodotCompatibleSignature(typeCache);
-
-                if (invokeMethodData == null)
-                    continue;
-
-                godotSignalDelegates.Add(new(signalName, signalDelegateSymbol, invokeMethodData.Value));
-            }
+            var godotClassMethods = methodSymbols.WhereHasGodotCompatibleSignature(typeCache)
+                .Distinct(new MethodOverloadEqualityComparer())
+                .ToArray();
 
             source.Append("    private partial class GodotInternal {\n");
 
             // Generate cached StringNames for methods and properties, for fast lookup
 
-            // TODO: Move the generation of these cached StringNames to its own generator
+            var distinctMethodNames = godotClassMethods
+                .Select(m => m.Method.Name)
+                .Distinct()
+                .ToArray();
 
-            foreach (var method in godotClassMethods)
+            foreach (string methodName in distinctMethodNames)
             {
-                string methodName = method.Method.Name;
                 source.Append("        public static readonly StringName MethodName_");
                 source.Append(methodName);
                 source.Append(" = \"");
@@ -168,6 +153,36 @@ namespace Godot.SourceGenerators
 
             source.Append("    }\n"); // class GodotInternal
 
+            // Generate GetGodotMethodList
+
+            if (godotClassMethods.Length > 0)
+            {
+                source.Append("#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword\n");
+
+                const string listType = "System.Collections.Generic.List<global::Godot.Bridge.MethodInfo>";
+
+                source.Append("    internal new static ")
+                    .Append(listType)
+                    .Append(" GetGodotMethodList()\n    {\n");
+
+                source.Append("        var methods = new ")
+                    .Append(listType)
+                    .Append("(")
+                    .Append(godotClassMethods.Length)
+                    .Append(");\n");
+
+                foreach (var method in godotClassMethods)
+                {
+                    var methodInfo = DetermineMethodInfo(method);
+                    AppendMethodInfo(source, methodInfo);
+                }
+
+                source.Append("        return methods;\n");
+                source.Append("    }\n");
+
+                source.Append("#pragma warning restore CS0109\n");
+            }
+
             // Generate InvokeGodotClassMethod
 
             if (godotClassMethods.Length > 0)
@@ -187,14 +202,14 @@ namespace Godot.SourceGenerators
 
             // Generate HasGodotClassMethod
 
-            if (godotClassMethods.Length > 0)
+            if (distinctMethodNames.Length > 0)
             {
                 source.Append("    protected override bool HasGodotClassMethod(in godot_string_name method)\n    {\n");
 
                 bool isFirstEntry = true;
-                foreach (var method in godotClassMethods)
+                foreach (string methodName in distinctMethodNames)
                 {
-                    GenerateHasMethodEntry(method, source, isFirstEntry);
+                    GenerateHasMethodEntry(methodName, source, isFirstEntry);
                     isFirstEntry = false;
                 }
 
@@ -203,111 +218,141 @@ namespace Godot.SourceGenerators
                 source.Append("    }\n");
             }
 
-            // Generate RaiseGodotClassSignalCallbacks
+            source.Append("}\n"); // partial class
 
-            if (godotSignalDelegates.Count > 0)
+            if (isInnerClass)
             {
-                source.Append(
-                    "    protected override void RaiseGodotClassSignalCallbacks(in godot_string_name signal, ");
-                source.Append("NativeVariantPtrArgs args, int argCount)\n    {\n");
+                var containingType = symbol.ContainingType;
 
-                foreach (var signal in godotSignalDelegates)
+                while (containingType != null)
                 {
-                    GenerateSignalEventInvoker(signal, source);
-                }
-
-                source.Append("        base.RaiseGodotClassSignalCallbacks(signal, args, argCount);\n");
+                    source.Append("}\n"); // outer class
 
-                source.Append("    }\n");
+                    containingType = containingType.ContainingType;
+                }
             }
 
-            // Generate Set/GetGodotClassPropertyValue
-
-            if (godotClassProperties.Length > 0 || godotClassFields.Length > 0)
+            if (hasNamespace)
             {
-                bool isFirstEntry;
-
-                // Setters
-
-                bool allPropertiesAreReadOnly = godotClassFields.All(fi => fi.FieldSymbol.IsReadOnly) &&
-                                                godotClassProperties.All(pi => pi.PropertySymbol.IsReadOnly);
+                source.Append("\n}\n");
+            }
 
-                if (!allPropertiesAreReadOnly)
-                {
-                    source.Append("    protected override bool SetGodotClassPropertyValue(in godot_string_name name, ");
-                    source.Append("in godot_variant value)\n    {\n");
+            context.AddSource(uniqueHint, SourceText.From(source.ToString(), Encoding.UTF8));
+        }
 
-                    isFirstEntry = true;
-                    foreach (var property in godotClassProperties)
-                    {
-                        if (property.PropertySymbol.IsReadOnly)
-                            continue;
+        private static void AppendMethodInfo(StringBuilder source, MethodInfo methodInfo)
+        {
+            source.Append("        methods.Add(new(name: GodotInternal.MethodName_")
+                .Append(methodInfo.Name)
+                .Append(", returnVal: ");
 
-                        GeneratePropertySetter(property.PropertySymbol.Name,
-                            property.PropertySymbol.Type, property.Type, source, isFirstEntry);
-                        isFirstEntry = false;
-                    }
+            AppendPropertyInfo(source, methodInfo.ReturnVal);
 
-                    foreach (var field in godotClassFields)
-                    {
-                        if (field.FieldSymbol.IsReadOnly)
-                            continue;
+            source.Append(", flags: (Godot.MethodFlags)")
+                .Append((int)methodInfo.Flags)
+                .Append(", arguments: ");
 
-                        GeneratePropertySetter(field.FieldSymbol.Name,
-                            field.FieldSymbol.Type, field.Type, source, isFirstEntry);
-                        isFirstEntry = false;
-                    }
+            if (methodInfo.Arguments is { Count: > 0 })
+            {
+                source.Append("new() { ");
 
-                    source.Append("        return base.SetGodotClassPropertyValue(name, value);\n");
+                foreach (var param in methodInfo.Arguments)
+                {
+                    AppendPropertyInfo(source, param);
 
-                    source.Append("    }\n");
+                    // C# allows colon after the last element
+                    source.Append(", ");
                 }
 
-                // Getters
-
-                source.Append("    protected override bool GetGodotClassPropertyValue(in godot_string_name name, ");
-                source.Append("out godot_variant value)\n    {\n");
+                source.Append(" }");
+            }
+            else
+            {
+                source.Append("null");
+            }
 
-                isFirstEntry = true;
-                foreach (var property in godotClassProperties)
-                {
-                    GeneratePropertyGetter(property.PropertySymbol.Name,
-                        property.Type, source, isFirstEntry);
-                    isFirstEntry = false;
-                }
+            source.Append(", defaultArguments: null));\n");
+        }
 
-                foreach (var field in godotClassFields)
-                {
-                    GeneratePropertyGetter(field.FieldSymbol.Name,
-                        field.Type, source, isFirstEntry);
-                    isFirstEntry = false;
-                }
+        private static void AppendPropertyInfo(StringBuilder source, PropertyInfo propertyInfo)
+        {
+            source.Append("new(type: (Godot.Variant.Type)")
+                .Append((int)propertyInfo.Type)
+                .Append(", name: \"")
+                .Append(propertyInfo.Name)
+                .Append("\", hint: (Godot.PropertyHint)")
+                .Append((int)propertyInfo.Hint)
+                .Append(", hintString: \"")
+                .Append(propertyInfo.HintString)
+                .Append("\", usage: (Godot.PropertyUsageFlags)")
+                .Append((int)propertyInfo.Usage)
+                .Append(", exported: ")
+                .Append(propertyInfo.Exported ? "true" : "false")
+                .Append(")");
+        }
 
-                source.Append("        return base.GetGodotClassPropertyValue(name, out value);\n");
+        private static MethodInfo DetermineMethodInfo(GodotMethodData method)
+        {
+            PropertyInfo returnVal;
 
-                source.Append("    }\n");
+            if (method.RetType != null)
+            {
+                returnVal = DeterminePropertyInfo(method.RetType.Value, name: string.Empty);
+            }
+            else
+            {
+                returnVal = new PropertyInfo(VariantType.Nil, string.Empty, PropertyHint.None,
+                    hintString: null, PropertyUsageFlags.Default, exported: false);
             }
 
-            source.Append("}\n"); // partial class
+            int paramCount = method.ParamTypes.Length;
 
-            if (isInnerClass)
+            List<PropertyInfo>? arguments;
+
+            if (paramCount > 0)
             {
-                var containingType = symbol.ContainingType;
+                arguments = new(capacity: paramCount);
 
-                while (containingType != null)
+                for (int i = 0; i < paramCount; i++)
                 {
-                    source.Append("}\n"); // outer class
-
-                    containingType = containingType.ContainingType;
+                    arguments.Add(DeterminePropertyInfo(method.ParamTypes[i],
+                        name: method.Method.Parameters[i].Name));
                 }
             }
-
-            if (hasNamespace)
+            else
             {
-                source.Append("\n}\n");
+                arguments = null;
             }
 
-            context.AddSource(uniqueHint, SourceText.From(source.ToString(), Encoding.UTF8));
+            return new MethodInfo(method.Method.Name, returnVal, MethodFlags.Default, arguments,
+                defaultArguments: null);
+        }
+
+        private static PropertyInfo DeterminePropertyInfo(MarshalType marshalType, string name)
+        {
+            var memberVariantType = MarshalUtils.ConvertMarshalTypeToVariantType(marshalType)!.Value;
+
+            var propUsage = PropertyUsageFlags.Default;
+
+            if (memberVariantType == VariantType.Nil)
+                propUsage |= PropertyUsageFlags.NilIsVariant;
+
+            return new PropertyInfo(memberVariantType, name,
+                PropertyHint.None, string.Empty, propUsage, exported: false);
+        }
+
+        private static void GenerateHasMethodEntry(
+            string methodName,
+            StringBuilder source,
+            bool isFirstEntry
+        )
+        {
+            source.Append("        ");
+            if (!isFirstEntry)
+                source.Append("else ");
+            source.Append("if (method == GodotInternal.MethodName_");
+            source.Append(methodName);
+            source.Append(") {\n           return true;\n        }\n");
         }
 
         private static void GenerateMethodInvoker(
@@ -359,105 +404,5 @@ namespace Godot.SourceGenerators
 
             source.Append("        }\n");
         }
-
-        private static void GenerateSignalEventInvoker(
-            GodotSignalDelegateData signal,
-            StringBuilder source
-        )
-        {
-            string signalName = signal.Name;
-            var invokeMethodData = signal.InvokeMethodData;
-
-            source.Append("        if (signal == GodotInternal.SignalName_");
-            source.Append(signalName);
-            source.Append(" && argCount == ");
-            source.Append(invokeMethodData.ParamTypes.Length);
-            source.Append(") {\n");
-            source.Append("            backing_");
-            source.Append(signalName);
-            source.Append("?.Invoke(");
-
-            for (int i = 0; i < invokeMethodData.ParamTypes.Length; i++)
-            {
-                if (i != 0)
-                    source.Append(", ");
-
-                source.AppendVariantToManagedExpr(string.Concat("args[", i.ToString(), "]"),
-                    invokeMethodData.ParamTypeSymbols[i], invokeMethodData.ParamTypes[i]);
-            }
-
-            source.Append(");\n");
-
-            source.Append("            return;\n");
-
-            source.Append("        }\n");
-        }
-
-        private static void GeneratePropertySetter(
-            string propertyMemberName,
-            ITypeSymbol propertyTypeSymbol,
-            MarshalType propertyMarshalType,
-            StringBuilder source,
-            bool isFirstEntry
-        )
-        {
-            source.Append("        ");
-
-            if (!isFirstEntry)
-                source.Append("else ");
-
-            source.Append("if (name == GodotInternal.PropName_")
-                .Append(propertyMemberName)
-                .Append(") {\n")
-                .Append("            ")
-                .Append(propertyMemberName)
-                .Append(" = ")
-                .AppendVariantToManagedExpr("value", propertyTypeSymbol, propertyMarshalType)
-                .Append(";\n")
-                .Append("            return true;\n")
-                .Append("        }\n");
-        }
-
-        private static void GeneratePropertyGetter(
-            string propertyMemberName,
-            MarshalType propertyMarshalType,
-            StringBuilder source,
-            bool isFirstEntry
-        )
-        {
-            source.Append("        ");
-
-            if (!isFirstEntry)
-                source.Append("else ");
-
-            source.Append("if (name == GodotInternal.PropName_")
-                .Append(propertyMemberName)
-                .Append(") {\n")
-                .Append("            value = ")
-                .AppendManagedToVariantExpr(propertyMemberName, propertyMarshalType)
-                .Append(";\n")
-                .Append("            return true;\n")
-                .Append("        }\n");
-        }
-
-        private static void GenerateHasMethodEntry(
-            GodotMethodData method,
-            StringBuilder source,
-            bool isFirstEntry
-        )
-        {
-            string methodName = method.Method.Name;
-
-            source.Append("        ");
-            if (!isFirstEntry)
-                source.Append("else ");
-            source.Append("if (method == GodotInternal.MethodName_");
-            source.Append(methodName);
-            source.Append(") {\n           return true;\n        }\n");
-        }
-
-        public void Initialize(GeneratorInitializationContext context)
-        {
-        }
     }
 }

+ 111 - 2
modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs

@@ -146,10 +146,72 @@ namespace Godot.SourceGenerators
 
             source.Append("    }\n"); // class GodotInternal
 
-            // Generate GetGodotPropertyList
-
             if (godotClassProperties.Length > 0 || godotClassFields.Length > 0)
             {
+                bool isFirstEntry;
+
+                // Generate SetGodotClassPropertyValue
+
+                bool allPropertiesAreReadOnly = godotClassFields.All(fi => fi.FieldSymbol.IsReadOnly) &&
+                                                godotClassProperties.All(pi => pi.PropertySymbol.IsReadOnly);
+
+                if (!allPropertiesAreReadOnly)
+                {
+                    source.Append("    protected override bool SetGodotClassPropertyValue(in godot_string_name name, ");
+                    source.Append("in godot_variant value)\n    {\n");
+
+                    isFirstEntry = true;
+                    foreach (var property in godotClassProperties)
+                    {
+                        if (property.PropertySymbol.IsReadOnly)
+                            continue;
+
+                        GeneratePropertySetter(property.PropertySymbol.Name,
+                            property.PropertySymbol.Type, property.Type, source, isFirstEntry);
+                        isFirstEntry = false;
+                    }
+
+                    foreach (var field in godotClassFields)
+                    {
+                        if (field.FieldSymbol.IsReadOnly)
+                            continue;
+
+                        GeneratePropertySetter(field.FieldSymbol.Name,
+                            field.FieldSymbol.Type, field.Type, source, isFirstEntry);
+                        isFirstEntry = false;
+                    }
+
+                    source.Append("        return base.SetGodotClassPropertyValue(name, value);\n");
+
+                    source.Append("    }\n");
+                }
+
+                // Generate GetGodotClassPropertyValue
+
+                source.Append("    protected override bool GetGodotClassPropertyValue(in godot_string_name name, ");
+                source.Append("out godot_variant value)\n    {\n");
+
+                isFirstEntry = true;
+                foreach (var property in godotClassProperties)
+                {
+                    GeneratePropertyGetter(property.PropertySymbol.Name,
+                        property.Type, source, isFirstEntry);
+                    isFirstEntry = false;
+                }
+
+                foreach (var field in godotClassFields)
+                {
+                    GeneratePropertyGetter(field.FieldSymbol.Name,
+                        field.Type, source, isFirstEntry);
+                    isFirstEntry = false;
+                }
+
+                source.Append("        return base.GetGodotClassPropertyValue(name, out value);\n");
+
+                source.Append("    }\n");
+
+                // Generate GetGodotPropertyList
+
                 source.Append("#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword\n");
 
                 string dictionaryType = "System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo>";
@@ -212,6 +274,53 @@ namespace Godot.SourceGenerators
             context.AddSource(uniqueHint, SourceText.From(source.ToString(), Encoding.UTF8));
         }
 
+        private static void GeneratePropertySetter(
+            string propertyMemberName,
+            ITypeSymbol propertyTypeSymbol,
+            MarshalType propertyMarshalType,
+            StringBuilder source,
+            bool isFirstEntry
+        )
+        {
+            source.Append("        ");
+
+            if (!isFirstEntry)
+                source.Append("else ");
+
+            source.Append("if (name == GodotInternal.PropName_")
+                .Append(propertyMemberName)
+                .Append(") {\n")
+                .Append("            ")
+                .Append(propertyMemberName)
+                .Append(" = ")
+                .AppendVariantToManagedExpr("value", propertyTypeSymbol, propertyMarshalType)
+                .Append(";\n")
+                .Append("            return true;\n")
+                .Append("        }\n");
+        }
+
+        private static void GeneratePropertyGetter(
+            string propertyMemberName,
+            MarshalType propertyMarshalType,
+            StringBuilder source,
+            bool isFirstEntry
+        )
+        {
+            source.Append("        ");
+
+            if (!isFirstEntry)
+                source.Append("else ");
+
+            source.Append("if (name == GodotInternal.PropName_")
+                .Append(propertyMemberName)
+                .Append(") {\n")
+                .Append("            value = ")
+                .AppendManagedToVariantExpr(propertyMemberName, propertyMarshalType)
+                .Append(";\n")
+                .Append("            return true;\n")
+                .Append("        }\n");
+        }
+
         private static void AppendPropertyInfo(StringBuilder source, PropertyInfo propertyInfo)
         {
             source.Append("        properties.Add(new(type: (Godot.Variant.Type)")

+ 51 - 0
modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs

@@ -232,6 +232,24 @@ namespace Godot.SourceGenerators
                     .Append("}\n");
             }
 
+            // Generate RaiseGodotClassSignalCallbacks
+
+            if (godotSignalDelegates.Count > 0)
+            {
+                source.Append(
+                    "    protected override void RaiseGodotClassSignalCallbacks(in godot_string_name signal, ");
+                source.Append("NativeVariantPtrArgs args, int argCount)\n    {\n");
+
+                foreach (var signal in godotSignalDelegates)
+                {
+                    GenerateSignalEventInvoker(signal, source);
+                }
+
+                source.Append("        base.RaiseGodotClassSignalCallbacks(signal, args, argCount);\n");
+
+                source.Append("    }\n");
+            }
+
             source.Append("}\n"); // partial class
 
             if (isInnerClass)
@@ -356,5 +374,38 @@ namespace Godot.SourceGenerators
             return new PropertyInfo(memberVariantType, name,
                 PropertyHint.None, string.Empty, propUsage, exported: false);
         }
+
+        private static void GenerateSignalEventInvoker(
+            GodotSignalDelegateData signal,
+            StringBuilder source
+        )
+        {
+            string signalName = signal.Name;
+            var invokeMethodData = signal.InvokeMethodData;
+
+            source.Append("        if (signal == GodotInternal.SignalName_");
+            source.Append(signalName);
+            source.Append(" && argCount == ");
+            source.Append(invokeMethodData.ParamTypes.Length);
+            source.Append(") {\n");
+            source.Append("            backing_");
+            source.Append(signalName);
+            source.Append("?.Invoke(");
+
+            for (int i = 0; i < invokeMethodData.ParamTypes.Length; i++)
+            {
+                if (i != 0)
+                    source.Append(", ");
+
+                source.AppendVariantToManagedExpr(string.Concat("args[", i.ToString(), "]"),
+                    invokeMethodData.ParamTypeSymbols[i], invokeMethodData.ParamTypes[i]);
+            }
+
+            source.Append(");\n");
+
+            source.Append("            return;\n");
+
+            source.Append("        }\n");
+        }
     }
 }

+ 1 - 1
modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs

@@ -24,7 +24,7 @@ namespace Godot.Bridge
         public delegate* unmanaged<godot_string*, godot_ref*, void> ScriptManagerBridge_GetOrCreateScriptBridgeForPath;
         public delegate* unmanaged<IntPtr, void> ScriptManagerBridge_RemoveScriptBridge;
         public delegate* unmanaged<IntPtr, godot_bool> ScriptManagerBridge_TryReloadRegisteredScriptWithClass;
-        public delegate* unmanaged<IntPtr, godot_bool*, godot_dictionary*, godot_dictionary*, godot_ref*, void> ScriptManagerBridge_UpdateScriptClassInfo;
+        public delegate* unmanaged<IntPtr, godot_bool*, godot_array*, godot_dictionary*, godot_dictionary*, godot_ref*, void> ScriptManagerBridge_UpdateScriptClassInfo;
         public delegate* unmanaged<IntPtr, IntPtr*, godot_bool, godot_bool> ScriptManagerBridge_SwapGCHandleForType;
         public delegate* unmanaged<IntPtr, delegate* unmanaged<IntPtr, godot_string*, void*, int, void>, void> ScriptManagerBridge_GetPropertyInfoList;
         public delegate* unmanaged<IntPtr, delegate* unmanaged<IntPtr, void*, int, void>, void> ScriptManagerBridge_GetPropertyDefaultValues;

+ 67 - 8
modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs

@@ -557,7 +557,8 @@ namespace Godot.Bridge
 
         [UnmanagedCallersOnly]
         internal static unsafe void UpdateScriptClassInfo(IntPtr scriptPtr, godot_bool* outTool,
-            godot_dictionary* outRpcFunctionsDest, godot_dictionary* outEventSignalsDest, godot_ref* outBaseScript)
+            godot_array* outMethodsDest, godot_dictionary* outRpcFunctionsDest,
+            godot_dictionary* outEventSignalsDest, godot_ref* outBaseScript)
         {
             try
             {
@@ -578,13 +579,59 @@ namespace Godot.Bridge
                 if (!(*outTool).ToBool() && scriptType.Assembly.GetName().Name == "GodotTools")
                     *outTool = godot_bool.True;
 
-                // RPC functions
+                // Methods
 
-                Collections.Dictionary<string, Collections.Dictionary> rpcFunctions = new();
+                // Performance is not critical here as this will be replaced with source generators.
+                using var methods = new Collections.Array();
 
                 Type? top = scriptType;
                 Type native = Object.InternalGetClassNativeBase(top);
 
+                while (top != null && top != native)
+                {
+                    var methodList = GetMethodListForType(top);
+
+                    if (methodList != null)
+                    {
+                        foreach (var method in methodList)
+                        {
+                            var methodInfo = new Collections.Dictionary();
+
+                            methodInfo.Add("name", method.Name);
+
+                            var methodParams = new Collections.Array();
+
+                            if (method.Arguments != null)
+                            {
+                                foreach (var param in method.Arguments)
+                                {
+                                    methodParams.Add(new Collections.Dictionary()
+                                    {
+                                        { "name", param.Name },
+                                        { "type", param.Type },
+                                        { "usage", param.Usage }
+                                    });
+                                }
+                            }
+
+                            methodInfo.Add("params", methodParams);
+
+                            methods.Add(methodInfo);
+                        }
+                    }
+
+                    top = top.BaseType;
+                }
+
+                *outMethodsDest = NativeFuncs.godotsharp_array_new_copy(
+                    (godot_array)methods.NativeValue);
+
+                // RPC functions
+
+                Collections.Dictionary<string, Collections.Dictionary> rpcFunctions = new();
+
+                top = scriptType;
+
                 while (top != null && top != native)
                 {
                     foreach (var method in top.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance |
@@ -617,9 +664,8 @@ namespace Godot.Bridge
                     top = top.BaseType;
                 }
 
-                *outRpcFunctionsDest =
-                    NativeFuncs.godotsharp_dictionary_new_copy(
-                        (godot_dictionary)((Collections.Dictionary)rpcFunctions).NativeValue);
+                *outRpcFunctionsDest = NativeFuncs.godotsharp_dictionary_new_copy(
+                    (godot_dictionary)((Collections.Dictionary)rpcFunctions).NativeValue);
 
                 // Event signals
 
@@ -663,8 +709,8 @@ namespace Godot.Bridge
                     top = top.BaseType;
                 }
 
-                *outEventSignalsDest =
-                    NativeFuncs.godotsharp_dictionary_new_copy((godot_dictionary)signals.NativeValue);
+                *outEventSignalsDest = NativeFuncs.godotsharp_dictionary_new_copy(
+                    (godot_dictionary)signals.NativeValue);
 
                 // Base script
 
@@ -701,6 +747,19 @@ namespace Godot.Bridge
             return (List<MethodInfo>?)getGodotSignalListMethod.Invoke(null, null);
         }
 
+        private static List<MethodInfo>? GetMethodListForType(Type type)
+        {
+            var getGodotMethodListMethod = type.GetMethod(
+                "GetGodotMethodList",
+                BindingFlags.DeclaredOnly | BindingFlags.Static |
+                BindingFlags.NonPublic | BindingFlags.Public);
+
+            if (getGodotMethodListMethod == null)
+                return null;
+
+            return (List<MethodInfo>?)getGodotMethodListMethod.Invoke(null, null);
+        }
+
         // ReSharper disable once InconsistentNaming
         [SuppressMessage("ReSharper", "NotAccessedField.Local")]
         [StructLayout(LayoutKind.Sequential)]

+ 1 - 1
modules/mono/mono_gd/gd_mono_cache.h

@@ -87,7 +87,7 @@ struct ManagedCallbacks {
 	using FuncScriptManagerBridge_GetOrCreateScriptBridgeForPath = void(GD_CLR_STDCALL *)(const String *, Ref<CSharpScript> *);
 	using FuncScriptManagerBridge_RemoveScriptBridge = void(GD_CLR_STDCALL *)(const CSharpScript *);
 	using FuncScriptManagerBridge_TryReloadRegisteredScriptWithClass = bool(GD_CLR_STDCALL *)(const CSharpScript *);
-	using FuncScriptManagerBridge_UpdateScriptClassInfo = void(GD_CLR_STDCALL *)(const CSharpScript *, bool *, Dictionary *, Dictionary *, Ref<CSharpScript> *);
+	using FuncScriptManagerBridge_UpdateScriptClassInfo = void(GD_CLR_STDCALL *)(const CSharpScript *, bool *, Array *, Dictionary *, Dictionary *, Ref<CSharpScript> *);
 	using FuncScriptManagerBridge_SwapGCHandleForType = bool(GD_CLR_STDCALL *)(GCHandleIntPtr, GCHandleIntPtr *, bool);
 	using FuncScriptManagerBridge_GetPropertyInfoList = void(GD_CLR_STDCALL *)(CSharpScript *, Callback_ScriptManagerBridge_GetPropertyInfoList_Add);
 	using FuncScriptManagerBridge_GetPropertyDefaultValues = void(GD_CLR_STDCALL *)(CSharpScript *, Callback_ScriptManagerBridge_GetPropertyDefaultValues_Add);