-
-
Notifications
You must be signed in to change notification settings - Fork 3.3k
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
Add example of GDExtension written pure C #8751
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wow! George, this is truly monumental work! Great job :-)
File structure | ||
-------------- | ||
|
||
To organize our files, we're gonna split in mainly two folders: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To organize our files, we're gonna split in mainly two folders: | |
To organize our files, we're gonna split into mainly two folders: |
|
||
Lastly, there's another source of information we need to refer to, which is the JSON | ||
file with the Godot API reference. This file won't be used by the code directly, we | ||
will only use to extract some information manually. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will only use to extract some information manually. | |
will only use it to extract some information manually. |
|
||
The following ``SConstruct`` file is a simple one that will build your extension | ||
to the current platform that you are using, be it Linux, macOS, or Windows. This | ||
will be a non-optimized build for debugging purposes. It also assumes a 64-bits |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will be a non-optimized build for debugging purposes. It also assumes a 64-bits | |
will be a non-optimized build for debugging purposes. It also assumes a 64-bit |
Initializing the extension | ||
-------------------------- | ||
|
||
The first bit of code will be responsible to initialize the extension. This is |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The first bit of code will be responsible to initialize the extension. This is | |
The first bit of code will be responsible for initializing the extension. This is |
needed. It also sets the initialization level which varies per extension. Since | ||
we plan to add a custom node, the ``SCENE`` level is enough. | ||
|
||
We will fill the ``initialize`` function later to register our custom class. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like this would be easier to understand with the full name of the function in this example (as opposed to the field on the struct):
We will fill the ``initialize`` function later to register our custom class. | |
We will fill the ``initialize_gdexample_module`` function later to register our custom class. |
Variant can cause crashes. | ||
|
||
Then it proceeds to extract the argument using the constructor we setup before. | ||
The one with no arguments instead set the return value after calling the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The one with no arguments instead set the return value after calling the | |
The one with no arguments instead sets the return value after calling the |
} | ||
|
||
Since this function is already being called by the initialization process, we | ||
can stop here. This function is much straightforward after we created all the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can stop here. This function is much straightforward after we created all the | |
can stop here. This function is much more straightforward after we created all the |
bind_property("GDExample", "speed", GDEXTENSION_VARIANT_TYPE_FLOAT, "get_speed", "set_speed"); | ||
} | ||
|
||
If you build the extension with ``SCons``, you'll see in the Godot editor the new property shown |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems odd upper case, because the command would be all lower case.
If you build the extension with ``SCons``, you'll see in the Godot editor the new property shown | |
If you build the extension with ``scons``, you'll see in the Godot editor the new property shown |
For now it will do nothing but update the private field we created. We'll come | ||
back to this after the method is properly bound. | ||
|
||
Virtual methods are a bit different for the regular bindings. Instead of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Virtual methods are a bit different for the regular bindings. Instead of | |
Virtual methods are a bit different from the regular bindings. Instead of |
calling the functions (such as wrong arguments). For the sake of simplicity | ||
we're not gonna handle that here. | ||
|
||
At then end we need to destruct the Variants we created. While technically the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At then end we need to destruct the Variants we created. While technically the | |
At the end we need to destruct the Variants we created. While technically the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will try do a grammar and language review in the next few days, but looks very interesting!
#include "init.h" | ||
|
||
void initialize_gdexample_module(void *p_userdata, GDExtensionInitializationLevel p_level) | ||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd suggest following the code style for this code as well, with brackets on the declaration
class, but it makes clearer to separate the concerns and let our class register | ||
its own metadata. | ||
|
||
The ``object`` field is necessary because our class will inherit a Godot class. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The ``object`` field is necessary because our class will inherit a Godot class. | |
The ``object`` field is necessary because our class will inherit a Godot class. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Left some minor comments on grammar and suggestions for improvements. Phenomenal work on this!
|
||
#endif // INIT_H | ||
|
||
The functions declared here have the signature expected by the GDExtension API. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The functions declared here have the signature expected by the GDExtension API. | |
The functions declared here has the expected signature by the GDExtension API. |
In order to make an actual node, first we'll create a C struct to hold data and | ||
functions that will act as methods. The plan is to make this a custom node that | ||
inherits from :ref:`Sprite2D <class_Sprite2D>`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In order to make an actual node, first we'll create a C struct to hold data and | |
functions that will act as methods. The plan is to make this a custom node that | |
inherits from :ref:`Sprite2D <class_Sprite2D>`. | |
In order to make an actual Node, first we'll create a C struct to hold data and | |
functions that will act as methods. The plan is to make this a custom Node that | |
inherits from :ref:`Sprite2D <class_Sprite2D>`. |
The only things special here are the ``object`` field, which holds a pointer to | ||
the Godot object, and the ``gdexample_bind_methods`` function, which will | ||
register the metadata of our custom class (properties, methods, and signals). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The only things special here are the ``object`` field, which holds a pointer to | |
the Godot object, and the ``gdexample_bind_methods`` function, which will | |
register the metadata of our custom class (properties, methods, and signals). | |
Noteworthy here is the ``object`` field, which holds a pointer to | |
the Godot object, and the ``gdexample_bind_methods`` function, which will | |
register the metadata of our custom class (properties, methods, and signals). |
reference to such objects when calling methods on the parent class, for | ||
instance. | ||
|
||
Let's create the source counterpart of this header. Make the file |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's create the source counterpart of this header. Make the file | |
Let's create the source counterpart of this header. Create the file |
Very boring stuff as for now we don't have anything to do with those functions, so | ||
they'll stay empty for a while. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very boring stuff as for now we don't have anything to do with those functions, so | |
they'll stay empty for a while. | |
As we don't have anything to do with those functions yet, they'll stay empty | |
for a while. | |
In order to make our node do stuff, we'll need to call Godot methods. Not only | ||
the GDExtension API functions as we've being doing so far, but actual engine | ||
methods, as we would do with scripting. This naturally require some extra setup. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In order to make our node do stuff, we'll need to call Godot methods. Not only | |
the GDExtension API functions as we've being doing so far, but actual engine | |
methods, as we would do with scripting. This naturally require some extra setup. | |
In order to make our Node do stuff, we'll need to call Godot methods. Not only | |
the GDExtension API functions as we've being doing so far, but actual engine | |
methods, as we would do with scripting. This naturally requires some extra setup. |
... | ||
} | ||
|
||
The only special thing here is the ``Vector2`` constructor, which we request the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The only special thing here is the ``Vector2`` constructor, which we request the | |
The only noteworthy part here is the ``Vector2`` constructor, in which we request the |
name. This index can be retrieved from the ``extension_api.json`` file. Note we | ||
also need a new local helper to get it. | ||
|
||
Note that we don't get anything for the methods struct here. This is because |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that we don't get anything for the methods struct here. This is because | |
Be aware that we don't get anything for the methods struct here. This is because |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of Note
since that was just used for the prior sentence
destruct_property(&args_info[0]); | ||
} | ||
|
||
This one is very similar to the function to bind methods. The main difference is |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one is very similar to the function to bind methods. The main difference is | |
This implementation is very similar to the function to bind methods. The main difference is |
destructors.variant_destroy(&ret); | ||
} | ||
|
||
This helper has some boilerplate but is quite straightforward. It sets up the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This helper has some boilerplate but is quite straightforward. It sets up the | |
This helper function has some boilerplate code but is quite straightforward. It sets up the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
quite straightforward
should also be avoided as it sounds like it's trivial to understand. Perhaps just end the sentence after boilerplate
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've only skimmed it so far, but this looks amazing. Was just about to try to work through how to use these APIs on my own (based on what I think godot-cpp is doing), glad it took me long enough to get to it that I noticed this PR first. 😄
An overall wish I would have if it's feasible is a TOC (and/or multiple pages) so it's easier to understand the scope of what's being provided and poke around.
The complete version is more involved. First, it creates ``String``s and | ||
``StringName``s for the needed fields, by allocating memory and calling their |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the s
is making rendering mess up? Looks like this on my local build (cherry-picked on top of a63a33a in master
):
... | ||
} | ||
|
||
Here we create ``StringName``s for the class and method we want to get, then use |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another ``s
rendering weird.
BTW, thanks for all the reviews. I did kind of forget this during weekend (on purpose to rest) but in the next few days I'll address all the issues pointed out. |
void gdexample_call_virtual_with_data(GDExtensionClassInstancePtr p_instance, GDExtensionConstStringNamePtr p_name, void *p_virtual_call_userdata, const GDExtensionConstTypePtr *p_args, GDExtensionTypePtr r_ret) | ||
{ | ||
// If it is the "_process" method, call it with a helper. | ||
if (is_string_name_equal(p_name, "_process")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is re-creating the StringName "_process"
every process frame, isn't it? It would be nice to avoid that here.
Edit: Comparing p_virtual_call_userdata
to &gdexample_process
would be fast.
Closing as Anyone feel free to take this, apply fixes for the feedback and open a new PR :) |
I'd like to salvage this. I'll make a new PR soon. |
PR #10403 is my remake of this one |
This is the same code as the C++ example, but written without any bindings using the GDExtension API directly. It is somewhat long because it needs to touch a lot of aspects and create boilerplate that would normally come from the bindings.
I also want to point out that the GDExtension API is not meant to be used directly like this, so it is more of a reference for people who wants to create their own bindings.
Production edit: closes godotengine/godot-roadmap#77