From 282bd37e5c9dbc1bba4fe349fc7868643a54164b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ignacio=20Rold=C3=A1n=20Etcheverry?= Date: Fri, 28 Oct 2022 22:59:13 +0200 Subject: 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)delegateObj)( VariantConversionCallbacks.GetToManagedCallback()(args[0]) ); ret = VariantConversionCallbacks.GetToVariantCallback()(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. --- .../GodotSharp/Core/Bridge/CSharpInstanceBridge.cs | 5 +- .../GodotSharp/Core/Bridge/ManagedCallbacks.cs | 2 +- .../GodotSharp/Core/Bridge/ScriptManagerBridge.cs | 2 +- .../glue/GodotSharp/GodotSharp/Core/Callable.cs | 74 +++- .../GodotSharp/Core/Callable.generics.cs | 480 +++++++++++++++++++++ .../GodotSharp/GodotSharp/Core/DelegateUtils.cs | 28 +- .../GodotSharp/Core/NativeInterop/Marshaling.cs | 38 +- .../GodotSharp/Core/NativeInterop/NativeFuncs.cs | 6 +- .../Core/NativeInterop/NativeVariantPtrArgs.cs | 16 +- .../NativeInterop/VariantConversionCallbacks.cs | 1 + .../glue/GodotSharp/GodotSharp/Core/Object.base.cs | 2 +- .../glue/GodotSharp/GodotSharp/GodotSharp.csproj | 1 + 12 files changed, 603 insertions(+), 52 deletions(-) create mode 100644 modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.generics.cs (limited to 'modules/mono/glue/GodotSharp') 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 SignalAwaiter_SignalCallback; - public delegate* unmanaged DelegateUtils_InvokeWithVariantArgs; + public delegate* unmanaged DelegateUtils_InvokeWithVariantArgs; public delegate* unmanaged DelegateUtils_DelegateEquals; public delegate* unmanaged DelegateUtils_TrySerializeDelegateWithGCHandle; public delegate* unmanaged 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 /// } /// /// - 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 _trampoline; /// /// Object that contains the method. @@ -48,10 +49,10 @@ namespace Godot public Delegate Delegate => _delegate; /// - /// Converts a to a . + /// Trampoline function pointer for dynamically invoking . /// - /// The delegate to convert. - public static implicit operator Callable(Delegate @delegate) => new Callable(@delegate); + public unsafe delegate* managed Trampoline + => _trampoline; /// /// Constructs a new for the method called @@ -59,22 +60,21 @@ namespace Godot /// /// Object that contains the method. /// Name of the method that will be called. - public Callable(Object target, StringName method) + public unsafe Callable(Object target, StringName method) { _target = target; _method = method; _delegate = null; + _trampoline = null; } - /// - /// Constructs a new for the given . - /// - /// Delegate method that will be called. - public Callable(Delegate @delegate) + private unsafe Callable(Delegate @delegate, + delegate* managed 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); } } + + /// + /// + /// Constructs a new using the + /// function pointer to dynamically invoke the given . + /// + /// + /// The parameters passed to the function are: + /// + /// + /// + /// delegateObj + /// The given , upcast to . + /// + /// + /// args + /// Array of arguments. + /// + /// + /// ret + /// Return value of type . + /// + /// + /// + /// The delegate should be downcast to a more specific delegate type before invoking. + /// + /// + /// + /// Usage example: + /// + /// + /// 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); + /// + /// + /// Delegate method that will be called. + /// Trampoline function pointer for invoking the delegate. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe Callable CreateWithUnsafeTrampoline(Delegate @delegate, + delegate* managed 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); + } + } + + /// + /// Constructs a new for the given . + /// + /// Action method that will be called. + 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); + } + + /// + public static unsafe Callable From( + Action action + ) + { + static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret) + { + ThrowIfArgCountMismatch(args, 1); + + ((Action)delegateObj)( + VariantConversionCallbacks.GetToManagedCallback()(args[0]) + ); + + ret = default; + } + + return CreateWithUnsafeTrampoline(action, &Trampoline); + } + + /// + public static unsafe Callable From( + Action action + ) + { + static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret) + { + ThrowIfArgCountMismatch(args, 2); + + ((Action)delegateObj)( + VariantConversionCallbacks.GetToManagedCallback()(args[0]), + VariantConversionCallbacks.GetToManagedCallback()(args[1]) + ); + + ret = default; + } + + return CreateWithUnsafeTrampoline(action, &Trampoline); + } + + /// + public static unsafe Callable From( + Action action + ) + { + static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret) + { + ThrowIfArgCountMismatch(args, 3); + + ((Action)delegateObj)( + VariantConversionCallbacks.GetToManagedCallback()(args[0]), + VariantConversionCallbacks.GetToManagedCallback()(args[1]), + VariantConversionCallbacks.GetToManagedCallback()(args[2]) + ); + + ret = default; + } + + return CreateWithUnsafeTrampoline(action, &Trampoline); + } + + /// + public static unsafe Callable From( + Action action + ) + { + static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret) + { + ThrowIfArgCountMismatch(args, 4); + + ((Action)delegateObj)( + VariantConversionCallbacks.GetToManagedCallback()(args[0]), + VariantConversionCallbacks.GetToManagedCallback()(args[1]), + VariantConversionCallbacks.GetToManagedCallback()(args[2]), + VariantConversionCallbacks.GetToManagedCallback()(args[3]) + ); + + ret = default; + } + + return CreateWithUnsafeTrampoline(action, &Trampoline); + } + + /// + public static unsafe Callable From( + Action action + ) + { + static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret) + { + ThrowIfArgCountMismatch(args, 5); + + ((Action)delegateObj)( + VariantConversionCallbacks.GetToManagedCallback()(args[0]), + VariantConversionCallbacks.GetToManagedCallback()(args[1]), + VariantConversionCallbacks.GetToManagedCallback()(args[2]), + VariantConversionCallbacks.GetToManagedCallback()(args[3]), + VariantConversionCallbacks.GetToManagedCallback()(args[4]) + ); + + ret = default; + } + + return CreateWithUnsafeTrampoline(action, &Trampoline); + } + + /// + public static unsafe Callable From( + Action action + ) + { + static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret) + { + ThrowIfArgCountMismatch(args, 6); + + ((Action)delegateObj)( + VariantConversionCallbacks.GetToManagedCallback()(args[0]), + VariantConversionCallbacks.GetToManagedCallback()(args[1]), + VariantConversionCallbacks.GetToManagedCallback()(args[2]), + VariantConversionCallbacks.GetToManagedCallback()(args[3]), + VariantConversionCallbacks.GetToManagedCallback()(args[4]), + VariantConversionCallbacks.GetToManagedCallback()(args[5]) + ); + + ret = default; + } + + return CreateWithUnsafeTrampoline(action, &Trampoline); + } + + /// + public static unsafe Callable From( + Action action + ) + { + static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret) + { + ThrowIfArgCountMismatch(args, 7); + + ((Action)delegateObj)( + VariantConversionCallbacks.GetToManagedCallback()(args[0]), + VariantConversionCallbacks.GetToManagedCallback()(args[1]), + VariantConversionCallbacks.GetToManagedCallback()(args[2]), + VariantConversionCallbacks.GetToManagedCallback()(args[3]), + VariantConversionCallbacks.GetToManagedCallback()(args[4]), + VariantConversionCallbacks.GetToManagedCallback()(args[5]), + VariantConversionCallbacks.GetToManagedCallback()(args[6]) + ); + + ret = default; + } + + return CreateWithUnsafeTrampoline(action, &Trampoline); + } + + /// + public static unsafe Callable From( + Action action + ) + { + static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret) + { + ThrowIfArgCountMismatch(args, 8); + + ((Action)delegateObj)( + VariantConversionCallbacks.GetToManagedCallback()(args[0]), + VariantConversionCallbacks.GetToManagedCallback()(args[1]), + VariantConversionCallbacks.GetToManagedCallback()(args[2]), + VariantConversionCallbacks.GetToManagedCallback()(args[3]), + VariantConversionCallbacks.GetToManagedCallback()(args[4]), + VariantConversionCallbacks.GetToManagedCallback()(args[5]), + VariantConversionCallbacks.GetToManagedCallback()(args[6]), + VariantConversionCallbacks.GetToManagedCallback()(args[7]) + ); + + ret = default; + } + + return CreateWithUnsafeTrampoline(action, &Trampoline); + } + + /// + public static unsafe Callable From( + Action action + ) + { + static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret) + { + ThrowIfArgCountMismatch(args, 9); + + ((Action)delegateObj)( + VariantConversionCallbacks.GetToManagedCallback()(args[0]), + VariantConversionCallbacks.GetToManagedCallback()(args[1]), + VariantConversionCallbacks.GetToManagedCallback()(args[2]), + VariantConversionCallbacks.GetToManagedCallback()(args[3]), + VariantConversionCallbacks.GetToManagedCallback()(args[4]), + VariantConversionCallbacks.GetToManagedCallback()(args[5]), + VariantConversionCallbacks.GetToManagedCallback()(args[6]), + VariantConversionCallbacks.GetToManagedCallback()(args[7]), + VariantConversionCallbacks.GetToManagedCallback()(args[8]) + ); + + ret = default; + } + + return CreateWithUnsafeTrampoline(action, &Trampoline); + } + + /// + /// Constructs a new for the given . + /// + /// Action method that will be called. + public static unsafe Callable From( + Func func + ) + { + static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret) + { + ThrowIfArgCountMismatch(args, 0); + + TResult res = ((Func)delegateObj)(); + + ret = VariantConversionCallbacks.GetToVariantCallback()(res); + } + + return CreateWithUnsafeTrampoline(func, &Trampoline); + } + + /// + public static unsafe Callable From( + Func func + ) + { + static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret) + { + ThrowIfArgCountMismatch(args, 1); + + TResult res = ((Func)delegateObj)( + VariantConversionCallbacks.GetToManagedCallback()(args[0]) + ); + + ret = VariantConversionCallbacks.GetToVariantCallback()(res); + } + + return CreateWithUnsafeTrampoline(func, &Trampoline); + } + + /// + public static unsafe Callable From( + Func func + ) + { + static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret) + { + ThrowIfArgCountMismatch(args, 2); + + TResult res = ((Func)delegateObj)( + VariantConversionCallbacks.GetToManagedCallback()(args[0]), + VariantConversionCallbacks.GetToManagedCallback()(args[1]) + ); + + ret = VariantConversionCallbacks.GetToVariantCallback()(res); + } + + return CreateWithUnsafeTrampoline(func, &Trampoline); + } + + /// + public static unsafe Callable From( + Func func + ) + { + static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret) + { + ThrowIfArgCountMismatch(args, 3); + + TResult res = ((Func)delegateObj)( + VariantConversionCallbacks.GetToManagedCallback()(args[0]), + VariantConversionCallbacks.GetToManagedCallback()(args[1]), + VariantConversionCallbacks.GetToManagedCallback()(args[2]) + ); + + ret = VariantConversionCallbacks.GetToVariantCallback()(res); + } + + return CreateWithUnsafeTrampoline(func, &Trampoline); + } + + /// + public static unsafe Callable From( + Func func + ) + { + static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret) + { + ThrowIfArgCountMismatch(args, 4); + + TResult res = ((Func)delegateObj)( + VariantConversionCallbacks.GetToManagedCallback()(args[0]), + VariantConversionCallbacks.GetToManagedCallback()(args[1]), + VariantConversionCallbacks.GetToManagedCallback()(args[2]), + VariantConversionCallbacks.GetToManagedCallback()(args[3]) + ); + + ret = VariantConversionCallbacks.GetToVariantCallback()(res); + } + + return CreateWithUnsafeTrampoline(func, &Trampoline); + } + + /// + public static unsafe Callable From( + Func func + ) + { + static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret) + { + ThrowIfArgCountMismatch(args, 5); + + TResult res = ((Func)delegateObj)( + VariantConversionCallbacks.GetToManagedCallback()(args[0]), + VariantConversionCallbacks.GetToManagedCallback()(args[1]), + VariantConversionCallbacks.GetToManagedCallback()(args[2]), + VariantConversionCallbacks.GetToManagedCallback()(args[3]), + VariantConversionCallbacks.GetToManagedCallback()(args[4]) + ); + + ret = VariantConversionCallbacks.GetToVariantCallback()(res); + } + + return CreateWithUnsafeTrampoline(func, &Trampoline); + } + + /// + public static unsafe Callable From( + Func func + ) + { + static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret) + { + ThrowIfArgCountMismatch(args, 6); + + TResult res = ((Func)delegateObj)( + VariantConversionCallbacks.GetToManagedCallback()(args[0]), + VariantConversionCallbacks.GetToManagedCallback()(args[1]), + VariantConversionCallbacks.GetToManagedCallback()(args[2]), + VariantConversionCallbacks.GetToManagedCallback()(args[3]), + VariantConversionCallbacks.GetToManagedCallback()(args[4]), + VariantConversionCallbacks.GetToManagedCallback()(args[5]) + ); + + ret = VariantConversionCallbacks.GetToVariantCallback()(res); + } + + return CreateWithUnsafeTrampoline(func, &Trampoline); + } + + /// + public static unsafe Callable From( + Func func + ) + { + static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret) + { + ThrowIfArgCountMismatch(args, 7); + + TResult res = ((Func)delegateObj)( + VariantConversionCallbacks.GetToManagedCallback()(args[0]), + VariantConversionCallbacks.GetToManagedCallback()(args[1]), + VariantConversionCallbacks.GetToManagedCallback()(args[2]), + VariantConversionCallbacks.GetToManagedCallback()(args[3]), + VariantConversionCallbacks.GetToManagedCallback()(args[4]), + VariantConversionCallbacks.GetToManagedCallback()(args[5]), + VariantConversionCallbacks.GetToManagedCallback()(args[6]) + ); + + ret = VariantConversionCallbacks.GetToVariantCallback()(res); + } + + return CreateWithUnsafeTrampoline(func, &Trampoline); + } + + /// + public static unsafe Callable From( + Func func + ) + { + static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret) + { + ThrowIfArgCountMismatch(args, 8); + + TResult res = ((Func)delegateObj)( + VariantConversionCallbacks.GetToManagedCallback()(args[0]), + VariantConversionCallbacks.GetToManagedCallback()(args[1]), + VariantConversionCallbacks.GetToManagedCallback()(args[2]), + VariantConversionCallbacks.GetToManagedCallback()(args[3]), + VariantConversionCallbacks.GetToManagedCallback()(args[4]), + VariantConversionCallbacks.GetToManagedCallback()(args[5]), + VariantConversionCallbacks.GetToManagedCallback()(args[6]), + VariantConversionCallbacks.GetToManagedCallback()(args[7]) + ); + + ret = VariantConversionCallbacks.GetToVariantCallback()(res); + } + + return CreateWithUnsafeTrampoline(func, &Trampoline); + } + + /// + public static unsafe Callable From( + Func func + ) + { + static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret) + { + ThrowIfArgCountMismatch(args, 9); + + TResult res = ((Func)delegateObj)( + VariantConversionCallbacks.GetToManagedCallback()(args[0]), + VariantConversionCallbacks.GetToManagedCallback()(args[1]), + VariantConversionCallbacks.GetToManagedCallback()(args[2]), + VariantConversionCallbacks.GetToManagedCallback()(args[3]), + VariantConversionCallbacks.GetToManagedCallback()(args[4]), + VariantConversionCallbacks.GetToManagedCallback()(args[5]), + VariantConversionCallbacks.GetToManagedCallback()(args[6]), + VariantConversionCallbacks.GetToManagedCallback()(args[7]), + VariantConversionCallbacks.GetToManagedCallback()(args[8]) + ); + + ret = VariantConversionCallbacks.GetToVariantCallback()(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)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)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; + } + + /// + /// Returns the number of arguments. + /// + 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. 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 @@ + -- cgit v1.2.3