Browse Source

Merge pull request #67987 from neikeq/dotnet-more-static-marshaling

C#: Reflection-less delegate callables and nested generic Godot collections
Ignacio Roldán Etcheverry 2 years ago
parent
commit
256c0079b0
21 changed files with 806 additions and 117 deletions
  1. 3 3
      modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs
  2. 4 3
      modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs
  3. 74 15
      modules/mono/editor/bindings_generator.cpp
  4. 22 10
      modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs
  5. 2 3
      modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/CSharpInstanceBridge.cs
  6. 1 1
      modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs
  7. 1 1
      modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs
  8. 64 10
      modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs
  9. 480 0
      modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.generics.cs
  10. 9 19
      modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs
  11. 40 27
      modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs
  12. 25 13
      modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs
  13. 3 3
      modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs
  14. 15 1
      modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeVariantPtrArgs.cs
  15. 45 0
      modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantConversionCallbacks.cs
  16. 1 1
      modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs
  17. 1 0
      modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj
  18. 9 3
      modules/mono/glue/runtime_interop.cpp
  19. 3 2
      modules/mono/managed_callable.cpp
  20. 3 1
      modules/mono/managed_callable.h
  21. 1 1
      modules/mono/mono_gd/gd_mono_cache.h

+ 3 - 3
modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs

@@ -188,14 +188,14 @@ namespace Godot.SourceGenerators
             if (godotClassMethods.Length > 0)
             {
                 source.Append("    protected override bool InvokeGodotClassMethod(in godot_string_name method, ");
-                source.Append("NativeVariantPtrArgs args, int argCount, out godot_variant ret)\n    {\n");
+                source.Append("NativeVariantPtrArgs args, out godot_variant ret)\n    {\n");
 
                 foreach (var method in godotClassMethods)
                 {
                     GenerateMethodInvoker(method, source);
                 }
 
-                source.Append("        return base.InvokeGodotClassMethod(method, args, argCount, out ret);\n");
+                source.Append("        return base.InvokeGodotClassMethod(method, args, out ret);\n");
 
                 source.Append("    }\n");
             }
@@ -364,7 +364,7 @@ namespace Godot.SourceGenerators
 
             source.Append("        if (method == MethodName.");
             source.Append(methodName);
-            source.Append(" && argCount == ");
+            source.Append(" && args.Count == ");
             source.Append(method.ParamTypes.Length);
             source.Append(") {\n");
 

+ 4 - 3
modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs

@@ -167,6 +167,7 @@ namespace Godot.SourceGenerators
                             Common.ReportSignalDelegateSignatureMustReturnVoid(context, signalDelegateSymbol);
                         }
                     }
+
                     continue;
                 }
 
@@ -257,14 +258,14 @@ namespace Godot.SourceGenerators
             {
                 source.Append(
                     "    protected override void RaiseGodotClassSignalCallbacks(in godot_string_name signal, ");
-                source.Append("NativeVariantPtrArgs args, int argCount)\n    {\n");
+                source.Append("NativeVariantPtrArgs args)\n    {\n");
 
                 foreach (var signal in godotSignalDelegates)
                 {
                     GenerateSignalEventInvoker(signal, source);
                 }
 
-                source.Append("        base.RaiseGodotClassSignalCallbacks(signal, args, argCount);\n");
+                source.Append("        base.RaiseGodotClassSignalCallbacks(signal, args);\n");
 
                 source.Append("    }\n");
             }
@@ -404,7 +405,7 @@ namespace Godot.SourceGenerators
 
             source.Append("        if (signal == SignalName.");
             source.Append(signalName);
-            source.Append(" && argCount == ");
+            source.Append(" && args.Count == ");
             source.Append(invokeMethodData.ParamTypes.Length);
             source.Append(") {\n");
             source.Append("            backing_");

+ 74 - 15
modules/mono/editor/bindings_generator.cpp

@@ -1614,7 +1614,7 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str
 
 		output << MEMBER_BEGIN "protected internal " << (is_derived_type ? "override" : "virtual")
 			   << " bool " CS_METHOD_INVOKE_GODOT_CLASS_METHOD "(in godot_string_name method, "
-			   << "NativeVariantPtrArgs args, int argCount, out godot_variant ret)\n"
+			   << "NativeVariantPtrArgs args, out godot_variant ret)\n"
 			   << INDENT1 "{\n";
 
 		for (const MethodInterface &imethod : itype.methods) {
@@ -1630,7 +1630,7 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str
 			// We check both native names (snake_case) and proxy names (PascalCase)
 			output << INDENT2 "if ((method == " << CS_STATIC_FIELD_METHOD_PROXY_NAME_PREFIX << imethod.name
 				   << " || method == MethodName." << imethod.proxy_name
-				   << ") && argCount == " << itos(imethod.arguments.size())
+				   << ") && args.Count == " << itos(imethod.arguments.size())
 				   << " && " << CS_METHOD_HAS_GODOT_CLASS_METHOD << "((godot_string_name)"
 				   << CS_STATIC_FIELD_METHOD_PROXY_NAME_PREFIX << imethod.name << ".NativeValue))\n"
 				   << INDENT2 "{\n";
@@ -1682,7 +1682,7 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str
 		}
 
 		if (is_derived_type) {
-			output << INDENT2 "return base." CS_METHOD_INVOKE_GODOT_CLASS_METHOD "(method, args, argCount, out ret);\n";
+			output << INDENT2 "return base." CS_METHOD_INVOKE_GODOT_CLASS_METHOD "(method, args, out ret);\n";
 		} else {
 			output << INDENT2 "ret = default;\n"
 				   << INDENT2 "return false;\n";
@@ -2200,6 +2200,11 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf
 
 Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterface &p_itype, const BindingsGenerator::SignalInterface &p_isignal, StringBuilder &p_output) {
 	String arguments_sig;
+	String delegate_type_params;
+
+	if (!p_isignal.arguments.is_empty()) {
+		delegate_type_params += "<";
+	}
 
 	// Retrieve information from the arguments
 	const ArgumentInterface &first = p_isignal.arguments.front()->get();
@@ -2220,11 +2225,18 @@ Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterf
 
 		if (&iarg != &first) {
 			arguments_sig += ", ";
+			delegate_type_params += ", ";
 		}
 
 		arguments_sig += arg_type->cs_type;
 		arguments_sig += " ";
 		arguments_sig += iarg.name;
+
+		delegate_type_params += arg_type->cs_type;
+	}
+
+	if (!p_isignal.arguments.is_empty()) {
+		delegate_type_params += ">";
 	}
 
 	// Generate signal
@@ -2248,15 +2260,46 @@ Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterf
 			p_output.append("\")]");
 		}
 
-		String delegate_name = p_isignal.proxy_name;
-		delegate_name += "EventHandler"; // Delegate name is [SignalName]EventHandler
+		bool is_parameterless = p_isignal.arguments.size() == 0;
 
-		// Generate delegate
-		p_output.append(MEMBER_BEGIN "public delegate void ");
-		p_output.append(delegate_name);
-		p_output.append("(");
-		p_output.append(arguments_sig);
-		p_output.append(");\n");
+		// Delegate name is [SignalName]EventHandler
+		String delegate_name = is_parameterless ? "Action" : p_isignal.proxy_name + "EventHandler";
+
+		if (!is_parameterless) {
+			// Generate delegate
+			p_output.append(MEMBER_BEGIN "public delegate void ");
+			p_output.append(delegate_name);
+			p_output.append("(");
+			p_output.append(arguments_sig);
+			p_output.append(");\n");
+
+			// Generate Callable trampoline for the delegate
+			p_output << MEMBER_BEGIN "private static unsafe void " << p_isignal.proxy_name << "Trampoline"
+					 << "(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)\n"
+					 << INDENT1 "{\n"
+					 << INDENT2 "Callable.ThrowIfArgCountMismatch(args, " << itos(p_isignal.arguments.size()) << ");\n"
+					 << INDENT2 "((" << delegate_name << ")delegateObj)(";
+
+			int idx = 0;
+			for (const ArgumentInterface &iarg : p_isignal.arguments) {
+				const TypeInterface *arg_type = _get_type_or_null(iarg.type);
+				ERR_FAIL_NULL_V(arg_type, ERR_BUG); // Argument type not found
+
+				if (idx != 0) {
+					p_output << ",";
+				}
+
+				// TODO: We don't need to use VariantConversionCallbacks. We have the type information so we can use [cs_variant_to_managed] and [cs_managed_to_variant].
+				p_output << "\n" INDENT3 "VariantConversionCallbacks.GetToManagedCallback<"
+						 << arg_type->cs_type << ">()(args[" << itos(idx) << "])";
+
+				idx++;
+			}
+
+			p_output << ");\n"
+					 << INDENT2 "ret = default;\n"
+					 << INDENT1 "}\n";
+		}
 
 		if (p_isignal.method_doc && p_isignal.method_doc->description.size()) {
 			String xml_summary = bbcode_to_xml(fix_doc_description(p_isignal.method_doc->description), &p_itype);
@@ -2292,6 +2335,11 @@ Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterf
 			p_output.append("static ");
 		}
 
