mirror of
https://github.com/godotengine/godot.git
synced 2025-10-28 20:24:41 +00: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:
parent
f013315980
commit
282bd37e5c
19 changed files with 702 additions and 82 deletions
|
|
@ -188,14 +188,14 @@ namespace Godot.SourceGenerators
|
|||
if (godotClassMethods.Length > 0)
|
||||
{
|
||||
source.Append(" protected override bool InvokeGodotClassMethod(in godot_string_name method, ");
|
||||
source.Append("NativeVariantPtrArgs args, int argCount, out godot_variant ret)\n {\n");
|
||||
source.Append("NativeVariantPtrArgs args, out godot_variant ret)\n {\n");
|
||||
|
||||
foreach (var method in godotClassMethods)
|
||||
{
|
||||
GenerateMethodInvoker(method, source);
|
||||
}
|
||||
|
||||
source.Append(" return base.InvokeGodotClassMethod(method, args, argCount, out ret);\n");
|
||||
source.Append(" return base.InvokeGodotClassMethod(method, args, out ret);\n");
|
||||
|
||||
source.Append(" }\n");
|
||||
}
|
||||
|
|
@ -364,7 +364,7 @@ namespace Godot.SourceGenerators
|
|||
|
||||
source.Append(" if (method == MethodName.");
|
||||
source.Append(methodName);
|
||||
source.Append(" && argCount == ");
|
||||
source.Append(" && args.Count == ");
|
||||
source.Append(method.ParamTypes.Length);
|
||||
source.Append(") {\n");
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue