summaryrefslogtreecommitdiff
path: root/modules/mono/glue
diff options
context:
space:
mode:
authorIgnacio Roldán Etcheverry <ignalfonsore@gmail.com>2022-10-28 22:59:13 +0200
committerIgnacio Roldán Etcheverry <ignalfonsore@gmail.com>2022-10-30 01:24:15 +0200
commit282bd37e5c9dbc1bba4fe349fc7868643a54164b (patch)
tree6e3d5f7499ceb4b0d928764fd9b8da16548911ce /modules/mono/glue
parentf013315980ca13f4b66df29b5e32d3ecc310cb84 (diff)
C#: Remove need for reflection to invoking callable delegates
We aim to make the C# API reflection-free, mainly for concerns about performance, and to be able to target NativeAOT in refletion-free mode, which reduces the binary size. One of the main usages of reflection still left was the dynamic invokation of callable delegates, and for some time I wasn't sure I would find an alternative solution that I'd be happy with. The new solution uses trampoline functions to invoke the delegates: ``` static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret) { if (args.Count != 1) throw new ArgumentException($"Callable expected 1 arguments but received {args.Count}."); string res = ((Func<int, string>)delegateObj)( VariantConversionCallbacks.GetToManagedCallback<int>()(args[0]) ); ret = VariantConversionCallbacks.GetToVariantCallback<string>()(res); } Callable.CreateWithUnsafeTrampoline((int num) => "Foo" + num, &Trampoline); ``` Of course, this is too much boilerplate for user code. To improve this, the `Callable.From` methods were added. These are overloads that take `Action` and `Func` delegates, which covers the most common use cases: lambdas and method groups: ``` // Lambda Callable.From((int num) => "Foo" + num); // Method group string AppendNum(int num) => "Foo" + num; Callable.From(AppendNum); ``` Unfortunately, due to limitations in the C# language, implicit conversions from delegates to `Callable` are not supported. `Callable.From` does not support custom delegates. These should be uncommon, but the Godot C# API actually uses them for event signals. As such, the bindings generator was updated to generate trampoline functions for event signals. It was also optimized to use `Action` instead of a custom delegate for parameterless signals, which removes the need for the trampoline functions for those signals. The change to reflection-free invokation removes one of the last needs for `ConvertVariantToManagedObjectOfType`. The only remaining usage is from calling script constructors with parameters from the engine (`CreateManagedForGodotObjectScriptInstance`). Once that one is made reflection-free, `ConvertVariantToManagedObjectOfType` can be removed.
Diffstat (limited to 'modules/mono/glue')
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/CSharpInstanceBridge.cs5
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs2
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs2
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs74
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.generics.cs480
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs28
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs38
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs6
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeVariantPtrArgs.cs16
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantConversionCallbacks.cs1
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs2
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj1
-rw-r--r--modules/mono/glue/runtime_interop.cpp12
13 files changed, 612 insertions, 55 deletions
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/CSharpInstanceBridge.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/CSharpInstanceBridge.cs
index ae44f8f4ba..354212da1b 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/CSharpInstanceBridge.cs
+++ b/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)
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs
index 57240624bc..44ea8fc83d 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs
+++ b/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;
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs
index 092724a6b1..d83cf43eb2 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs
+++ b/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)
{
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs
index bdedd2e87a..f9309ca13e 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs
+++ b/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);
}
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.generics.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.generics.cs
new file mode 100644
index 0000000000..6c6a104019
--- /dev/null
+++ b/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);
+ }
+}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs
index 9b3969d453..d19e0c08f2 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs
+++ b/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)
{
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs
index 76b186cd15..649661ee06 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs
+++ b/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
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs
index 20ede9f0dd..088f4e7ecf 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs
+++ b/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);
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeVariantPtrArgs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeVariantPtrArgs.cs
index 422df74c23..d8c5d99cb8 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeVariantPtrArgs.cs
+++ b/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]
{
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantConversionCallbacks.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantConversionCallbacks.cs
index 9cde62c7c5..2707cb9bd2 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantConversionCallbacks.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantConversionCallbacks.cs
@@ -3,6 +3,7 @@ using System.Diagnostics.CodeAnalysis;
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
{
[SuppressMessage("ReSharper", "RedundantNameQualifier")]
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs
index 5cb678c280..60ee6eb6f4 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs
+++ b/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)
{
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj
index a63b668387..e3fb254f49 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj
+++ b/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" />
diff --git a/modules/mono/glue/runtime_interop.cpp b/modules/mono/glue/runtime_interop.cpp
index 2717b945f6..e20a88076a 100644
--- a/modules/mono/glue/runtime_interop.cpp
+++ b/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;