+		if (!is_parameterless) {
+			// `unsafe` is needed for taking the trampoline's function pointer
+			p_output << "unsafe ";
+		}
+
 		p_output.append("event ");
 		p_output.append(delegate_name);
 		p_output.append(" ");
@@ -2304,8 +2352,13 @@ Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterf
 			p_output.append("add => Connect(SignalName.");
 		}
 
-		p_output.append(p_isignal.proxy_name);
-		p_output.append(", new Callable(value));\n");
+		if (is_parameterless) {
+			// Delegate type is Action. No need for custom trampoline.
+			p_output << p_isignal.proxy_name << ", Callable.From(value));\n";
+		} else {
+			p_output << p_isignal.proxy_name
+					 << ", Callable.CreateWithUnsafeTrampoline(value, &" << p_isignal.proxy_name << "Trampoline));\n";
+		}
 
 		if (p_itype.is_singleton) {
 			p_output.append(INDENT2 "remove => " CS_PROPERTY_SINGLETON ".Disconnect(SignalName.");
@@ -2313,8 +2366,14 @@ Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterf
 			p_output.append(INDENT2 "remove => Disconnect(SignalName.");
 		}
 
-		p_output.append(p_isignal.proxy_name);
-		p_output.append(", new Callable(value));\n");
+		if (is_parameterless) {
+			// Delegate type is Action. No need for custom trampoline.
+			p_output << p_isignal.proxy_name << ", Callable.From(value));\n";
+		} else {
+			p_output << p_isignal.proxy_name
+					 << ", Callable.CreateWithUnsafeTrampoline(value, &" << p_isignal.proxy_name << "Trampoline));\n";
+		}
+
 		p_output.append(CLOSE_BLOCK_L1);
 	}
 

+ 22 - 10
modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs

@@ -489,25 +489,37 @@ namespace Godot.Collections
         ICollection<T>,
         IEnumerable<T>
     {
+        private static godot_variant ToVariantFunc(in Array<T> godotArray) =>
+            VariantUtils.CreateFromArray(godotArray);
+
+        private static Array<T> FromVariantFunc(in godot_variant variant) =>
+            VariantUtils.ConvertToArrayObject<T>(variant);
+
         // ReSharper disable StaticMemberInGenericType
         // Warning is about unique static fields being created for each generic type combination:
         // https://www.jetbrains.com/help/resharper/StaticMemberInGenericType.html
         // In our case this is exactly what we want.
 
-        private static unsafe delegate* managed<in T, godot_variant> _convertToVariantCallback;
-        private static unsafe delegate* managed<in godot_variant, T> _convertToManagedCallback;
+        private static readonly unsafe delegate* managed<in T, godot_variant> ConvertToVariantCallback;
+        private static readonly unsafe delegate* managed<in godot_variant, T> ConvertToManagedCallback;
 
         // ReSharper restore StaticMemberInGenericType
 
         static unsafe Array()
         {
-            _convertToVariantCallback = VariantConversionCallbacks.GetToVariantCallback<T>();
-            _convertToManagedCallback = VariantConversionCallbacks.GetToManagedCallback<T>();
+            VariantConversionCallbacks.GenericConversionCallbacks[typeof(Array<T>)] =
+            (
+                (IntPtr)(delegate* managed<in Array<T>, godot_variant>)&ToVariantFunc,
+                (IntPtr)(delegate* managed<in godot_variant, Array<T>>)&FromVariantFunc
+            );
+
+            ConvertToVariantCallback = VariantConversionCallbacks.GetToVariantCallback<T>();
+            ConvertToManagedCallback = VariantConversionCallbacks.GetToManagedCallback<T>();
         }
 
         private static unsafe void ValidateVariantConversionCallbacks()
         {
-            if (_convertToVariantCallback == null || _convertToManagedCallback == null)
+            if (ConvertToVariantCallback == null || ConvertToManagedCallback == null)
             {
                 throw new InvalidOperationException(
                     $"The array element type is not supported for conversion to Variant: '{typeof(T).FullName}'.");
@@ -653,7 +665,7 @@ namespace Godot.Collections
             get
             {
                 _underlyingArray.GetVariantBorrowElementAt(index, out godot_variant borrowElem);
-                return _convertToManagedCallback(borrowElem);
+                return ConvertToManagedCallback(borrowElem);
             }
             set
             {
@@ -663,7 +675,7 @@ namespace Godot.Collections
                 godot_variant* ptrw = NativeFuncs.godotsharp_array_ptrw(ref self);
                 godot_variant* itemPtr = &ptrw[index];
                 (*itemPtr).Dispose();
-                *itemPtr = _convertToVariantCallback(value);
+                *itemPtr = ConvertToVariantCallback(value);
             }
         }
 
@@ -675,7 +687,7 @@ namespace Godot.Collections
         /// <returns>The index of the item, or -1 if not found.</returns>
         public unsafe int IndexOf(T item)
         {
-            using var variantValue = _convertToVariantCallback(item);
+            using var variantValue = ConvertToVariantCallback(item);
             var self = (godot_array)_underlyingArray.NativeValue;
             return NativeFuncs.godotsharp_array_index_of(ref self, variantValue);
         }
@@ -693,7 +705,7 @@ namespace Godot.Collections
             if (index < 0 || index > Count)
                 throw new ArgumentOutOfRangeException(nameof(index));
 
-            using var variantValue = _convertToVariantCallback(item);
+            using var variantValue = ConvertToVariantCallback(item);
             var self = (godot_array)_underlyingArray.NativeValue;
             NativeFuncs.godotsharp_array_insert(ref self, index, variantValue);
         }
@@ -726,7 +738,7 @@ namespace Godot.Collections
         /// <returns>The new size after adding the item.</returns>
         public unsafe void Add(T item)
         {
-            using var variantValue = _convertToVariantCallback(item);
+            using var variantValue = ConvertToVariantCallback(item);
             var self = (godot_array)_underlyingArray.NativeValue;
             _ = NativeFuncs.godotsharp_array_add(ref self, variantValue);
         }

+ 2 - 3
modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/CSharpInstanceBridge.cs

@@ -22,8 +22,7 @@ namespace Godot.Bridge
                 }
 
                 bool methodInvoked = godotObject.InvokeGodotClassMethod(CustomUnsafe.AsRef(method),
-                    new NativeVariantPtrArgs(args),
-                    argCount, out godot_variant retValue);
+                    new NativeVariantPtrArgs(args, argCount), out godot_variant retValue);
 
                 if (!methodInvoked)
                 {
@@ -102,7 +101,7 @@ namespace Godot.Bridge
                     return godot_bool.False;
                 }
 
-                *outRet = Marshaling.ConvertManagedObjectToVariant(ret);
+                *outRet = ret.CopyNativeVariant();
                 return godot_bool.True;
             }
             catch (Exception e)

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

@@ -9,7 +9,7 @@ namespace Godot.Bridge
     {
         // @formatter:off
         public delegate* unmanaged<IntPtr, godot_variant**, int, godot_bool*, void> SignalAwaiter_SignalCallback;
-        public delegate* unmanaged<IntPtr, godot_variant**, uint, godot_variant*, void> DelegateUtils_InvokeWithVariantArgs;
+        public delegate* unmanaged<IntPtr, void*, godot_variant**, int, godot_variant*, void> DelegateUtils_InvokeWithVariantArgs;
         public delegate* unmanaged<IntPtr, IntPtr, godot_bool> DelegateUtils_DelegateEquals;
         public delegate* unmanaged<IntPtr, godot_array*, godot_bool> DelegateUtils_TrySerializeDelegateWithGCHandle;
         public delegate* unmanaged<godot_array*, IntPtr*, godot_bool> DelegateUtils_TryDeserializeDelegateWithGCHandle;

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

@@ -339,7 +339,7 @@ namespace Godot.Bridge
                 *outOwnerIsNull = godot_bool.False;
 
                 owner.RaiseGodotClassSignalCallbacks(CustomUnsafe.AsRef(eventSignalName),
-                    new NativeVariantPtrArgs(args), argCount);
+                    new NativeVariantPtrArgs(args, argCount));
             }
             catch (Exception e)
             {

+ 64 - 10
modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs

@@ -26,11 +26,12 @@ namespace Godot
     /// }
     /// </code>
     /// </example>
-    public readonly struct Callable
+    public readonly partial struct Callable
     {
         private readonly Object _target;
         private readonly StringName _method;
         private readonly Delegate _delegate;
+        private readonly unsafe delegate* managed<object, NativeVariantPtrArgs, out godot_variant, void> _trampoline;
 
         /// <summary>
         /// Object that contains the method.
@@ -48,10 +49,10 @@ namespace Godot
         public Delegate Delegate => _delegate;
 
         /// <summary>
-        /// Converts a <see cref="Delegate"/> to a <see cref="Callable"/>.
+        /// Trampoline function pointer for dynamically invoking <see cref="Callable.Delegate"/>.
         /// </summary>
-        /// <param name="delegate">The delegate to convert.</param>
-        public static implicit operator Callable(Delegate @delegate) => new Callable(@delegate);
+        public unsafe delegate* managed<object, NativeVariantPtrArgs, out godot_variant, void> Trampoline
+            => _trampoline;
 
         /// <summary>
         /// Constructs a new <see cref="Callable"/> for the method called <paramref name="method"/>
@@ -59,22 +60,21 @@ namespace Godot
         /// </summary>
         /// <param name="target">Object that contains the method.</param>
         /// <param name="method">Name of the method that will be called.</param>
-        public Callable(Object target, StringName method)
+        public unsafe Callable(Object target, StringName method)
         {
             _target = target;
             _method = method;
             _delegate = null;
+            _trampoline = null;
         }
 
-        /// <summary>
-        /// Constructs a new <see cref="Callable"/> for the given <paramref name="delegate"/>.
-        /// </summary>
-        /// <param name="delegate">Delegate method that will be called.</param>
-        public Callable(Delegate @delegate)
+        private unsafe Callable(Delegate @delegate,
+            delegate* managed<object, NativeVariantPtrArgs, out godot_variant, void> trampoline)
         {
             _target = @delegate?.Target as Object;
             _method = null;
             _delegate = @delegate;
+            _trampoline = trampoline;
         }
 
         private const int VarArgsSpanThreshold = 5;
@@ -149,5 +149,59 @@ namespace Godot
                 NativeFuncs.godotsharp_callable_call_deferred(callable, (godot_variant**)argsPtr, argc);
             }
         }
+
+        /// <summary>
+        /// <para>
+        /// Constructs a new <see cref="Callable"/> using the <paramref name="trampoline"/>
+        /// function pointer to dynamically invoke the given <paramref name="delegate"/>.
+        /// </para>
+        /// <para>
+        /// The parameters passed to the <paramref name="trampoline"/> function are:
+        /// </para>
+        /// <list type="number">
+        ///    <item>
+        ///        <term>delegateObj</term>
+        ///        <description>The given <paramref name="delegate"/>, upcast to <see cref="object"/>.</description>
+        ///    </item>
+        ///    <item>
+        ///        <term>args</term>
+        ///        <description>Array of <see cref="godot_variant"/> arguments.</description>
+        ///    </item>
+        ///    <item>
+        ///        <term>ret</term>
+        ///        <description>Return value of type <see cref="godot_variant"/>.</description>
+        ///    </item>
+        ///</list>
+        /// <para>
+        /// The delegate should be downcast to a more specific delegate type before invoking.
+        /// </para>
+        /// </summary>
+        /// <example>
+        /// Usage example:
+        ///
+        /// <code>
+        ///     static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)
+        ///     {
+        ///         if (args.Count != 1)
+        ///             throw new ArgumentException($&quot;Callable expected {1} arguments but received {args.Count}.&quot;);
+        ///
+        ///         TResult res = ((Func&lt;int, string&gt;)delegateObj)(
+        ///             VariantConversionCallbacks.GetToManagedCallback&lt;int&gt;()(args[0])
+        ///         );
+        ///
+        ///         ret = VariantConversionCallbacks.GetToVariantCallback&lt;string&gt;()(res);
+        ///     }
+        ///
+        ///     var callable = Callable.CreateWithUnsafeTrampoline((int num) =&gt; &quot;foo&quot; + num.ToString(), &amp;Trampoline);
+        ///     var res = (string)callable.Call(10);
+        ///     Console.WriteLine(res);
+        /// </code>
+        /// </example>
+        /// <param name="delegate">Delegate method that will be called.</param>
+        /// <param name="trampoline">Trampoline function pointer for invoking the delegate.</param>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static unsafe Callable CreateWithUnsafeTrampoline(Delegate @delegate,
+            delegate* managed<object, NativeVariantPtrArgs, out godot_variant, void> trampoline)
+            => new(@delegate, trampoline);
     }
 }

+ 480 - 0
modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.generics.cs

