My understanding may be out of date, but the C# support was created before GDExtension existed. The team has been working hard on porting it over to GDExtension. Once they are done, it should be much more performant, and they will finally be able to ship only one version of the editor. I believe the original C# bindings do a lot of unnecessary marshaling at the ABI.
With GDExtension, the core builtin types like `Vector3` are passed by value. Avoid unnecessarily wrapping them in Variant, a specialized tagged union, where you can. You can see the documentation here; you have direct access to the float fields: https://gdzig.github.io/gdzig/#gdzig.builtin.vector3.Vector3
Engine objects are passed around as opaque pointers into engine managed memory. The memory for your custom types is managed by you. You allocate the engine object and essentially attach userdata to it, tagged with a unique token provided to your extension. You can see the header functions that manage this: https://github.com/godotengine/godot/blob/e67074d0abe9b69f3d...
But, this is how the lifetimes for the objects gets slightly hairy (for us, the people creating the language bindings). Our goal with the Zig bindings is to make the allocations and lifetimes extremely obvious, a la Zig's philosophy of "No hidden memory allocations". It is proving somewhat challenging, but I think we can get there.
There's still a lot of surprising or unintuitive allocations that can happen when calling into Godot, but we hope to expose those. My current idea is to accept a `GodotAllocator` on those functions (and do nothing with it; just use it to signal the allocation). You can read the code for the `GodotAllocator` implementation: https://github.com/gdzig/gdzig/blob/master/gdzig/heap.zig#L8...
If we succeed, I think Zig can become the best language to write highly performant Godot extensions in.
I think you are talking about dispatch of virtual methods, which is still a thing, but the performance cost can be somewhat mitigated.
the names of the methods are interned strings (called `StringName`). a naive implementation will allocate the `StringName`, but you can avoid the allocation with a static lifetime string. we expose a helper for comptime strings in Zig[0].
then, extension classes need to provide callback(s)[1] on registration that lookup and call the virtual methods. as far as I know, the lookup happens once, and the engine stores the function pointer, but I haven't confirmed this yet. it would be unfortunate if not.
at least right now in GDZig, though this might change, we use Zig comptime to generate a unique function pointer for every virtual function on a class[2]. this implementation was by the original `godot-zig` author (we are a fork). in theory we could inline the underlying function here with `@call(.always_inline)` and avoid an extra layer indirection, among other optimizations. it is an area I am still figuring out the best approach for
virtual methods are only ever necessary when interfacing with the editor, GDScript, or other extensions. don't pay the cost of a virtual method when you can just use a plain method call without the overhead.
With GDExtension, the core builtin types like `Vector3` are passed by value. Avoid unnecessarily wrapping them in Variant, a specialized tagged union, where you can. You can see the documentation here; you have direct access to the float fields: https://gdzig.github.io/gdzig/#gdzig.builtin.vector3.Vector3
Engine objects are passed around as opaque pointers into engine managed memory. The memory for your custom types is managed by you. You allocate the engine object and essentially attach userdata to it, tagged with a unique token provided to your extension. You can see the header functions that manage this: https://github.com/godotengine/godot/blob/e67074d0abe9b69f3d...
But, this is how the lifetimes for the objects gets slightly hairy (for us, the people creating the language bindings). Our goal with the Zig bindings is to make the allocations and lifetimes extremely obvious, a la Zig's philosophy of "No hidden memory allocations". It is proving somewhat challenging, but I think we can get there.
There's still a lot of surprising or unintuitive allocations that can happen when calling into Godot, but we hope to expose those. My current idea is to accept a `GodotAllocator` on those functions (and do nothing with it; just use it to signal the allocation). You can read the code for the `GodotAllocator` implementation: https://github.com/gdzig/gdzig/blob/master/gdzig/heap.zig#L8...
If we succeed, I think Zig can become the best language to write highly performant Godot extensions in.