Skip to content
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

Merged
merged 1 commit into from
May 16, 2023

Conversation

dsnopek
Copy link
Contributor

@dsnopek dsnopek commented Apr 24, 2023

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:

  • The GDExtensionInterface struct no longer exists in gdextension_interface.h, and is replaced by a bunch of function pointer typedefs, and the initialization function in each GDExtension is now passed a GDExtensionInterfaceGetProcAddress function pointer instead
  • In the XXX.gdextension file, you can set a compatibility_minimum property (came from a discussion on RocketChat with @mhilbrunner in the gdextension channel) to denote the minimum Godot version that a given GDExtension needs
  • Any GDExtension without comptability_minimum or where it's set to 4.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)
  • The companion PR for godot-cpp (Update to load function pointers for GDExtension interface godot-cpp#1095) also does some work to ensure that a newer GDExtension that uses this new interface won't be loaded by Godot 4.0, with a nice message rather than a crash

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!

@BastiaanOlij
Copy link
Contributor

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 gdextension_interface.h because that is the file that ends up being distributed to any extension being created.

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.
This may mean that we also need to add some meta data here such as the function name and parameters. A bit of smart macro work could automate that for us.

@dsnopek
Copy link
Contributor Author

dsnopek commented Apr 25, 2023

Thanks!

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.

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.

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.

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.

@mhilbrunner
Copy link
Member

mhilbrunner commented Apr 26, 2023

This indeed looks good! 🎉 And thanks for getting the compatibility_minimum in!

FWIW,

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 perfectly OK IMO, lots of stuff works like this.

@dsnopek dsnopek force-pushed the gdextension-interface branch 2 times, most recently from 00bb5bf to cd75ea6 Compare May 1, 2023 21:59
@dsnopek
Copy link
Contributor Author

dsnopek commented May 2, 2023

One suggestion I had was to add this documentation alongside the function definitions in gdextension_interface.h because that is the file that ends up being distributed to any extension being created.

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.

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 @GDExtensionInterface.xml from the registered interface functions, and list each of the functions with <method name="..."> (where 'name' is the name it's registered with, that you need to pass to the "GetProcAddress()" function) in the same XML format that we're using for rest of the class reference.

However, looking at Godot's make_rst.py and the godot-docs repo, I think this would require a ton of work to do well, and without creating some confusion. First of all, the types on the parameters would need to refer to C types, not normal Godot types. And we'd need a way to provide definitions for those C types too, and there isn't a nice way to make that all self-contained in a single @GDExtensionInterface.xml. All the while keeping this stuff separate from the other docs so we don't confuse users into thinking that these functions can be called from GDScript or C#.

So, I'm thinking it might be best to:

  1. Just put the documentation into the header file. :-) This will be unstructured, but maybe we could use some adhoc structure that we could possibly parse later, like @param, @name, @return etc?
  2. Punt on the documentation for now, with the plan to come back to this later, and then do all the work necessary to setup a system that generates XML from the function registrations and transforms that into something nice for the docs?

@BastiaanOlij What do you think?

@dsnopek dsnopek force-pushed the gdextension-interface branch from cd75ea6 to 0aa52e1 Compare May 3, 2023 22:17
@dsnopek
Copy link
Contributor Author

dsnopek commented May 3, 2023

My latest push shows a proposed way to include the docs in the gdextension_interface.h header:

https://github.com/godotengine/godot/compare/cd75ea62fcf067c865047beb2396a6529c61c442..0aa52e104d55ba056657746e1bc28d9c0040eb50

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!

@BastiaanOlij
Copy link
Contributor

@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.

@Calinou
Copy link
Member

Calinou commented May 3, 2023

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 🙂

@dsnopek
Copy link
Contributor Author

dsnopek commented May 4, 2023

Great, thanks!!

@dsnopek dsnopek force-pushed the gdextension-interface branch from 0aa52e1 to a59accb Compare May 9, 2023 22:21
@dsnopek dsnopek marked this pull request as ready for review May 9, 2023 22:24
@dsnopek dsnopek requested a review from a team as a code owner May 9, 2023 22:24
@dsnopek
Copy link
Contributor Author

dsnopek commented May 9, 2023

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.)

@dsnopek dsnopek changed the title [Draft] Rework GDExtension interface from a struct to loading function pointers Rework GDExtension interface from a struct to loading function pointers May 9, 2023
@dsnopek dsnopek force-pushed the gdextension-interface branch from a59accb to 9d58de0 Compare May 9, 2023 23:13
@dsnopek
Copy link
Contributor Author

dsnopek commented May 13, 2023

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).

@dsnopek dsnopek force-pushed the gdextension-interface branch from 9d58de0 to 67382d4 Compare May 13, 2023 22:27
@dsnopek dsnopek requested a review from a team as a code owner May 13, 2023 22:27
@dsnopek
Copy link
Contributor Author

dsnopek commented May 13, 2023

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.

@dsnopek dsnopek force-pushed the gdextension-interface branch 2 times, most recently from 2b79872 to 28d30e4 Compare May 15, 2023 23:01
@dsnopek
Copy link
Contributor Author

dsnopek commented May 15, 2023

Now that PR #35813 has been merged, I've rebased on master - this should be ready to merge!

@akien-mga
Copy link
Member

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.

@dsnopek dsnopek force-pushed the gdextension-interface branch from 28d30e4 to 9b9482d Compare May 16, 2023 15:28
@akien-mga akien-mga merged commit a8453cb into godotengine:master May 16, 2023
@akien-mga
Copy link
Member

Thanks!

@akien-mga akien-mga modified the milestones: 4.x, 4.1 May 16, 2023
bors bot added a commit to godot-rust/gdext that referenced this pull request May 24, 2023
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]>
@dsnopek dsnopek deleted the gdextension-interface branch July 22, 2024 15:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants