diff options
author | Ignacio Roldán Etcheverry <ignalfonsore@gmail.com> | 2022-10-28 22:59:13 +0200 |
---|---|---|
committer | Ignacio Roldán Etcheverry <ignalfonsore@gmail.com> | 2022-10-30 01:24:15 +0200 |
commit | 282bd37e5c9dbc1bba4fe349fc7868643a54164b (patch) | |
tree | 6e3d5f7499ceb4b0d928764fd9b8da16548911ce /modules/mono/glue | |
parent | f013315980ca13f4b66df29b5e32d3ecc310cb84 (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')
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($"Callable expected {1} arguments but received {args.Count}."); + /// + /// TResult res = ((Func<int, string>)delegateObj)( + /// VariantConversionCallbacks.GetToManagedCallback<int>()(args[0]) + /// ); + /// + /// ret = VariantConversionCallbacks.GetToVariantCallback<string>()(res); + /// } + /// + /// var callable = Callable.CreateWithUnsafeTrampoline((int num) => "foo" + num.ToString(), &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; |