mirror of
https://github.com/godotengine/godot.git
synced 2026-01-05 06:11:29 +03:00
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.
This commit is contained in:
@@ -92,7 +92,7 @@ void ManagedCallable::call(const Variant **p_arguments, int p_argcount, Variant
|
||||
ERR_FAIL_COND(delegate_handle.value == nullptr);
|
||||
|
||||
GDMonoCache::managed_callbacks.DelegateUtils_InvokeWithVariantArgs(
|
||||
delegate_handle, p_arguments, p_argcount, &r_return_value);
|
||||
delegate_handle, trampoline, p_arguments, p_argcount, &r_return_value);
|
||||
|
||||
r_call_error.error = Callable::CallError::CALL_OK;
|
||||
}
|
||||
@@ -106,7 +106,8 @@ void ManagedCallable::release_delegate_handle() {
|
||||
|
||||
// Why you do this clang-format...
|
||||
/* clang-format off */
|
||||
ManagedCallable::ManagedCallable(GCHandleIntPtr p_delegate_handle, ObjectID p_object_id) : delegate_handle(p_delegate_handle), object_id(p_object_id) {
|
||||
ManagedCallable::ManagedCallable(GCHandleIntPtr p_delegate_handle, void *p_trampoline, ObjectID p_object_id) :
|
||||
delegate_handle(p_delegate_handle), trampoline(p_trampoline), object_id(p_object_id) {
|
||||
#ifdef GD_MONO_HOT_RELOAD
|
||||
{
|
||||
MutexLock lock(instances_mutex);
|
||||
|
||||
Reference in New Issue
Block a user