@@ -0,0 +1,480 @@
+using System;
+using System.Runtime.CompilerServices;
+using Godot.NativeInterop;
+
+namespace Godot;
+
+#nullable enable
+
+public readonly partial struct Callable
+{
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    internal static void ThrowIfArgCountMismatch(NativeVariantPtrArgs args, int countExpected,
+        [CallerArgumentExpression("args")] string? paramName = null)
+    {
+        if (countExpected != args.Count)
+            ThrowArgCountMismatch(countExpected, args.Count, paramName);
+
+        static void ThrowArgCountMismatch(int countExpected, int countReceived, string? paramName)
+        {
+            throw new ArgumentException(
+                "Invalid argument count for invoking callable." +
+                $" Expected {countExpected} arguments, received {countReceived}.",
+                paramName);
+        }
+    }
+
+    /// <summary>
+    /// Constructs a new <see cref="Callable"/> for the given <paramref name="action"/>.
+    /// </summary>
+    /// <param name="action">Action method that will be called.</param>
+    public static unsafe Callable From(
+        Action action
+    )
+    {
+        static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)
+        {
+            ThrowIfArgCountMismatch(args, 0);
+
+            ((Action)delegateObj)();
+
+            ret = default;
+        }
+
+        return CreateWithUnsafeTrampoline(action, &Trampoline);
+    }
+
+    /// <inheritdoc cref="From(Action)"/>
+    public static unsafe Callable From<T0>(
+        Action<T0> action
+    )
+    {
+        static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)
+        {
+            ThrowIfArgCountMismatch(args, 1);
+
+            ((Action<T0>)delegateObj)(
+                VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0])
+            );
+
+            ret = default;
+        }
+
+        return CreateWithUnsafeTrampoline(action, &Trampoline);
+    }
+
+    /// <inheritdoc cref="From(Action)"/>
+    public static unsafe Callable From<T0, T1>(
+        Action<T0, T1> action
+    )
+    {
+        static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)
+        {
+            ThrowIfArgCountMismatch(args, 2);
+
+            ((Action<T0, T1>)delegateObj)(
+                VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]),
+                VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1])
+            );
+
+            ret = default;
+        }
+
+        return CreateWithUnsafeTrampoline(action, &Trampoline);
+    }
+
+    /// <inheritdoc cref="From(Action)"/>
+    public static unsafe Callable From<T0, T1, T2>(
+        Action<T0, T1, T2> action
+    )
+    {
+        static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)
+        {
+            ThrowIfArgCountMismatch(args, 3);
+
+            ((Action<T0, T1, T2>)delegateObj)(
+                VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]),
+                VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]),
+                VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2])
+            );
+
+            ret = default;
+        }
+
+        return CreateWithUnsafeTrampoline(action, &Trampoline);
+    }
+
+    /// <inheritdoc cref="From(Action)"/>
+    public static unsafe Callable From<T0, T1, T2, T3>(
+        Action<T0, T1, T2, T3> action
+    )
+    {
+        static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)
+        {
+            ThrowIfArgCountMismatch(args, 4);
+
+            ((Action<T0, T1, T2, T3>)delegateObj)(
+                VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]),
+                VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]),
+                VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]),
+                VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3])
+            );
+
+            ret = default;
+        }
+
+        return CreateWithUnsafeTrampoline(action, &Trampoline);
+    }
+
+    /// <inheritdoc cref="From(Action)"/>
+    public static unsafe Callable From<T0, T1, T2, T3, T4>(
+        Action<T0, T1, T2, T3, T4> action
+    )
+    {
+        static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)
+        {
+            ThrowIfArgCountMismatch(args, 5);
+
+            ((Action<T0, T1, T2, T3, T4>)delegateObj)(
+                VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]),
+                VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]),
+                VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]),
+                VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3]),
+                VariantConversionCallbacks.GetToManagedCallback<T4>()(args[4])
+            );
+
+            ret = default;
+        }
+
+        return CreateWithUnsafeTrampoline(action, &Trampoline);
+    }
+
+    /// <inheritdoc cref="From(Action)"/>
+    public static unsafe Callable From<T0, T1, T2, T3, T4, T5>(
+        Action<T0, T1, T2, T3, T4, T5> action
+    )
+    {
+        static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)
+        {
+            ThrowIfArgCountMismatch(args, 6);
+
+            ((Action<T0, T1, T2, T3, T4, T5>)delegateObj)(
+                VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]),
+                VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]),
+                VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]),
+                VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3]),
+                VariantConversionCallbacks.GetToManagedCallback<T4>()(args[4]),
+                VariantConversionCallbacks.GetToManagedCallback<T5>()(args[5])
+            );
+
+            ret = default;
+        }
+
+        return CreateWithUnsafeTrampoline(action, &Trampoline);
+    }
+
+    /// <inheritdoc cref="From(Action)"/>
+    public static unsafe Callable From<T0, T1, T2, T3, T4, T5, T6>(
+        Action<T0, T1, T2, T3, T4, T5, T6> action
+    )
+    {
+        static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)
+        {
+            ThrowIfArgCountMismatch(args, 7);
+
+            ((Action<T0, T1, T2, T3, T4, T5, T6>)delegateObj)(
+                VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]),
+                VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]),
+                VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]),
+                VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3]),
+                VariantConversionCallbacks.GetToManagedCallback<T4>()(args[4]),
+                VariantConversionCallbacks.GetToManagedCallback<T5>()(args[5]),
+                VariantConversionCallbacks.GetToManagedCallback<T6>()(args[6])
+            );
+
+            ret = default;
+        }
+
+        return CreateWithUnsafeTrampoline(action, &Trampoline);
+    }
+
+    /// <inheritdoc cref="From(Action)"/>
+    public static unsafe Callable From<T0, T1, T2, T3, T4, T5, T6, T7>(
+        Action<T0, T1, T2, T3, T4, T5, T6, T7> action
+    )
+    {
+        static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)
+        {
+            ThrowIfArgCountMismatch(args, 8);
+
+            ((Action<T0, T1, T2, T3, T4, T5, T6, T7>)delegateObj)(
+                VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]),
+                VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]),
+                VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]),
+                VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3]),
+                VariantConversionCallbacks.GetToManagedCallback<T4>()(args[4]),
+                VariantConversionCallbacks.GetToManagedCallback<T5>()(args[5]),
+                VariantConversionCallbacks.GetToManagedCallback<T6>()(args[6]),
+                VariantConversionCallbacks.GetToManagedCallback<T7>()(args[7])
+            );
+
+            ret = default;
+        }
+
+        return CreateWithUnsafeTrampoline(action, &Trampoline);
+    }
+
+    /// <inheritdoc cref="From(Action)"/>
+    public static unsafe Callable From<T0, T1, T2, T3, T4, T5, T6, T7, T8>(
+        Action<T0, T1, T2, T3, T4, T5, T6, T7, T8> action
+    )
+    {
+        static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)
+        {
+            ThrowIfArgCountMismatch(args, 9);
+
+            ((Action<T0, T1, T2, T3, T4, T5, T6, T7, T8>)delegateObj)(
+                VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]),
+                VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]),
+                VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]),
+                VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3]),
+                VariantConversionCallbacks.GetToManagedCallback<T4>()(args[4]),
+                VariantConversionCallbacks.GetToManagedCallback<T5>()(args[5]),
+                VariantConversionCallbacks.GetToManagedCallback<T6>()(args[6]),
+                VariantConversionCallbacks.GetToManagedCallback<T7>()(args[7]),
+                VariantConversionCallbacks.GetToManagedCallback<T8>()(args[8])
+            );
+
+            ret = default;
+        }
+
+        return CreateWithUnsafeTrampoline(action, &Trampoline);
+    }
+
+    /// <summary>
+    /// Constructs a new <see cref="Callable"/> for the given <paramref name="func"/>.
+    /// </summary>
+    /// <param name="func">Action method that will be called.</param>
+    public static unsafe Callable From<TResult>(
+        Func<TResult> func
+    )
+    {
+        static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)
+        {
+            ThrowIfArgCountMismatch(args, 0);
+
+            TResult res = ((Func<TResult>)delegateObj)();
+
+            ret = VariantConversionCallbacks.GetToVariantCallback<TResult>()(res);
+        }
+
+        return CreateWithUnsafeTrampoline(func, &Trampoline);
+    }
+
+    /// <inheritdoc cref="From{TResult}(Func{TResult})"/>
+    public static unsafe Callable From<T0, TResult>(
+        Func<T0, TResult> func
+    )
+    {
+        static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)
+        {
+            ThrowIfArgCountMismatch(args, 1);
+
+            TResult res = ((Func<T0, TResult>)delegateObj)(
+                VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0])
+            );
+
+            ret = VariantConversionCallbacks.GetToVariantCallback<TResult>()(res);
+        }
+
+        return CreateWithUnsafeTrampoline(func, &Trampoline);
+    }
+
+    /// <inheritdoc cref="From{TResult}(Func{TResult})"/>
+    public static unsafe Callable From<T0, T1, TResult>(
+        Func<T0, T1, TResult> func
+    )
+    {
+        static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)
+        {
+            ThrowIfArgCountMismatch(args, 2);
+
+            TResult res = ((Func<T0, T1, TResult>)delegateObj)(
+                VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]),
+                VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1])
+            );
+
+            ret = VariantConversionCallbacks.GetToVariantCallback<TResult>()(res);
+        }
+
+        return CreateWithUnsafeTrampoline(func, &Trampoline);
+    }
+
+    /// <inheritdoc cref="From{TResult}(Func{TResult})"/>
+    public static unsafe Callable From<T0, T1, T2, TResult>(
+        Func<T0, T1, T2, TResult> func
+    )
+    {
+        static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)
+        {
+            ThrowIfArgCountMismatch(args, 3);
+
+            TResult res = ((Func<T0, T1, T2, TResult>)delegateObj)(
+                VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]),
+                VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]),
+                VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2])
+            );
+
+            ret = VariantConversionCallbacks.GetToVariantCallback<TResult>()(res);
+        }
+
+        return CreateWithUnsafeTrampoline(func, &Trampoline);
+    }
+
+    /// <inheritdoc cref="From{TResult}(Func{TResult})"/>
+    public static unsafe Callable From<T0, T1, T2, T3, TResult>(
+        Func<T0, T1, T2, T3, TResult> func
+    )
+    {
+        static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)
+        {
+            ThrowIfArgCountMismatch(args, 4);
+
+            TResult res = ((Func<T0, T1, T2, T3, TResult>)delegateObj)(
+                VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]),
+                VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]),
+                VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]),
+                VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3])
+            );
+
+            ret = VariantConversionCallbacks.GetToVariantCallback<TResult>()(res);
+        }
+
+        return CreateWithUnsafeTrampoline(func, &Trampoline);
+    }
+
+    /// <inheritdoc cref="From{TResult}(Func{TResult})"/>
+    public static unsafe Callable From<T0, T1, T2, T3, T4, TResult>(
+        Func<T0, T1, T2, T3, T4, TResult> func
+    )
+    {
+        static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)
+        {
+            ThrowIfArgCountMismatch(args, 5);
+
+            TResult res = ((Func<T0, T1, T2, T3, T4, TResult>)delegateObj)(
+                VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]),
+                VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]),
+                VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]),
+                VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3]),
+                VariantConversionCallbacks.GetToManagedCallback<T4>()(args[4])
+            );
+
+            ret = VariantConversionCallbacks.GetToVariantCallback<TResult>()(res);
+        }
+
+        return CreateWithUnsafeTrampoline(func, &Trampoline);
+    }
+
+    /// <inheritdoc cref="From{TResult}(Func{TResult})"/>
+    public static unsafe Callable From<T0, T1, T2, T3, T4, T5, TResult>(
+        Func<T0, T1, T2, T3, T4, T5, TResult> func
+    )
+    {
+        static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)
+        {
+            ThrowIfArgCountMismatch(args, 6);
+
+            TResult res = ((Func<T0, T1, T2, T3, T4, T5, TResult>)delegateObj)(
+                VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]),
+                VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]),
+                VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]),
+                VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3]),
+                VariantConversionCallbacks.GetToManagedCallback<T4>()(args[4]),
+                VariantConversionCallbacks.GetToManagedCallback<T5>()(args[5])
+            );
+
+            ret = VariantConversionCallbacks.GetToVariantCallback<TResult>()(res);
+        }
+
+        return CreateWithUnsafeTrampoline(func, &Trampoline);
+    }
+
+    /// <inheritdoc cref="From{TResult}(Func{TResult})"/>
+    public static unsafe Callable From<T0, T1, T2, T3, T4, T5, T6, TResult>(
+        Func<T0, T1, T2, T3, T4, T5, T6, TResult> func
+    )
+    {
+        static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)
+        {
+            ThrowIfArgCountMismatch(args, 7);
+
+            TResult res = ((Func<T0, T1, T2, T3, T4, T5, T6, TResult>)delegateObj)(
+                VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]),
+                VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]),
+                VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]),
+                VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3]),
+                VariantConversionCallbacks.GetToManagedCallback<T4>()(args[4]),
+                VariantConversionCallbacks.GetToManagedCallback<T5>()(args[5]),
+                VariantConversionCallbacks.GetToManagedCallback<T6>()(args[6])
+            );
+
+            ret = VariantConversionCallbacks.GetToVariantCallback<TResult>()(res);
+        }
+
+        return CreateWithUnsafeTrampoline(func, &Trampoline);
+    }
+
+    /// <inheritdoc cref="From{TResult}(Func{TResult})"/>
+    public static unsafe Callable From<T0, T1, T2, T3, T4, T5, T6, T7, TResult>(
+        Func<T0, T1, T2, T3, T4, T5, T6, T7, TResult> func
+    )
+    {
+        static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)
+        {
+            ThrowIfArgCountMismatch(args, 8);
+
+            TResult res = ((Func<T0, T1, T2, T3, T4, T5, T6, T7, TResult>)delegateObj)(
+                VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]),
+                VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]),
+                VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]),
+                VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3]),
+                VariantConversionCallbacks.GetToManagedCallback<T4>()(args[4]),
+                VariantConversionCallbacks.GetToManagedCallback<T5>()(args[5]),
+                VariantConversionCallbacks.GetToManagedCallback<T6>()(args[6]),
+                VariantConversionCallbacks.GetToManagedCallback<T7>()(args[7])
+            );
+
+            ret = VariantConversionCallbacks.GetToVariantCallback<TResult>()(res);
+        }
+
+        return CreateWithUnsafeTrampoline(func, &Trampoline);
+    }
+
+    /// <inheritdoc cref="From{TResult}(Func{TResult})"/>
+    public static unsafe Callable From<T0, T1, T2, T3, T4, T5, T6, T7, T8, TResult>(
+        Func<T0, T1, T2, T3, T4, T5, T6, T7, T8, TResult> func
+    )
+    {
+        static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)
+        {
+            ThrowIfArgCountMismatch(args, 9);
+
+            TResult res = ((Func<T0, T1, T2, T3, T4, T5, T6, T7, T8, TResult>)delegateObj)(
+                VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]),
+                VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]),
+                VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]),
+                VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3]),
+                VariantConversionCallbacks.GetToManagedCallback<T4>()(args[4]),
+                VariantConversionCallbacks.GetToManagedCallback<T5>()(args[5]),
+                VariantConversionCallbacks.GetToManagedCallback<T6>()(args[6]),
+                VariantConversionCallbacks.GetToManagedCallback<T7>()(args[7]),
+                VariantConversionCallbacks.GetToManagedCallback<T8>()(args[8])
+            );
+
+            ret = VariantConversionCallbacks.GetToVariantCallback<TResult>()(res);
+        }
+
+        return CreateWithUnsafeTrampoline(func, &Trampoline);
+    }
+}

+ 9 - 19
modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs

@@ -30,33 +30,23 @@ namespace Godot
         }
 
         [UnmanagedCallersOnly]
-        internal static unsafe void InvokeWithVariantArgs(IntPtr delegateGCHandle, godot_variant** args, uint argc,
-            godot_variant* outRet)
+        internal static unsafe void InvokeWithVariantArgs(IntPtr delegateGCHandle, void* trampoline,
+            godot_variant** args, int argc, godot_variant* outRet)
         {
             try
             {
-                // TODO: Optimize
-                var @delegate = (Delegate)GCHandle.FromIntPtr(delegateGCHandle).Target!;
-                var managedArgs = new object?[argc];
-
-                var parameterInfos = @delegate.Method.GetParameters();
-                var paramsLength = parameterInfos.Length;
-
-                if (argc != paramsLength)
+                if (trampoline == null)
                 {
-                    throw new InvalidOperationException(
-                        $"The delegate expects {paramsLength} arguments, but received {argc}.");
+                    throw new ArgumentNullException(nameof(trampoline),
+                        "Cannot dynamically invoke delegate because the trampoline is null.");
                 }
 
-                for (uint i = 0; i < argc; i++)
-                {
-                    managedArgs[i] = Marshaling.ConvertVariantToManagedObjectOfType(
-                        *args[i], parameterInfos[i].ParameterType);
-                }
+                var @delegate = (Delegate)GCHandle.FromIntPtr(delegateGCHandle).Target!;
+                var trampolineFn = (delegate* managed<object, NativeVariantPtrArgs, out godot_variant, void>)trampoline;
 
-                object? invokeRet = @delegate.DynamicInvoke(managedArgs);
+                trampolineFn(@delegate, new NativeVariantPtrArgs(args, argc), out godot_variant ret);
 
-                *outRet = Marshaling.ConvertManagedObjectToVariant(invokeRet);
+                *outRet = ret;
             }
             catch (Exception e)
             {

+ 40 - 27
modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs

@@ -356,35 +356,47 @@ namespace Godot.Collections
         IDictionary<TKey, TValue>,
         IReadOnlyDictionary<TKey, TValue>
     {
+        private static godot_variant ToVariantFunc(in Dictionary<TKey, TValue> godotDictionary) =>
+            VariantUtils.CreateFromDictionary(godotDictionary);
+
+        private static Dictionary<TKey, TValue> FromVariantFunc(in godot_variant variant) =>
+            VariantUtils.ConvertToDictionaryObject<TKey, TValue>(variant);
+
         // ReSharper disable StaticMemberInGenericType
         // Warning is about unique static fields being created for each generic type combination:
         // https://www.jetbrains.com/help/resharper/StaticMemberInGenericType.html
         // In our case this is exactly what we want.
 
-        private static unsafe delegate* managed<in TKey, godot_variant> _convertKeyToVariantCallback;
-        private static unsafe delegate* managed<in godot_variant, TKey> _convertKeyToManagedCallback;
-        private static unsafe delegate* managed<in TValue, godot_variant> _convertValueToVariantCallback;
-        private static unsafe delegate* managed<in godot_variant, TValue> _convertValueToManagedCallback;
+        private static readonly unsafe delegate* managed<in TKey, godot_variant> ConvertKeyToVariantCallback;
+        private static readonly unsafe delegate* managed<in godot_variant, TKey> ConvertKeyToManagedCallback;
+        private static readonly unsafe delegate* managed<in TValue, godot_variant> ConvertValueToVariantCallback;
+        private static readonly unsafe delegate* managed<in godot_variant, TValue> ConvertValueToManagedCallback;
 
         // ReSharper restore StaticMemberInGenericType
 
         static unsafe Dictionary()
         {
-            _convertKeyToVariantCallback = VariantConversionCallbacks.GetToVariantCallback<TKey>();
-            _convertKeyToManagedCallback = VariantConversionCallbacks.GetToManagedCallback<TKey>();
-            _convertValueToVariantCallback = VariantConversionCallbacks.GetToVariantCallback<TValue>();
-            _convertValueToManagedCallback = VariantConversionCallbacks.GetToManagedCallback<TValue>();
+            VariantConversionCallbacks.GenericConversionCallbacks[typeof(Dictionary<TKey, TValue>)] =
+            (
+                (IntPtr)(delegate* managed<in Dictionary<TKey, TValue>, godot_variant>)&ToVariantFunc,
+                (IntPtr)(delegate* managed<in godot_variant, Dictionary<TKey, TValue>>)&FromVariantFunc
+            );
+
+            ConvertKeyToVariantCallback = VariantConversionCallbacks.GetToVariantCallback<TKey>();
+            ConvertKeyToManagedCallback = VariantConversionCallbacks.GetToManagedCallback<TKey>();
+            ConvertValueToVariantCallback = VariantConversionCallbacks.GetToVariantCallback<TValue>();
+            ConvertValueToManagedCallback = VariantConversionCallbacks.GetToManagedCallback<TValue>();
         }
 
         private static unsafe void ValidateVariantConversionCallbacks()
         {
-            if (_convertKeyToVariantCallback == null || _convertKeyToManagedCallback == null)
+            if (ConvertKeyToVariantCallback == null || ConvertKeyToManagedCallback == null)
             {
                 throw new InvalidOperationException(
                     $"The dictionary key type is not supported for conversion to Variant: '{typeof(TKey).FullName}'.");
             }
 
-            if (_convertValueToVariantCallback == null || _convertValueToManagedCallback == null)
+            if (ConvertValueToVariantCallback == null || ConvertValueToManagedCallback == null)
             {
                 throw new InvalidOperationException(
                     $"The dictionary value type is not supported for conversion to Variant: '{typeof(TValue).FullName}'.");
@@ -473,14 +485,14 @@ namespace Godot.Collections
         {
             get
             {
-                using var variantKey = _convertKeyToVariantCallback(key);
+                using var variantKey = ConvertKeyToVariantCallback(key);
                 var self = (godot_dictionary)_underlyingDict.NativeValue;
 
                 if (NativeFuncs.godotsharp_dictionary_try_get_value(ref self,
                         variantKey, out godot_variant value).ToBool())
                 {
                     using (value)
-                        return _convertValueToManagedCallback(value);
+                        return ConvertValueToManagedCallback(value);
                 }
                 else
                 {
@@ -489,8 +501,8 @@ namespace Godot.Collections
             }
             set
             {
-                using var variantKey = _convertKeyToVariantCallback(key);
-                using var variantValue = _convertValueToVariantCallback(value);
+                using var variantKey = ConvertKeyToVariantCallback(key);
+                using var variantValue = ConvertValueToVariantCallback(value);
                 var self = (godot_dictionary)_underlyingDict.NativeValue;
                 NativeFuncs.godotsharp_dictionary_set_value(ref self,
                     variantKey, variantValue);
@@ -539,8 +551,8 @@ namespace Godot.Collections
             using (value)
             {
                 return new KeyValuePair<TKey, TValue>(
-                    _convertKeyToManagedCallback(key),
-                    _convertValueToManagedCallback(value));
+                    ConvertKeyToManagedCallback(key),
+                    ConvertValueToManagedCallback(value));
             }
         }
 
@@ -552,13 +564,13 @@ namespace Godot.Collections
         /// <param name="value">The object to add.</param>
         public unsafe void Add(TKey key, TValue value)
         {
-            using var variantKey = _convertKeyToVariantCallback(key);
+            using var variantKey = ConvertKeyToVariantCallback(key);
             var self = (godot_dictionary)_underlyingDict.NativeValue;
 
             if (NativeFuncs.godotsharp_dictionary_contains_key(ref self, variantKey).ToBool())
                 throw new ArgumentException("An element with the same key already exists.", nameof(key));
 
-            using var variantValue = _convertValueToVariantCallback(value);
+            using var variantValue = ConvertValueToVariantCallback(value);
             NativeFuncs.godotsharp_dictionary_add(ref self, variantKey, variantValue);
         }
 
@@ -569,7 +581,7 @@ namespace Godot.Collections
         /// <returns>Whether or not this dictionary contains the given key.</returns>
         public unsafe bool ContainsKey(TKey key)
         {
-            using var variantKey = _convertKeyToVariantCallback(key);
+            using var variantKey = ConvertKeyToVariantCallback(key);
             var self = (godot_dictionary)_underlyingDict.NativeValue;
             return NativeFuncs.godotsharp_dictionary_contains_key(ref self, variantKey).ToBool();
         }
@@ -580,7 +592,7 @@ namespace Godot.Collections
         /// <param name="key">The key of the element to remove.</param>
         public unsafe bool Remove(TKey key)
         {
-            using var variantKey = _convertKeyToVariantCallback(key);
+            using var variantKey = ConvertKeyToVariantCallback(key);
             var self = (godot_dictionary)_underlyingDict.NativeValue;
             return NativeFuncs.godotsharp_dictionary_remove_key(ref self, variantKey).ToBool();
         }
@@ -593,13 +605,13 @@ namespace Godot.Collections
         /// <returns>If an object was found for the given <paramref name="key"/>.</returns>
         public unsafe bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value)
         {
-            using var variantKey = _convertKeyToVariantCallback(key);
+            using var variantKey = ConvertKeyToVariantCallback(key);
             var self = (godot_dictionary)_underlyingDict.NativeValue;
             bool found = NativeFuncs.godotsharp_dictionary_try_get_value(ref self,
                 variantKey, out godot_variant retValue).ToBool();
 
             using (retValue)
-                value = found ? _convertValueToManagedCallback(retValue) : default;
+                value = found ? ConvertValueToManagedCallback(retValue) : default;
 
             return found;
         }
@@ -625,7 +637,7 @@ namespace Godot.Collections
 
         unsafe bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item)
         {
-            using var variantKey = _convertKeyToVariantCallback(item.Key);
+            using var variantKey = ConvertKeyToVariantCallback(item.Key);
             var self = (godot_dictionary)_underlyingDict.NativeValue;
             bool found = NativeFuncs.godotsharp_dictionary_try_get_value(ref self,
                 variantKey, out godot_variant retValue).ToBool();
@@ -635,7 +647,7 @@ namespace Godot.Collections
                 if (!found)
                     return false;
 
-                using var variantValue = _convertValueToVariantCallback(item.Value);
+                using var variantValue = ConvertValueToVariantCallback(item.Value);
                 return NativeFuncs.godotsharp_variant_equals(variantValue, retValue).ToBool();
             }
         }
@@ -670,7 +682,7 @@ namespace Godot.Collections
 
         unsafe bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item)
         {
-            using var variantKey = _convertKeyToVariantCallback(item.Key);
+            using var variantKey = ConvertKeyToVariantCallback(item.Key);
             var self = (godot_dictionary)_underlyingDict.NativeValue;
             bool found = NativeFuncs.godotsharp_dictionary_try_get_value(ref self,
                 variantKey, out godot_variant retValue).ToBool();
@@ -680,7 +692,7 @@ namespace Godot.Collections
                 if (!found)
                     return false;
 
-                using var variantValue = _convertValueToVariantCallback(item.Value);
+                using var variantValue = ConvertValueToVariantCallback(item.Value);
                 if (NativeFuncs.godotsharp_variant_equals(variantValue, retValue).ToBool())
                 {
                     return NativeFuncs.godotsharp_dictionary_remove_key(
@@ -717,6 +729,7 @@ namespace Godot.Collections
         public static implicit operator Variant(Dictionary<TKey, TValue> from) => Variant.CreateFrom(from);
 
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        public static explicit operator Dictionary<TKey, TValue>(Variant from) => from.AsGodotDictionary<TKey, TValue>();
+        public static explicit operator Dictionary<TKey, TValue>(Variant from) =>
+            from.AsGodotDictionary<TKey, TValue>();
     }
 }

+ 25 - 13
modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs

@@ -721,10 +721,19 @@ namespace Godot.NativeInterop
             if (p_managed_callable.Delegate != null)
             {
                 var gcHandle = CustomGCHandle.AllocStrong(p_managed_callable.Delegate);
-                IntPtr objectPtr = p_managed_callable.Target != null ? Object.GetPtr(p_managed_callable.Target) : IntPtr.Zero;
-                NativeFuncs.godotsharp_callable_new_with_delegate(
-                    GCHandle.ToIntPtr(gcHandle), objectPtr, out godot_callable callable);
-                return callable;
+
+                IntPtr objectPtr = p_managed_callable.Target != null ?
+                    Object.GetPtr(p_managed_callable.Target) :
+                    IntPtr.Zero;
+
+                unsafe
+                {
+                    NativeFuncs.godotsharp_callable_new_with_delegate(
+                        GCHandle.ToIntPtr(gcHandle), (IntPtr)p_managed_callable.Trampoline,
+                        objectPtr, out godot_callable callable);
+
+                    return callable;
+                }
             }
             else
             {
@@ -748,19 +757,22 @@ namespace Godot.NativeInterop
         public static Callable ConvertCallableToManaged(in godot_callable p_callable)
         {
             if (NativeFuncs.godotsharp_callable_get_data_for_marshalling(p_callable,
-                    out IntPtr delegateGCHandle, out IntPtr godotObject,
-                    out godot_string_name name).ToBool())
+                    out IntPtr delegateGCHandle, out IntPtr trampoline,
+                    out IntPtr godotObject, out godot_string_name name).ToBool())
             {
                 if (delegateGCHandle != IntPtr.Zero)
                 {
-                    return new Callable((Delegate?)GCHandle.FromIntPtr(delegateGCHandle).Target);
-                }
-                else
-                {
-                    return new Callable(
-                        InteropUtils.UnmanagedGetManaged(godotObject),
-                        StringName.CreateTakingOwnershipOfDisposableValue(name));
+                    unsafe
+                    {
+                        return Callable.CreateWithUnsafeTrampoline(
+                            (Delegate?)GCHandle.FromIntPtr(delegateGCHandle).Target,
+                            (delegate* managed<object, NativeVariantPtrArgs, out godot_variant, void>)trampoline);
+                    }
                 }
+
+                return new Callable(
+                    InteropUtils.UnmanagedGetManaged(godotObject),
+                    StringName.CreateTakingOwnershipOfDisposableValue(name));
             }
 
             // Some other unsupported callable

+ 3 - 3
modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs

@@ -141,11 +141,11 @@ namespace Godot.NativeInterop
         public static partial void godotsharp_packed_string_array_add(ref godot_packed_string_array r_dest,
             in godot_string p_element);
 
-        public static partial void godotsharp_callable_new_with_delegate(IntPtr p_delegate_handle, IntPtr p_object,
-            out godot_callable r_callable);
+        public static partial void godotsharp_callable_new_with_delegate(IntPtr p_delegate_handle, IntPtr p_trampoline,
+            IntPtr p_object, out godot_callable r_callable);
 
         internal static partial godot_bool godotsharp_callable_get_data_for_marshalling(in godot_callable p_callable,
-            out IntPtr r_delegate_handle, out IntPtr r_object, out godot_string_name r_name);
+            out IntPtr r_delegate_handle, out IntPtr r_trampoline, out IntPtr r_object, out godot_string_name r_name);
 
         internal static partial godot_variant godotsharp_callable_call(in godot_callable p_callable,
             godot_variant** p_args, int p_arg_count, out godot_variant_call_error p_call_error);

+ 15 - 1
modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeVariantPtrArgs.cs

@@ -8,8 +8,22 @@ namespace Godot.NativeInterop
     public unsafe ref struct NativeVariantPtrArgs
     {
         private godot_variant** _args;
+        private int _argc;
 
-        internal NativeVariantPtrArgs(godot_variant** args) => _args = args;
+        internal NativeVariantPtrArgs(godot_variant** args, int argc)
+        {
+            _args = args;
+            _argc = argc;
+        }
+
+        /// <summary>
+        /// Returns the number of arguments.
+        /// </summary>
+        public int Count
+        {
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            get => _argc;
+        }
 
         public ref godot_variant this[int index]
         {

+ 45 - 0
modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantConversionCallbacks.cs

@@ -1,10 +1,15 @@
 using System;
 using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
 
 namespace Godot.NativeInterop;
 
+// TODO: Change VariantConversionCallbacks<T>. Store the callback in a static field for quick repeated access, instead of checking every time.
 internal static unsafe class VariantConversionCallbacks
 {
+    internal static System.Collections.Generic.Dictionary<Type, (IntPtr ToVariant, IntPtr FromVariant)>
+        GenericConversionCallbacks = new();
+
     [SuppressMessage("ReSharper", "RedundantNameQualifier")]
     internal static delegate*<in T, godot_variant> GetToVariantCallback<T>()
     {
@@ -502,6 +507,26 @@ internal static unsafe class VariantConversionCallbacks
                 &FromVariant;
         }
 
+        // TODO:
+        //   IsGenericType and GetGenericTypeDefinition don't work in NativeAOT's reflection-free mode.
+        //   We could make the Godot collections implement an interface and use IsAssignableFrom instead.
+        //   Or we could just skip the check and always look for a conversion callback for the type.
+        if (typeOfT.IsGenericType)
+        {
+            var genericTypeDef = typeOfT.GetGenericTypeDefinition();
+
+            if (genericTypeDef == typeof(Godot.Collections.Dictionary<,>) ||
+                genericTypeDef == typeof(Godot.Collections.Array<>))
+            {
+                RuntimeHelpers.RunClassConstructor(typeOfT.TypeHandle);
+
+                if (GenericConversionCallbacks.TryGetValue(typeOfT, out var genericConversion))
+                {
+                    return (delegate*<in T, godot_variant>)genericConversion.ToVariant;
+                }
+            }
+        }
+
         return null;
     }
 
@@ -1005,6 +1030,26 @@ internal static unsafe class VariantConversionCallbacks
                 &ToVariant;
         }
 
+        // TODO:
+        //   IsGenericType and GetGenericTypeDefinition don't work in NativeAOT's reflection-free mode.
+        //   We could make the Godot collections implement an interface and use IsAssignableFrom instead.
+        //   Or we could just skip the check and always look for a conversion callback for the type.
+        if (typeOfT.IsGenericType)
+        {
+            var genericTypeDef = typeOfT.GetGenericTypeDefinition();
+
+            if (genericTypeDef == typeof(Godot.Collections.Dictionary<,>) ||
+                genericTypeDef == typeof(Godot.Collections.Array<>))
+            {
+                RuntimeHelpers.RunClassConstructor(typeOfT.TypeHandle);
+
+                if (GenericConversionCallbacks.TryGetValue(typeOfT, out var genericConversion))
+                {
+                    return (delegate*<in godot_variant, T>)genericConversion.FromVariant;
+                }
+            }
+        }
+
         // ReSharper restore RedundantCast
 
         return null;

+ 1 - 1
modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs

@@ -202,7 +202,7 @@ namespace Godot
 
         // ReSharper disable once VirtualMemberNeverOverridden.Global
         protected internal virtual void RaiseGodotClassSignalCallbacks(in godot_string_name signal,
-            NativeVariantPtrArgs args, int argCount)
+            NativeVariantPtrArgs args)
         {
         }
 

+ 1 - 0
modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj

@@ -52,6 +52,7 @@
     <Compile Include="Core\AABB.cs" />
     <Compile Include="Core\Bridge\GodotSerializationInfo.cs" />
     <Compile Include="Core\Bridge\MethodInfo.cs" />
+    <Compile Include="Core\Callable.generics.cs" />
     <Compile Include="Core\CustomGCHandle.cs" />
     <Compile Include="Core\Array.cs" />
     <Compile Include="Core\Attributes\AssemblyHasScriptsAttribute.cs" />

+ 9 - 3
modules/mono/glue/runtime_interop.cpp

@@ -447,15 +447,16 @@ void godotsharp_packed_string_array_add(PackedStringArray *r_dest, const String
 	r_dest->append(*p_element);
 }
 
-void godotsharp_callable_new_with_delegate(GCHandleIntPtr p_delegate_handle, const Object *p_object, Callable *r_callable) {
+void godotsharp_callable_new_with_delegate(GCHandleIntPtr p_delegate_handle, void *p_trampoline,
+		const Object *p_object, Callable *r_callable) {
 	// TODO: Use pooling for ManagedCallable instances.
 	ObjectID objid = p_object ? p_object->get_instance_id() : ObjectID();
-	CallableCustom *managed_callable = memnew(ManagedCallable(p_delegate_handle, objid));
+	CallableCustom *managed_callable = memnew(ManagedCallable(p_delegate_handle, p_trampoline, objid));
 	memnew_placement(r_callable, Callable(managed_callable));
 }
 
 bool godotsharp_callable_get_data_for_marshalling(const Callable *p_callable,
-		GCHandleIntPtr *r_delegate_handle, Object **r_object, StringName *r_name) {
+		GCHandleIntPtr *r_delegate_handle, void **r_trampoline, Object **r_object, StringName *r_name) {
 	if (p_callable->is_custom()) {
 		CallableCustom *custom = p_callable->get_custom();
 		CallableCustom::CompareEqualFunc compare_equal_func = custom->get_compare_equal_func();
@@ -463,18 +464,21 @@ bool godotsharp_callable_get_data_for_marshalling(const Callable *p_callable,
 		if (compare_equal_func == ManagedCallable::compare_equal_func_ptr) {
 			ManagedCallable *managed_callable = static_cast<ManagedCallable *>(custom);
 			*r_delegate_handle = managed_callable->get_delegate();
+			*r_trampoline = managed_callable->get_trampoline();
 			*r_object = nullptr;
 			memnew_placement(r_name, StringName());
 			return true;
 		} else if (compare_equal_func == SignalAwaiterCallable::compare_equal_func_ptr) {
 			SignalAwaiterCallable *signal_awaiter_callable = static_cast<SignalAwaiterCallable *>(custom);
 			*r_delegate_handle = { nullptr };
+			*r_trampoline = nullptr;
 			*r_object = ObjectDB::get_instance(signal_awaiter_callable->get_object());
 			memnew_placement(r_name, StringName(signal_awaiter_callable->get_signal()));
 			return true;
 		} else if (compare_equal_func == EventSignalCallable::compare_equal_func_ptr) {
 			EventSignalCallable *event_signal_callable = static_cast<EventSignalCallable *>(custom);
 			*r_delegate_handle = { nullptr };
+			*r_trampoline = nullptr;
 			*r_object = ObjectDB::get_instance(event_signal_callable->get_object());
 			memnew_placement(r_name, StringName(event_signal_callable->get_signal()));
 			return true;
@@ -482,11 +486,13 @@ bool godotsharp_callable_get_data_for_marshalling(const Callable *p_callable,
 
 		// Some other CallableCustom. We only support ManagedCallable.
 		*r_delegate_handle = { nullptr };
+		*r_trampoline = nullptr;
 		*r_object = nullptr;
 		memnew_placement(r_name, StringName());
 		return false;
 	} else {
 		*r_delegate_handle = { nullptr };
+		*r_trampoline = nullptr;
 		*r_object = ObjectDB::get_instance(p_callable->get_object_id());
 		memnew_placement(r_name, StringName(p_callable->get_method()));
 		return true;

+ 3 - 2
modules/mono/managed_callable.cpp

@@ -92,7 +92,7 @@ void ManagedCallable::call(const Variant **p_arguments, int p_argcount, Variant
 	ERR_FAIL_COND(delegate_handle.value == nullptr);
 
 	GDMonoCache::managed_callbacks.DelegateUtils_InvokeWithVariantArgs(
-			delegate_handle, p_arguments, p_argcount, &r_return_value);
+			delegate_handle, trampoline, p_arguments, p_argcount, &r_return_value);
 
 	r_call_error.error = Callable::CallError::CALL_OK;
 }
@@ -106,7 +106,8 @@ void ManagedCallable::release_delegate_handle() {
 
 // Why you do this clang-format...
 /* clang-format off */
-ManagedCallable::ManagedCallable(GCHandleIntPtr p_delegate_handle, ObjectID p_object_id) : delegate_handle(p_delegate_handle), object_id(p_object_id) {
+ManagedCallable::ManagedCallable(GCHandleIntPtr p_delegate_handle, void *p_trampoline, ObjectID p_object_id) :
+		delegate_handle(p_delegate_handle), trampoline(p_trampoline), object_id(p_object_id) {
 #ifdef GD_MONO_HOT_RELOAD
 	{
 		MutexLock lock(instances_mutex);

+ 3 - 1
modules/mono/managed_callable.h

@@ -40,6 +40,7 @@
 class ManagedCallable : public CallableCustom {
 	friend class CSharpLanguage;
 	GCHandleIntPtr delegate_handle;
+	void *trampoline = nullptr;
 	ObjectID object_id;
 
 #ifdef GD_MONO_HOT_RELOAD
@@ -58,6 +59,7 @@ public:
 	void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override;
 
 	_FORCE_INLINE_ GCHandleIntPtr get_delegate() const { return delegate_handle; }
+	_FORCE_INLINE_ void *get_trampoline() const { return trampoline; }
 
 	static bool compare_equal(const CallableCustom *p_a, const CallableCustom *p_b);
 	static bool compare_less(const CallableCustom *p_a, const CallableCustom *p_b);
@@ -67,7 +69,7 @@ public:
 
 	void release_delegate_handle();
 
-	ManagedCallable(GCHandleIntPtr p_delegate_handle, ObjectID p_object_id);
+	ManagedCallable(GCHandleIntPtr p_delegate_handle, void *p_trampoline, ObjectID p_object_id);
 	~ManagedCallable();
 };
 

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

@@ -74,7 +74,7 @@ struct ManagedCallbacks {
 	using Callback_ScriptManagerBridge_GetPropertyDefaultValues_Add = void(GD_CLR_STDCALL *)(CSharpScript *p_script, void *p_def_vals, int32_t p_count);
 
 	using FuncSignalAwaiter_SignalCallback = void(GD_CLR_STDCALL *)(GCHandleIntPtr, const Variant **, int32_t, bool *);
-	using FuncDelegateUtils_InvokeWithVariantArgs = void(GD_CLR_STDCALL *)(GCHandleIntPtr, const Variant **, uint32_t, const Variant *);
+	using FuncDelegateUtils_InvokeWithVariantArgs = void(GD_CLR_STDCALL *)(GCHandleIntPtr, void *, const Variant **, int32_t, const Variant *);
 	using FuncDelegateUtils_DelegateEquals = bool(GD_CLR_STDCALL *)(GCHandleIntPtr, GCHandleIntPtr);
 	using FuncDelegateUtils_TrySerializeDelegateWithGCHandle = bool(GD_CLR_STDCALL *)(GCHandleIntPtr, const Array *);
 	using FuncDelegateUtils_TryDeserializeDelegateWithGCHandle = bool(GD_CLR_STDCALL *)(const Array *, GCHandleIntPtr *);