-
-
Notifications
You must be signed in to change notification settings - Fork 21.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Rework GDExtension interface from a struct to loading function pointers #76406
Conversation
658a593
to
db3246c
Compare
This is looking really good David, One other thing we discussed that we need to take along here is documenting what these functions do and basically create a document about the GDExtension C API. One suggestion I had was to add this documentation alongside the function definitions in Alternatively we can make this work the same way all documentation works in Godot and look into feeding the function definitions into our documentation XML. Especially since you've implemented a function register system and we thus have an array with all function pointers, we can use this to generate our documentation as well. |
Thanks!
Yep! That's one of the "clean up" things I was planning to do once I was sure that folks were OK with the overall approach.
That's a really interesting idea! It'd certainly be nicer to point to well formatted docs online, as opposed to just comments in a header file. I'll look into it. |
This indeed looks good! 🎉 And thanks for getting the FWIW,
This is perfectly OK IMO, lots of stuff works like this. |
00bb5bf
to
cd75ea6
Compare
I've been struggling with how to move forward on documentation. I really like the idea of generating XML that gets transformed into something nice for the docs. OpenGL also works like this - the "source" of the API is XML, and then the HTML docs are generated from that. This seems like a good fit for an API that is based on loading function pointers, because you're actually documenting sort of "imaginary functions". My first thought was that we could generate a However, looking at Godot's So, I'm thinking it might be best to:
@BastiaanOlij What do you think? |
cd75ea6
to
0aa52e1
Compare
My latest push shows a proposed way to include the docs in the gdextension_interface.h header: Does this look like an acceptable way to handle it? If so, I can go through and add docs like this to all of the interface functions! |
@dsnopek looks good! Indeed if it's too much work to get the documentation working through the XML approach it shouldn't be something we hold this back on. This really is only interesting to people who are building the bindings themselves and they would be using the header file. It may be worth talking to some of the guys who know more about the in build documentation system like George (vnen) or Hugo (Calinou) if you haven't already. But I'm happy if we continue with having the descriptions in the header file. |
The docs aspect looks good to me; most IDEs will display those on hover. Of course, there should be a corresponding manual page to tell you to check the interface header to know those details in the first place 🙂 |
Great, thanks!! |
0aa52e1
to
a59accb
Compare
I've finished adding documentation for all the registered GDExtension interface functions. There's no other changes I was planning on this PR, so I'm taking this one out of draft - it's ready for review! (I've still got some changes I want to make on the companion PR for godot-cpp, so I'm leaving that one as a draft for now. Of course, they will need to be merged at the same time so that tests pass on both ends. Updates coming soon on that one.) |
a59accb
to
9d58de0
Compare
Discussed at the GDExtension meeting, and approved! Needs to be merged at the same time as godotengine/godot-cpp#1095 so that the tests pass (they are only failing here currently because the godot-cpp changes are required). |
9d58de0
to
67382d4
Compare
This PR conflicts with PR #35813, so one of them would need to be rebased on the other. To be fair to @touilleMan whose PR was approved at an earlier GDExtension meeting than mine, I've rebased this PR on his. |
2b79872
to
28d30e4
Compare
Now that PR #35813 has been merged, I've rebased on master - this should be ready to merge! |
Seems like the godot-cpp change needs to be merged first, then this one rebased. The best would be for you to do the merges when you're around, so you can merge the godot-cpp one, rebase this PR to force a new build against the new godot-cpp, and merge it once ready. |
28d30e4
to
9b9482d
Compare
Thanks! |
284: Compatibility with 4.1 GDExtension API r=Bromeon a=Bromeon This pull request migrates gdext to the **major GDExtension API change**, as introduced in godotengine/godot#76406. It also adopts to the new "uninitialized pointer" types, which were added in godotengine/godot#35813. Doing so renders the CI green again, unblocking other PRs that have been broken by the upstream changes. --- # Major GDExtension API update TLDR of Godot's changes: instead of one giant `GDExtensionInterface` struct with all the function pointers as data members, the C API now offers only a single function pointer `get_proc_address`. This one is passed to the entry point, in place of a pointer to the interface. With `get_proc_address`, it is possible to retrieve concrete API functions. In C++, this is done by looking up via name and casting the result to the correct function pointer type: ```cpp auto variant_call = (GDExtensionInterfaceVariantCall) get_proc_address("variant_call"); variant_call(...); ``` On the Rust side, I incorporated these changes by only modifying the FFI layer (`godot-ffi` crate), so the rest of the project is mostly unaffected. This is done by generating a `GDExtensionInterface` struct very similarly to how it existed before, in `godot-codegen`. As input, we parse the `gdextension_interface.h` header's documentation metadata. This works well so far, but we'll need to make sure with Godot devs that the doc metadata doesn't suddenly change format. --- # Uninitialized pointer types Certain FFI functions construct objects "into an uninitialized pointer" (placement new in C++). There used to be quite some confusion regarding _which_ functions do that. As a result, the C API introduced new types such as `GDExtensionUninitializedStringPtr`, which represent the uninitialized counterpart to `GDExtensionStringPtr`. These typedefs are declared as `void*` in the C API, but we turn them into strongly typed opaque pointers by post-processing the C header. As such, it is impossible to accidentally mix initialized and uninitialized pointer types. I also added a trait `AsUninit` which allows explicit conversion between the two. This may not be necessary in the future, but is helpful until we are sure about correct usage everywhere. I marked some places that may need another look as `// TODO(uninit)`. --- # Compatibility between Godot 4.0.x and 4.1+ Due to the API changes in GDExtension, extensions compiled under Godot 4.0.x are by default **not compatible** with Godot 4.1+ (which includes current `master` versions of Godot, so also our nightly CI builds). From now on, the `.gdextension` interface must contain a new property `compatibility_minimum`: ```toml [configuration] entry_symbol = "gdext_rust_init" compatibility_minimum = 4.0 [libraries] linux.debug.x86_64 = "res://../../../target/debug/libdodge_the_creeps.so" ... ``` This value must be set to `4.0` if you compile against the old GDExtension API (current gdext uses 4.0.3 by default). Set it to `4.1` if you use a Godot development version (Cargo feature `custom-godot`). If you do this wrong, gdext will abort initialization and print a descriptive error message. ## Compat bridge Since breaking changes can be very disruptive, I built a bridging layer that allows to use existing compiled extensions under Godot v4.1 (or current development versions). Because the Rust code relies on certain features only available in the newer C header (e.g. uninitialized pointers), such changes were backported via dynamic post-processing of `gdextension_interface.h`. Therefore, you don't need to mess around with `custom-godot`, just keep using gdext as-is and set `compatibility_minimum = 4.0` to run it under newer engine versions. Developing against a specific GDExtension API is still possible via patch, the prebuilt artifacts have been updated for all 4.0.x versions. For example, to use version `4.0`: ```toml [patch."https://github.com/godot-rust/godot4-prebuilt"] godot4-prebuilt = { git = "https://github.com//godot-rust/godot4-prebuilt", branch = "4.0"} ``` Once Godot 4.1.0 is released, we will likely make that the default version used by gdext, but I plan to maintain the compat layer as long as this is possible with reasonable effort. A new CI setup verifies that the integration tests run against 4.0, 4.0.1, 4.0.2, 4.0.3 and nightly Godot versions, at least for Linux. This will become a challenge as soon as there are breaking API changes (and there is already one upcoming in `Basis::looking_at()`). --- # Other changes There is now an experimental `godot::sys::GdextBuild` struct, which provides informations about static (compiled-against) and runtime (loaded-by) Godot versions. This may be extended by other build/runtime metadata and will likely be promoted to an official API in the `godot::init` module. Several memory leaks that came up during the change to uninitialized pointers were addressed. In many places, we now use `from_sys_init()` instead of `from_sys_init_default()`, avoiding the need to construct throwaway default instances. In addition to more CI jobs for integration tests, there are now also 2 more linux-memcheck ones, to cover both 4.0.3 and nightly. This is mostly to have extra confidence during the migration phase and may be removed later. Godot 4.0.3 is now the default version used by gdext. Co-authored-by: Jan Haller <[email protected]>
At the GDExtension meeting on 2023-04-14, we were discussing (among other things) how to allow us to add new methods to the
GDExtensionInterface
struct without breaking the ABI. (There is at least one GDExtension bug that requires a new method to be added in order to fix it.)We discussed a few possibilities, including just adding new pointers to the end of the struct, or adding a
void *next
pointer to the end of the struct (so that could point to a new struct with new methods, similar to how we did it in GDNative).@vnen had the idea of having a "low-level ClassDB" where we could register these functions, rather than putting them in a big struct. And @BastiaanOlij brought up how many APIs (like OpenGL, OpenXR and Vulkan) do exactly that, where you're given a
void *GetProcAddress(const char *p_name)
function and then load each of the function pointers individually.So, this PR is attempting to implement that!
Here's an overview:
GDExtensionInterface
struct no longer exists ingdextension_interface.h
, and is replaced by a bunch of function pointertypedef
s, and the initialization function in each GDExtension is now passed aGDExtensionInterfaceGetProcAddress
function pointer insteadcompatibility_minimum
property (came from a discussion on RocketChat with @mhilbrunner in the gdextension channel) to denote the minimum Godot version that a given GDExtension needscomptability_minimum
or where it's set to4.0
will still get passed the "legacy" structure, so GDExtensions made for Godot 4.0 should still work with Godot 4.1+, but extensions made for Godot 4.1+ won't work with Godot 4.0 (Its on the agenda for the next GDExtension meeting to discuss if this sort of forward-but-not-backward compatibility is OK)This is currently a draft for discussion! If the overall approach looks good, there's a bunch of little bits of clean-up I'd like to do (in addition to whatever comes up in review)UPDATE: No longer a draft!