diff --git a/tutorials/scripting/gdextension/gdextension_c_example.rst b/tutorials/scripting/gdextension/gdextension_c_example.rst new file mode 100644 index 00000000000..1f1ef7f17ab --- /dev/null +++ b/tutorials/scripting/gdextension/gdextension_c_example.rst @@ -0,0 +1,2166 @@ +.. _doc_gdextension_c_example: + +GDExtension C example +===================== + +Introduction +------------ + +This is a simple example on how to work with GDExtension directly with C code. +Note that the API is not meant to be used directly, so this will definitely be +quite verbose and require a lot of steps even for a small example. However, it +serves as a reference for creating bindings for a different language. It is +still possible to use the API directly if you prefer, which might be convenient +when only binding a third-party library. + +In this example we will create a custom node that moves a sprite on the screen +based on the user's parameters. While very simple, it serves to show how to do +some of the things with GDExtension, like registering custom classes with +methods, properties, and signals. It gives an insight on the GDExtension API. + +Setting up the project +---------------------- + +There are a few prerequisites you'll need: + +- a Godot 4.2 (or later) executable, +- a C compiler, +- SCons as a build tool. + +Since this is using the API directly, there's no need to use the +`godot-cpp repository `__. + +File structure +-------------- + +To organize our files, we're gonna split into mainly two folders: + +.. code-block:: none + + gdextension_c_example/ + | + +--demo/ # game example/demo to test the extension + | + +--src/ # source code of the extension we are building + +We also need a copy of the ``gdextension_interface.h`` header from the Godot +source code, which can be obtained directly from the Godot executable by running +the following command: + +.. code-block:: none + + godot --dump-gdextension-interface + +This creates the header in the current folder, so you can just copy it to the ``src`` +folder in the example project. + +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 it to extract some information manually. + +To get this JSON file, just call the Godot executable: + +.. code-block:: none + + godot --dump-extension-api + +The resulting ``extension_api.json`` file will be created in the current +folder. You can copy this file to the example folder to have it handy. + +.. note:: + This extension is targeting Godot 4.2, but it should work on later versions as + well. If you want to target a different minimal version, make sure to get the + header and the JSON from the version Godot version you are targeting. + +Buildsystem +----------- + +Using a buildsystem makes our life a lot easier when dealing with C code. For +the sake of convenience, we'll use SCons since it's the same as what Godot +itself uses. + +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-bit +build, which is relevant for some parts of the example code. Making other build +types and cross-compilation is out of the scope of this tutorial. Save this file +to the root folder. + +.. code-block:: python + + #!/bin/env python + from SCons.Script import Environment + from os import path + import sys + + env = Environment() + + # Set the target path and name. + target_path = "demo/bin/" + target_name = "libgdexample" + + # Set the compiler and flags. + env.Append(CPPPATH=["src"]) # Add the src folder to the include path. + env.Append(CFLAGS=["-O0", "-g"]) # Make it a debug build. + + # Use Clang on macOS. + if sys.platform == "darwin": + env["CC"] = "clang" + + # Add all C files in "src" folder as sources. + sources = env.Glob("src/*.c") + + # Create a shared library. + library = env.SharedLibrary( + target=path.join(target_path, target_name), + source=sources, + ) + + # Set the library as the default target. + env.Default(library) + +This will include all C files in the ``src`` folder, so we don't need to change +this file when adding new source files. + +Initializing the extension +-------------------------- + +The first bit of code will be responsible for initializing the extension. This is +what makes Godot aware of what our GDExtension provides, such as classes and +plugins. + +Create the file ``init.h`` in the ``src`` folder, with the following contents: + +.. code-block:: c + + #ifndef INIT_H + #define INIT_H + + #include "defs.h" + + #include "gdextension_interface.h" + + void initialize_gdexample_module(void *p_userdata, GDExtensionInitializationLevel p_level); + void deinitialize_gdexample_module(void *p_userdata, GDExtensionInitializationLevel p_level); + GDExtensionBool GDE_EXPORT gdexample_library_init(GDExtensionInterfaceGetProcAddress p_get_proc_address, GDExtensionClassLibraryPtr p_library, GDExtensionInitialization *r_initialization); + + #endif // INIT_H + +The functions declared here have the signatures expected by the GDExtension API. + +Note the inclusion of the ``defs.h`` file. This is one of our helpers to +simplify writing the extension code. For now it will only contain the definition +of ``GDE_EXPORT``, a macro that makes the function public in the shared library +so Godot can properly call it. This macro helps abstracting what each compiler +expects. + +Create the ``defs.h`` file in the ``src`` folder with the following contents: + +.. code-block:: c + + #ifndef DEFS_H + #define DEFS_H + + #include + #include + #include + + #if !defined(GDE_EXPORT) + #if defined(_WIN32) + #define GDE_EXPORT __declspec(dllexport) + #elif defined(__GNUC__) + #define GDE_EXPORT __attribute__((visibility("default"))) + #else + #define GDE_EXPORT + #endif + #endif // ! GDE_EXPORT + + #endif // DEFS_H + +We also include some standard headers to make things easier. Now we only have to +include ``defs.h`` and those will come as a bonus. + +Now, let's implement the functions we just declared. Create a file called +``init.c`` in the ``src`` folder and add this code: + +.. code-block:: c + + #include "init.h" + + void initialize_gdexample_module(void *p_userdata, GDExtensionInitializationLevel p_level) + { + } + + void deinitialize_gdexample_module(void *p_userdata, GDExtensionInitializationLevel p_level) + { + } + + GDExtensionBool GDE_EXPORT gdexample_library_init(GDExtensionInterfaceGetProcAddress p_get_proc_address, GDExtensionClassLibraryPtr p_library, GDExtensionInitialization *r_initialization) + { + r_initialization->initialize = initialize_gdexample_module; + r_initialization->deinitialize = deinitialize_gdexample_module; + r_initialization->userdata = NULL; + r_initialization->minimum_initialization_level = GDEXTENSION_INITIALIZATION_SCENE; + + return true; + } + +What this does is set up the initialization data that Godot expects. The +functions to initialize and deinitialize are set so Godot will call then when +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_gdexample_module()`` function later to register our custom class. + +A basic class +------------- + +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 `. + +Create a file called ``gdexample.h`` in the ``src`` folder with the following +contents: + +.. code-block:: c + + #ifndef GDEXAMPLE_H + #define GDEXAMPLE_H + + #include "gdextension_interface.h" + + #include "defs.h" + + // Struct to hold the node data. + typedef struct + { + // Metadata. + GDExtensionObjectPtr object; // Stores the underlying Godot object. + } GDExample; + + // Constructor for the node. + void gdexample_class_constructor(GDExample *self); + + // Destructor for the node. + void gdexample_class_destructor(GDExample *self); + + // Bindings. + void gdexample_class_bind_methods(); + + #endif // GDEXAMPLE_H + +Noteworthy here is the ``object`` field, which holds a pointer to +the Godot object, and the ``gdexample_class_bind_methods()`` function, which will +register the metadata of our custom class (properties, methods, and signals). +The latter is not entirely necessary, as we can do it when registering the +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. +Since we can't inherit it directly, as we are not interacting with the source +code (and C doesn't even have classes), we instead tell Godot to create an +object of a type it knows and attach our extension to it. We will need the +reference to such objects when calling methods on the parent class, for +instance. + +Let's create the source counterpart of this header. Create the file +``gdexample.c`` in the ``src`` folder and add the following code to it: + +.. code-block:: c + + #include "gdexample.h" + + void gdexample_class_constructor(GDExample *self) + { + } + + void gdexample_class_destructor(GDExample *self) + { + } + + void gdexample_class_bind_methods() + { + } + + +As we don't have anything to do with those functions yet, they'll stay empty +for a while. + +The next step is registering our class. However, in order to do so we need to +create a :ref:`StringName ` and for that we have to get a +function from the GDExtension API. Since we'll need this a few times and we'll +also need other things, let's create a wrapper API to facilitate this kind of +chore. + +A wrapper API +------------- + +We'll start by creating an ``api.h`` file in the ``src`` folder: + +.. code-block:: c + + #ifndef API_H + #define API_H + + /* + This file works as a collection of helpers to call the GDExtension API + in a less verbose way, as well as a cache for methods from the discovery API, + just so we don't have to keep loading the same methods again. + */ + + #include "gdextension_interface.h" + + #include "defs.h" + + extern GDExtensionClassLibraryPtr class_library; + + // API methods. + + struct Constructors + { + GDExtensionInterfaceStringNameNewWithLatin1Chars string_name_new_with_latin1_chars; + } constructors; + + struct Destructors + { + GDExtensionPtrDestructor string_name_destructor; + } destructors; + + struct API + { + GDExtensionInterfaceClassdbRegisterExtensionClass2 classdb_register_extension_class2; + } api; + + void load_api(GDExtensionInterfaceGetProcAddress p_get_proc_address); + + + + #endif // API_H + +This file will include many other helpers as we fill our extension with +something useful. For now it only has a pointer to a function that creates a +StringName from a C string (in Latin-1 encoding) and another to destruct a +StringName, which we'll need to use to avoid leaking memory, as well as the +function to register a class, which is our initial goal. + +We also keep a reference to the ``class_library`` here. This is something that +Godot provides to us when initializing the extension and we'll need to use it +when registering the things we create so Godot can tell which extension is +making the call. + +There's also a function to load those function pointers from the GDExtension API. + +Let's work on the source counterpart of this header. Create the ``api.c`` file +in the ``src`` folder, adding the following code: + +.. code-block:: c + + #include "api.h" + + GDExtensionClassLibraryPtr class_library = NULL; + + void load_api(GDExtensionInterfaceGetProcAddress p_get_proc_address) + { + // Get helper functions first. + GDExtensionInterfaceVariantGetPtrDestructor variant_get_ptr_destructor = (GDExtensionInterfaceVariantGetPtrDestructor)p_get_proc_address("variant_get_ptr_destructor"); + + // API. + api.classdb_register_extension_class2 = p_get_proc_address("classdb_register_extension_class2"); + + // Constructors. + constructors.string_name_new_with_latin1_chars = p_get_proc_address("string_name_new_with_latin1_chars"); + + // Destructors. + destructors.string_name_destructor = variant_get_ptr_destructor(GDEXTENSION_VARIANT_TYPE_STRING_NAME); + } + +The first important thing here is ``p_get_proc_address``. This a function from +the GDExtension API that is passed during initialization. You can use this +function to request specific functions from the API by their name. Here we are +caching the results so we don't have to keep a reference for +``p_get_proc_address`` everywhere and use our wrapper instead. + +At the start we request the ``variant_get_ptr_destructor()`` function. This is not +going to be used outside of this function, so we don't add to our wrapper and +only cache it locally. The cast is necessary to silence compiler warnings. + +Then we get the function that creates a StringName from a C string, exactly what +we mentioned before as a needed function. We store that in our ``constructors`` +struct. + +Next, we use the ``variant_get_ptr_destructor()`` function we just got to query +for the destructor for StringName, using the enum value from +``gdextension_interface.h`` API as a parameter. We could get destructors for +other types in a similar manner, but we'll limit ourselves to what is needed for +the example. + +Lastly, we get the ``classdb_register_extension_class2()`` function, which we'll +need in order to register our custom class. + +.. note:: + You may wonder why the ``2`` is there in the function name. This means it's the + second version of this function. The old version is kept to ensure backwards + compatibility with older extensions, but since we have the second version + available, it's best to use the new one, because we don't intend to support older + Godot versions in this example. + + The ``gdextension_interface.h`` header documents in which Godot version each + function was introduced. + +We also define the ``class_library`` variable here, which will be set during +initialization. + +Speaking of initialization, now we have to change the ``init.c`` file in +order to fill the things we just added: + +.. code-block:: c + + GDExtensionBool GDE_EXPORT gdexample_library_init(GDExtensionInterfaceGetProcAddress p_get_proc_address, GDExtensionClassLibraryPtr p_library, GDExtensionInitialization *r_initialization) + { + class_library = p_library; + load_api(p_get_proc_address); + + ... + +Here we set the ``class_library`` as needed and call our new ``load_api()`` +function. Don't forget to also include the new headers at the top of this file: + +.. code-block:: c + + #include "init.h" + + #include "api.h" + #include "gdexample.h" + ... + +Since we are here, we can register our new custom class. Let's fill the +``initialize_gdexample_module()`` function: + +.. code-block:: c + + void initialize_gdexample_module(void *p_userdata, GDExtensionInitializationLevel p_level) + { + if (p_level != GDEXTENSION_INITIALIZATION_SCENE) + { + return; + } + + // Register class. + StringName class_name; + constructors.string_name_new_with_latin1_chars(&class_name, "GDExample", false); + StringName parent_class_name; + constructors.string_name_new_with_latin1_chars(&parent_class_name, "Sprite2D", false); + + GDExtensionClassCreationInfo2 class_info = { + .is_virtual = false, + .is_abstract = false, + .is_exposed = true, + .set_func = NULL, + .get_func = NULL, + .get_property_list_func = NULL, + .free_property_list_func = NULL, + .property_can_revert_func = NULL, + .property_get_revert_func = NULL, + .validate_property_func = NULL, + .notification_func = NULL, + .to_string_func = NULL, + .reference_func = NULL, + .unreference_func = NULL, + .create_instance_func = gdexample_class_create_instance, + .free_instance_func = gdexample_class_free_instance, + .recreate_instance_func = NULL, + .get_virtual_func = NULL, + .get_virtual_call_data_func = NULL, + .call_virtual_with_data_func = NULL, + .get_rid_func = NULL, + .class_userdata = NULL, + }; + + api.classdb_register_extension_class2(class_library, &class_name, &parent_class_name, &class_info); + + // Bind methods. + gdexample_class_bind_methods(); + + // Destruct things. + destructors.string_name_destructor(&class_name); + destructors.string_name_destructor(&parent_class_name); + } + +The struct with the class information is the biggest thing here. None of its +fields are required with the exception of ``create_instance_func`` and +``free_instance_func``. We haven't made those functions yet, so we'll have +to work on them soon. Note that we skip the initialization if it isn't at the +``SCENE`` level. This function may be called multiple times, once for each +level, but we only want to register our class once. + +The other undefined thing here is ``StringName``. This will be an opaque struct +meant to hold the data of a Godot StringName in our extension. We'll define it +in the appropriately named ``defs.h`` file: + +.. code-block:: c + + ... + // The sizes can be obtained from the extension_api.json file. + #ifdef BUILD_32 + #define STRING_NAME_SIZE 4 + #else + #define STRING_NAME_SIZE 8 + #endif + + // Types. + + typedef struct + { + uint8_t data[STRING_NAME_SIZE]; + } StringName; + + #endif // DEFS_H + +As mentioned in the comment, the sizes can be found in the +``extension_api.json`` file that we generated earlier, under the +``builtin_class_sizes`` property. The ``BUILD_32`` is never defined, as we +assume we are working with a 64-bits build of Godot here, but if you need it you +can add ``env.Append(CPPDEFINES=["BUILD_32"])`` to your ``SConstruct`` file. + +The ``// Types.`` comment foreshadows that we'll be adding more types to this +file. Let's leave that for later. + +The ``StringName`` struct here is just to hold Godot data, so we don't really +care what is inside of it. Though, in this case, it is just a pointer to the +data in the heap. We'll use this struct when we need to allocate data for a +StringName ourselves, like we are doing when registering our class. + +Back to registering, we need to work on our create and free functions. Let's +include them in ``gdexample.h`` since they're specific to the custom class: + +.. code-block:: c + + ... + // Bindings. + void gdexample_class_bind_methods(); + GDExtensionObjectPtr gdexample_class_create_instance(void *p_class_userdata); + void gdexample_class_free_instance(void *p_class_userdata, GDExtensionClassInstancePtr p_instance); + ... + +Before we can implement those function, we'll need a few more things in our API. +We need a way to allocate and free memory. While we could do this with good ol' +``malloc()``, we can instead make use of Godot's memory management functions. +We'll also need a way to create a Godot object and set it with our custom +instance. + +So let's change the ``api.h`` to include these new functions: + +.. code-block:: c + + ... + struct API + { + GDExtensionInterfaceClassdbRegisterExtensionClass2 classdb_register_extension_class2; + GDExtensionInterfaceClassdbConstructObject classdb_construct_object; + GDExtensionInterfaceObjectSetInstance object_set_instance; + GDExtensionInterfaceObjectSetInstanceBinding object_set_instance_binding; + GDExtensionInterfaceMemAlloc mem_alloc; + GDExtensionInterfaceMemFree mem_free; + } api; + +Then we change the ``load_api()`` function in ``api.c`` to grab these new functions: + +.. code-block:: c + + ... + void load_api(GDExtensionInterfaceGetProcAddress p_get_proc_address) + { + ... + // API. + api.classdb_register_extension_class2 = p_get_proc_address("classdb_register_extension_class2"); + api.classdb_construct_object = (GDExtensionInterfaceClassdbConstructObject)p_get_proc_address("classdb_construct_object"); + api.object_set_instance = p_get_proc_address("object_set_instance"); + api.object_set_instance_binding = p_get_proc_address("object_set_instance_binding"); + api.mem_alloc = (GDExtensionInterfaceMemAlloc)p_get_proc_address("mem_alloc"); + api.mem_free = (GDExtensionInterfaceMemFree)p_get_proc_address("mem_free"); + } + +Now we can go back to ``gdexample.c`` and define the new functions, without forgetting to +include the ``api.h`` header: + +.. code-block:: c + + #include "gdexample.h" + + #include "api.h" + + ... + + const GDExtensionInstanceBindingCallbacks gdexample_class_binding_callbacks = { + .create_callback = NULL, + .free_callback = NULL, + .reference_callback = NULL, + }; + + GDExtensionObjectPtr gdexample_class_create_instance(void *p_class_userdata) + { + // Create native Godot object; + StringName class_name; + constructors.string_name_new_with_latin1_chars(&class_name, "Sprite2D", false); + GDExtensionObjectPtr object = api.classdb_construct_object(&class_name); + destructors.string_name_destructor(&class_name); + + // Create extension object. + GDExample *self = (GDExample *)api.mem_alloc(sizeof(GDExample)); + gdexample_class_constructor(self); + self->object = object; + + // Set the extension instance in the native Godot object. + constructors.string_name_new_with_latin1_chars(&class_name, "GDExample", false); + api.object_set_instance(object, &class_name, self); + api.object_set_instance_binding(object, class_library, self, &gdexample_class_binding_callbacks); + destructors.string_name_destructor(&class_name); + + return object; + } + + void gdexample_class_free_instance(void *p_class_userdata, GDExtensionClassInstancePtr p_instance) + { + if (p_instance == NULL) + { + return; + } + GDExample *self = (GDExample *)p_instance; + gdexample_class_destructor(self); + api.mem_free(self); + } + +When instantiating an object, first we create a new Sprite2D object, since +that's the parent of our class. Then we allocate memory for our custom struct +and call its constructor. We save the pointer to the Godot object in the struct +as well like we mentioned earlier. + +Then we set our custom struct as the instance data. This will make Godot know +that the object is an instance of our custom class and properly call our custom +methods for instance, as well as passing this data back. + +Note that we return the Godot object we created, not our custom struct. + +For the ``gdextension_free_instance()`` function, we only call the destructor and free the memory we +allocated for the custom data. It is not necessary to destruct the Godot object +since that will be taken care of by the engine itself. + +A demo project +-------------- + +Now that we can create and free our custom object, we should be able to try it +out in an actual project. For this, you need to open Godot and create a new +project on the ``demo`` folder. The project manager may warn you the folder +isn't empty if you have compiled the extension before, you can safely ignore +this warning this time. + +If you didn't compile the extension yet, it is the time to do it now. To do +that, open a terminal or command prompt, navigate to the root folder of the +extension and run ``scons``. It should compile quickly since the extension is +very simple. + +Then, create a file called ``gdexample.gdextension`` inside the ``demo`` folder. +This is a Godot resource that describes the extension, allowing the engine to +properly load it. Put the following content in this file: + +.. code-block:: + + [configuration] + + entry_symbol = "gdexample_library_init" + compatibility_minimum = "4.2" + + [libraries] + macos.debug = "res://bin/libgdexample.dylib" + linux.debug = "res://bin/libgdexample.so" + windows.debug = "res://bin/libgdexample.dll" + +As you can see, ``gdexample_library_init()`` is the same name of the function we +defined in our ``init.c`` file. It is important that the names match because it +is how Godot calls the entry point of the extension. + +We also set the compatibility minimum to 4.2, since we are targeting this +version. It should still work on later versions. If you are using a later Godot +version and rely on the new features, you need to increase this value to a +version number that has everything you use. +See :ref:`doc_what_is_gdextension_version_compatibility` for more information. + +In the ``[libraries]`` section we set up the paths to the shared library on +different platforms. Here there's only the debug versions since that's what we +are working on for the example. Using :ref:`feature tags ` you +can fine tune this to also provide release versions, add more target operating systems, as +well as providing 32-bit and 64-bit binaries. + +You can also add library dependencies and custom icons for your classes in this +file, but this is out of the scope for this tutorial. + +After saving the file, go back to the editor. Godot should automatically load +the extension. Nothing will be seen because our extension only registers a new +class. To use this class add a ``Node2D`` as a root of the scene. Move it to +the middle of viewport for better visibility. Then add a new child node to the +root and in the **Create New Node** dialog search for "GDExample", the name of +our class, as it should be listed there. If it isn't, it means that Godot didn't +load the extension properly, so try restarting the editor and retrace the steps +to see if anything went missing. + +Our custom class is derived from ``Sprite2D``, so it has a **Texture** property +in the Inspector. Set this to the ``icon.svg`` file that Godot handily created +for us when making the project. Save this scene as ``main.tscn`` and run it. You +may want to set it as the main scene for convenience. + +.. image:: img/gdextension_c_running.webp + +VoilĂ ! We have a custom node running in Godot. However, it does not do anything +and has nothing different than a regular ``Sprite2D`` node. We will fix that next by +adding custom methods and properties. + +Custom methods +-------------- + +A common thing in extensions is creating methods for the custom classes and +exposing those to the Godot API. We are going to create a couple of getters and +setters which are need for binding the properties afterwards. + +First, let's add the new fields in our struct to hold the values for +``amplitude`` and ``speed``, which we will use later on when creating the +behavior for the node. Add them to the ``gdexample.h`` file, changing the +``GDExample`` struct: + +.. code-block:: c + + ... + + typedef struct + { + // Public properties. + double amplitude; + double speed; + // Metadata. + GDExtensionObjectPtr object; // Stores the underlying Godot object. + } GDExample; + + ... + + +In the same file, add the declaration for the getters and setters, right after +the destructor. + +.. code-block:: c + + ... + + // Destructor for the node. + void gdexample_class_destructor(GDExample *self); + + // Properties. + void gdexample_class_set_amplitude(GDExample *self, double amplitude); + double gdexample_class_get_amplitude(const GDExample *self); + void gdexample_class_set_speed(GDExample *self, double speed); + double gdexample_class_get_speed(const GDExample *self); + + ... + +In the ``gdexample.cpp`` file, we will initialize these values in the constructor +and add the implementations for those new functions, which are quite trivial: + +.. code-block:: c + + void gdexample_class_constructor(GDExample *self) + { + self->amplitude = 10.0; + self->speed = 1.0; + } + + void gdexample_class_set_amplitude(GDExample *self, double amplitude) + { + self->amplitude = amplitude; + } + + double gdexample_class_get_amplitude(const GDExample *self) + { + return self->amplitude; + } + + void gdexample_class_set_speed(GDExample *self, double speed) + { + self->speed = speed; + } + + double gdexample_class_get_speed(const GDExample *self) + { + return self->speed; + } + +To make those simple functions work when called by Godot, we will need some +wrappers to help us properly convert the data to and from the engine. + +First, we will create wrappers for ``ptrcall``. This is what Godot uses when the +types of the values are known to be exact, which avoids using Variant. We're +gonna need two of those: one for the functions that take no arguments and +return a ``double`` (for the getters) and another for the functions that take a +single ``double`` argument and return nothing (for the setters). + +Add the declarations to the ``api.h`` file: + +.. code-block:: c + + void ptrcall_0_args_ret_float(void *method_userdata, GDExtensionClassInstancePtr p_instance, const GDExtensionConstTypePtr *p_args, GDExtensionTypePtr r_ret); + void ptrcall_1_float_arg_no_ret(void *method_userdata, GDExtensionClassInstancePtr p_instance, const GDExtensionConstTypePtr *p_args, GDExtensionTypePtr r_ret); + + +Those two functions follow the ``GDExtensionClassMethodPtrCall`` type, as +defined in the ``gdextension_interface.h``. We use ``float`` as a name here +because in Godot the ``float`` type has double precision, so we keep this +convention. + +Then we implement those functions in the ``api.c`` file: + +.. code-block:: c + + void ptrcall_0_args_ret_float(void *method_userdata, GDExtensionClassInstancePtr p_instance, const GDExtensionConstTypePtr *p_args, GDExtensionTypePtr r_ret) + { + // Call the function. + double (*function)(void *) = method_userdata; + *((double *)r_ret) = function(p_instance); + } + + void ptrcall_1_float_arg_no_ret(void *method_userdata, GDExtensionClassInstancePtr p_instance, const GDExtensionConstTypePtr *p_args, GDExtensionTypePtr r_ret) + { + // Call the function. + void (*function)(void *, double) = method_userdata; + function(p_instance, *((double *)p_args[0])); + } + +The ``method_userdata`` argument is a custom value that we give to Godot, in +this case we will set as the function pointer for the one we want to call. So +first we convert it to the function type, then we just call it by passing the +arguments when needed, or setting the return value. + +The ``p_instance`` argument contains the custom instance of our class, which we +gave with ``object_set_instance()`` when creating the object. + +``p_args`` is an array of arguments. Note this contains **pointers** to the +values. That's why we dereference it when passing to our functions. The number +of arguments will be declared when binding the function (which we will do soon) +and it will always include default ones if those exist. + +Finally, the ``r_ret`` is a pointer to the variable where the return value needs to +be set. Like the arguments, it will be the correct type as declared. For the +function that does not return, we have to avoid setting it. + +Note how the type and argument counts are exact, so if we needed different +types, for example, we would have to create more wrappers. This could be +automated using some code generation, but this is out of the scope for this +tutorial. + +While the ``ptrcall`` functions are used when types are exact, sometimes Godot cannot know +if that's the case (when the call comes from a dynamically typed language, such +as GDScript). In those situations it uses regular ``call`` functions, so we need to +provide those as well when binding. + +Let's create two new wrappers in the ``api.h`` file: + +.. code-block:: c + + void call_0_args_ret_float(void *method_userdata, GDExtensionClassInstancePtr p_instance, const GDExtensionConstVariantPtr *p_args, GDExtensionInt p_argument_count, GDExtensionVariantPtr r_return, GDExtensionCallError *r_error); + void call_1_float_arg_no_ret(void *method_userdata, GDExtensionClassInstancePtr p_instance, const GDExtensionConstVariantPtr *p_args, GDExtensionInt p_argument_count, GDExtensionVariantPtr r_return, GDExtensionCallError *r_error); + +These follow the ``GDExtensionClassMethodCall`` type, which is a bit different. +First, you receive pointers to Variants instead of exact types. There's also the +amount of arguments and an error struct that you can set if something goes +wrong. + +In order to check the type and also extract interact with Variant, we will need +a few more functions from the GDExtension API. So let's expand our wrapper +structs: + +.. code-block:: c + + struct Constructors { + ... + GDExtensionVariantFromTypeConstructorFunc variant_from_float_constructor; + GDExtensionTypeFromVariantConstructorFunc float_from_variant_constructor; + } constructors; + + struct API + { + ... + GDExtensionInterfaceGetVariantFromTypeConstructor get_variant_from_type_constructor; + GDExtensionInterfaceGetVariantToTypeConstructor get_variant_to_type_constructor; + GDExtensionInterfaceVariantGetType variant_get_type; + } api; + +The names say all about what those do. We have a couple of constructors to +create and extract a floating point value to and from a Variant. We also have a +couple of helpers to actually get those constructors, as well as a function to +find out the type of a Variant. + +Let's get those from the API, like we did before, by changing the ``load_api()`` +function in the ``api.c`` file: + +.. code-block:: c + + void load_api(GDExtensionInterfaceGetProcAddress p_get_proc_address) + { + ... + + // API. + ... + api.get_variant_from_type_constructor = (GDExtensionInterfaceGetVariantFromTypeConstructor)p_get_proc_address("get_variant_from_type_constructor"); + api.get_variant_to_type_constructor = (GDExtensionInterfaceGetVariantToTypeConstructor)p_get_proc_address("get_variant_to_type_constructor"); + api.variant_get_type = (GDExtensionInterfaceVariantGetType)p_get_proc_address("variant_get_type"); + ... + + // Constructors. + ... + constructors.variant_from_float_constructor = api.get_variant_from_type_constructor(GDEXTENSION_VARIANT_TYPE_FLOAT); + constructors.float_from_variant_constructor = api.get_variant_to_type_constructor(GDEXTENSION_VARIANT_TYPE_FLOAT); + ... + } + +Now that we have these set, we can implement our call wrappers in the same file: + +.. code-block:: c + + void call_0_args_ret_float(void *method_userdata, GDExtensionClassInstancePtr p_instance, const GDExtensionConstVariantPtr *p_args, GDExtensionInt p_argument_count, GDExtensionVariantPtr r_return, GDExtensionCallError *r_error) + { + // Check argument count. + if (p_argument_count != 0) + { + r_error->error = GDEXTENSION_CALL_ERROR_TOO_MANY_ARGUMENTS; + r_error->expected = 0; + return; + } + + // Call the function. + double (*function)(void *) = method_userdata; + double result = function(p_instance); + // Set resulting Variant. + constructors.variant_from_float_constructor(r_return, &result); + } + + void call_1_float_arg_no_ret(void *method_userdata, GDExtensionClassInstancePtr p_instance, const GDExtensionConstVariantPtr *p_args, GDExtensionInt p_argument_count, GDExtensionVariantPtr r_return, GDExtensionCallError *r_error) + { + // Check argument count. + if (p_argument_count < 1) + { + r_error->error = GDEXTENSION_CALL_ERROR_TOO_FEW_ARGUMENTS; + r_error->expected = 1; + return; + } + else if (p_argument_count > 1) + { + r_error->error = GDEXTENSION_CALL_ERROR_TOO_MANY_ARGUMENTS; + r_error->expected = 1; + return; + } + + // Check the argument type. + GDExtensionVariantType type = api.variant_get_type(p_args[0]); + if (type != GDEXTENSION_VARIANT_TYPE_FLOAT) + { + r_error->error = GDEXTENSION_CALL_ERROR_INVALID_ARGUMENT; + r_error->expected = GDEXTENSION_VARIANT_TYPE_FLOAT; + r_error->argument = 0; + return; + } + + // Extract the argument. + double arg1; + constructors.float_from_variant_constructor(&arg1, (GDExtensionVariantPtr)p_args[0]); + + // Call the function. + void (*function)(void *, double) = method_userdata; + function(p_instance, arg1); + } + +These functions are a bit longer but easy to follow. First they check if the +argument count is as expected and if not they set the error struct and +return. For the one that has one parameter, it also checks if the argument type +is correct. This is important because mismatched types when extracting from +Variant can cause crashes. + +Then it proceeds to extract the argument using the constructor we setup before. +The one with no arguments instead sets the return value after calling the +function. Note how they use a pointer to a ``double`` variable, since this is +what those constructors expect. + +Before we can actually bind our methods, we need a way to create +``GDExtensionPropertyInfo`` instances. While we could do them inside the binding +functions that we'll implement afterwards, it's easier to have a helper for it +since we'll need it multiple times, including for when we bind properties. + +Let's create these two functions in the ``api.h`` file: + +.. code-block:: c + + // Create a PropertyInfo struct. + GDExtensionPropertyInfo make_property( + GDExtensionVariantType type, + const char *name); + + GDExtensionPropertyInfo make_property_full( + GDExtensionVariantType type, + const char *name, + uint32_t hint, + const char *hint_string, + const char *class_name, + uint32_t usage_flags); + + void destruct_property(GDExtensionPropertyInfo *info); + +The first one is a simplified version of the second since we usually don't need +all the arguments for the property and are okay with the defaults. Then we also +have a function to destruct the PropertyInfo since we need to create Strings and +StringNames that need to be properly disposed of. + +Speaking of which, we also need a way to create and destruct Strings, so we'll +make an addition to existing structs in this same file. We'll also get a new API +function for actually binding our custom method. + +.. code-block:: c + + struct Constructors + { + ... + GDExtensionInterfaceStringNewWithUtf8Chars string_new_with_utf8_chars; + } constructors; + + struct Destructors + { + ... + GDExtensionPtrDestructor string_destructor; + } destructors; + + struct API + { + ... + GDExtensionInterfaceClassdbRegisterExtensionClassMethod classdb_register_extension_class_method; + } api; + +Before implementing those, let's do a quick stop in the ``defs.h`` file and +include the size of the ``String`` type and a couple of enums: + +.. code-block:: c + + // The sizes can be obtained from the extension_api.json file. + #ifdef BUILD_32 + #define STRING_SIZE 4 + #define STRING_NAME_SIZE 4 + #else + #define STRING_SIZE 8 + #define STRING_NAME_SIZE 8 + #endif + + ... + + typedef struct + { + uint8_t data[STRING_SIZE]; + } String; + + // Enums. + + typedef enum + { + PROPERTY_HINT_NONE = 0, + } PropertyHint; + + typedef enum + { + PROPERTY_USAGE_NONE = 0, + PROPERTY_USAGE_STORAGE = 2, + PROPERTY_USAGE_EDITOR = 4, + PROPERTY_USAGE_DEFAULT = PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR, + } PropertyUsageFlags; + +While it's the same size as ``StringName``, it is more clear to use a different +name for it. + +The enums here are just helpers to give names to the numbers they represent. The +information about them is present in the ``extension_api.json`` file. Here we +just set up the ones we need for the tutorial, to keep it more concise. + +Going now to the ``api.c``, we need to load the pointers to the new functions we +added to the API. + +.. code-block:: c + + void load_api(GDExtensionInterfaceGetProcAddress p_get_proc_address) + { + ... + // API + ... + api.classdb_register_extension_class_method = p_get_proc_address("classdb_register_extension_class_method"); + + // Constructors. + ... + constructors.string_new_with_utf8_chars = p_get_proc_address("string_new_with_utf8_chars"); + + // Destructors. + ... + destructors.string_destructor = variant_get_ptr_destructor(GDEXTENSION_VARIANT_TYPE_STRING); + } + +Then we can also implement the functions to create the ``PropertyInfo`` struct. + +.. code-block:: c + + GDExtensionPropertyInfo make_property( + GDExtensionVariantType type, + const char *name) + { + + return make_property_full(type, name, PROPERTY_HINT_NONE, "", "", PROPERTY_USAGE_DEFAULT); + } + + GDExtensionPropertyInfo make_property_full( + GDExtensionVariantType type, + const char *name, + uint32_t hint, + const char *hint_string, + const char *class_name, + uint32_t usage_flags) + { + + StringName *prop_name = api.mem_alloc(sizeof(StringName)); + constructors.string_name_new_with_latin1_chars(prop_name, name, false); + String *prop_hint_string = api.mem_alloc(sizeof(String)); + constructors.string_new_with_utf8_chars(prop_hint_string, hint_string); + StringName *prop_class_name = api.mem_alloc(sizeof(StringName)); + constructors.string_name_new_with_latin1_chars(prop_class_name, class_name, false); + + GDExtensionPropertyInfo info = { + .name = prop_name, + .type = type, + .hint = hint, + .hint_string = prop_hint_string, + .class_name = prop_class_name, + .usage = usage_flags, + }; + + return info; + } + + void destruct_property(GDExtensionPropertyInfo *info) + { + destructors.string_name_destructor(info->name); + destructors.string_destructor(info->hint_string); + destructors.string_name_destructor(info->class_name); + api.mem_free(info->name); + api.mem_free(info->hint_string); + api.mem_free(info->class_name); + } + + +The simple version of ``make_property()`` just calls the more complete one with a +some default arguments. What those values mean exactly is out of the scope of +this tutorial, check the page about the :ref:`Object class ` +for more details about binding methods and properties. + +The complete version is more involved. First, it creates ``String``'s and +``StringName``'s for the needed fields, by allocating memory and calling their +constructors. Then it creates a ``GDExtensionPropertyInfo`` struct and sets all +the fields with the arguments provided. Finally it returns this created struct. + +The ``destruct_property()`` function is straightforward, it simply calls the +destructors for the created objects and frees their allocated memory. + +Let's go back again to the header ``api.h`` to create the functions that will +actually bind the methods: + +.. code-block:: c + + // Version for 0 arguments, with return. + void bind_method_0_r( + const char *class_name, + const char *method_name, + void *function, + GDExtensionVariantType return_type); + + // Version for 1 argument, no return. + void bind_method_1( + const char *class_name, + const char *method_name, + void *function, + const char *arg1_name, + GDExtensionVariantType arg1_type); + +Then switch back to the ``api.c`` file to implement these: + +.. code-block:: c + + // Version for 0 arguments, with return. + void bind_method_0_r( + const char *class_name, + const char *method_name, + void *function, + GDExtensionVariantType return_type) + { + StringName method_name_string; + constructors.string_name_new_with_latin1_chars(&method_name_string, method_name, false); + + GDExtensionClassMethodCall call_func = call_0_args_ret_float; + GDExtensionClassMethodPtrCall ptrcall_func = ptrcall_0_args_ret_float; + + GDExtensionPropertyInfo return_info = make_property(return_type, ""); + + GDExtensionClassMethodInfo method_info = { + .name = &method_name_string, + .method_userdata = function, + .call_func = call_func, + .ptrcall_func = ptrcall_func, + .method_flags = GDEXTENSION_METHOD_FLAGS_DEFAULT, + .has_return_value = true, + .return_value_info = &return_info, + .return_value_metadata = GDEXTENSION_METHOD_ARGUMENT_METADATA_NONE, + .argument_count = 0, + }; + + StringName class_name_string; + constructors.string_name_new_with_latin1_chars(&class_name_string, class_name, false); + + api.classdb_register_extension_class_method(class_library, &class_name_string, &method_info); + + // Destruct things. + destructors.string_name_destructor(&method_name_string); + destructors.string_name_destructor(&class_name_string); + destruct_property(&return_info); + } + + // Version for 1 argument, no return. + void bind_method_1( + const char *class_name, + const char *method_name, + void *function, + const char *arg1_name, + GDExtensionVariantType arg1_type) + { + + StringName method_name_string; + constructors.string_name_new_with_latin1_chars(&method_name_string, method_name, false); + + GDExtensionClassMethodCall call_func = call_1_float_arg_no_ret; + GDExtensionClassMethodPtrCall ptrcall_func = ptrcall_1_float_arg_no_ret; + + GDExtensionPropertyInfo args_info[] = { + make_property(arg1_type, arg1_name), + }; + GDExtensionClassMethodArgumentMetadata args_metadata[] = { + GDEXTENSION_METHOD_ARGUMENT_METADATA_NONE, + }; + + GDExtensionClassMethodInfo method_info = { + .name = &method_name_string, + .method_userdata = function, + .call_func = call_func, + .ptrcall_func = ptrcall_func, + .method_flags = GDEXTENSION_METHOD_FLAGS_DEFAULT, + .has_return_value = false, + .argument_count = 1, + .arguments_info = args_info, + .arguments_metadata = args_metadata, + }; + + StringName class_name_string; + constructors.string_name_new_with_latin1_chars(&class_name_string, class_name, false); + + api.classdb_register_extension_class_method(class_library, &class_name_string, &method_info); + + // Destruct things. + destructors.string_name_destructor(&method_name_string); + destructors.string_name_destructor(&class_name_string); + destruct_property(&args_info[0]); + } + +Both functions are very similar. First, they create a ``StringName`` with the +method name. This is created in the stack since we don't need to keep it after +the function ends. Then they create local variables to hold the ``call_func`` +and ``ptrcall_func``, pointing to the helper functions we defined earlier. + +In the next step they diverge a bit. The first one creates a property for the +return value, which has an empty name since it's not needed. The other creates +an array of properties for the arguments, which in this case has a single +element. This one also has an array of metadata, which can be used if there's +something special about the argument (e.g. if an ``int`` value is 32 bits long +instead of the default of 64 bits). + +Afterwards, they create the ``GDExtensionClassMethodInfo`` with the required +fields for each case. Then they make a ``StringName`` for the class name, in +order to associate the method with the class. Next, they call the API function +to actually bind this method to the class. Finally, we destruct the objects we +created since they aren't needed anymore. + +.. note:: + The bind helpers here use the call helpers we created earlier, so do note that + those call helpers only accept the Godot ``FLOAT`` type (which is equivalent to + ``double`` in C). If you intend to use this for other types, you would need to + check the type of the arguments and return type and select an appropriate + function callback. This is avoided here only to keep the example from becoming + even longer. + +Now that we have the means to bind methods, we can actually do so in our custom +class. Go to the ``gdexample.c`` file and fill up the +``gdexample_class_bind_methods()`` function: + +.. code-block:: c + + void gdexample_class_bind_methods() + { + bind_method_0_r("GDExample", "get_amplitude", gdexample_class_get_amplitude, GDEXTENSION_VARIANT_TYPE_FLOAT); + bind_method_1("GDExample", "set_amplitude", gdexample_class_set_amplitude, "amplitude", GDEXTENSION_VARIANT_TYPE_FLOAT); + + bind_method_0_r("GDExample", "get_speed", gdexample_class_get_speed, GDEXTENSION_VARIANT_TYPE_FLOAT); + bind_method_1("GDExample", "set_speed", gdexample_class_set_speed, "speed", GDEXTENSION_VARIANT_TYPE_FLOAT); + } + +Since this function is already being called by the initialization process, we +can stop here. This function is much more straightforward after we created all the +infrastructure to make this work. You can see that implementing the binding +functions inline here would take some space and also be quite repetitive. This +also makes it easier to add another method in the future. + +If you compile the code and reopen the demo project, nothing will be different +at first, since we only added two new methods. To ensure those are registered +properly, you can search for ``GDExample`` in the editor help and verify they +are present in the documentation page. + +.. image:: img/gdextension_c_methods_doc.webp + + +Custom properties +----------------- + +Since we now have the getter and setter for our properties already bound, we can +move forward to create actual properties that will be displayed in the Godot +editor inspector. + +Given our extensive setup in the previous section, there are only a few things +needed to enable us to bind properties. First, let's get a new API function in +the ``api.h`` file: + + +.. code-block:: c + + struct API { + ... + GDExtensionInterfaceClassdbRegisterExtensionClassProperty classdb_register_extension_class_property; + } api; + +Let's also declare a function here to bind properties: + +.. code-block:: c + + void bind_property( + const char *class_name, + const char *name, + GDExtensionVariantType type, + const char *getter, + const char *setter); + +In the ``api.c`` file, we can load the new API function: + +.. code-block:: c + + void load_api(GDExtensionInterfaceGetProcAddress p_get_proc_address) + { + // API + ... + api.classdb_register_extension_class_property = p_get_proc_address("classdb_register_extension_class_property"); + + ... + } + +Then we can implement our new helper function in this same file: + +.. code-block:: c + + void bind_property( + const char *class_name, + const char *name, + GDExtensionVariantType type, + const char *getter, + const char *setter) + { + StringName class_string_name; + constructors.string_name_new_with_latin1_chars(&class_string_name, class_name, false); + GDExtensionPropertyInfo info = make_property(type, name); + StringName getter_name; + constructors.string_name_new_with_latin1_chars(&getter_name, getter, false); + StringName setter_name; + constructors.string_name_new_with_latin1_chars(&setter_name, setter, false); + + api.classdb_register_extension_class_property(class_library, &class_string_name, &info, &setter_name, &getter_name); + + // Destruct things. + destructors.string_name_destructor(&class_string_name); + destruct_property(&info); + destructors.string_name_destructor(&getter_name); + destructors.string_name_destructor(&setter_name); + } + +This function is similar to the one for binding methods. The main difference is +that we don't need an extra struct since we can simply use the +``GDExtensionPropertyInfo`` that is created by our helper function, so it's more +straightforward. It only creates the ``StringName`` values from the +C strings, creates a property info struct using our helper, calls the API +function to register the property in the class and then destructs all the objects +we created. + +With this done, we can extend the ``gdexample_class_bind_methods()`` function in the +``gdexample.c`` file: + +.. code-block:: c + + void gdexample_class_bind_methods() + { + bind_method_0_r("GDExample", "get_amplitude", gdexample_class_get_amplitude, GDEXTENSION_VARIANT_TYPE_FLOAT); + bind_method_1("GDExample", "set_amplitude", gdexample_class_set_amplitude, "amplitude", GDEXTENSION_VARIANT_TYPE_FLOAT); + bind_property("GDExample", "amplitude", GDEXTENSION_VARIANT_TYPE_FLOAT, "get_amplitude", "set_amplitude"); + + bind_method_0_r("GDExample", "get_speed", gdexample_class_get_speed, GDEXTENSION_VARIANT_TYPE_FLOAT); + bind_method_1("GDExample", "set_speed", gdexample_class_set_speed, "speed", GDEXTENSION_VARIANT_TYPE_FLOAT); + 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 +not only on the documentation page for the custom class but also in the Inspector dock when the +``GDExample`` node is selected. + +.. image:: img/gdextension_c_inspector_properties.webp + +Binding virtual methods +----------------------- + +Our custom node now has properties to influence how it operates, but it still +doesn't do anything. In this section, we will bind the virtual method +:ref:`_process() ` and make our custom sprite +move a little bit. + +In the ``gdexample.h`` file, let's add a function that represents the custom +``_process()`` method: + +.. code-block:: c + + // Methods. + void gdexample_class_process(GDExample *self, double delta); + +We'll also add a "private" field to keep track of the time passed in our custom +struct. This is "private" only in the sense that it won't be bound to the Godot +API, even though it is public in the C side, given the language lacks access +modifiers. + +.. code-block:: c + + typedef struct + { + // Private properties. + double time_passed; + ... + } GDExample; + +On the counterpart source file ``gdexample.c`` we need to initialize the new +field in the constructor: + +.. code-block:: c + + void gdexample_class_constructor(GDExample *self) + { + self->time_passed = 0.0; + self->amplitude = 10.0; + self->speed = 1.0; + } + +Then we can create the simplest implementation for the ``_process`` method: + +.. code-block:: c + + void gdexample_class_process(GDExample *self, double delta) + { + self->time_passed += self->speed * delta; + } + +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 from the regular bindings. Instead of +explicitly registering the method itself, we'll register a special function that +Godot will call to ask if a particular virtual method is implemented in our +extension. The engine will pass a ``StringName`` as an argument so, following +the spirit of this tutorial, we'll create a helper function to check if it is +equal to a C string. + +Let's add the declaration to the ``api.h`` file: + +.. code-block:: c + + // Compare a StringName with a C string. + bool is_string_name_equal(GDExtensionConstStringNamePtr p_a, const char *p_b); + +We'll also add a new struct to this file, to hold function pointers for custom operators: + +.. code-block:: c + + struct Operators + { + GDExtensionPtrOperatorEvaluator string_name_equal; + } operators; + +Then in the ``api.c`` file we'll load the function pointer from the API: + +.. code-block:: c + + void load_api(GDExtensionInterfaceGetProcAddress p_get_proc_address) + { + // Get helper functions first. + ... + GDExtensionInterfaceVariantGetPtrOperatorEvaluator variant_get_ptr_operator_evaluator = (GDExtensionInterfaceVariantGetPtrOperatorEvaluator)p_get_proc_address("variant_get_ptr_operator_evaluator"); + + ... + + // Operators. + operators.string_name_equal = variant_get_ptr_operator_evaluator(GDEXTENSION_VARIANT_OP_EQUAL, GDEXTENSION_VARIANT_TYPE_STRING_NAME, GDEXTENSION_VARIANT_TYPE_STRING_NAME); + } + +As you can see we need a new local helper here in order to grab the function +pointer for the operator. + +With this handy, we can easily create our comparison function in the same file: + +.. code-block:: c + + bool is_string_name_equal(GDExtensionConstStringNamePtr p_a, const char *p_b) + { + // Create a StringName for the C string. + StringName string_name; + constructors.string_name_new_with_latin1_chars(&string_name, p_b, false); + + // Compare both StringNames. + bool is_equal = false; + operators.string_name_equal(p_a, &string_name, &is_equal); + + // Destroy the created StringName. + destructors.string_name_destructor(&string_name); + + // Return the result. + return is_equal; + } + +This function creates a ``StringName`` from the argument, compares with +the other one using the operator function pointer, and returns the result. Note +that the return value for the operator is passed as an out reference, this is a +common thing in the API. + +Let's go back to the ``gdexample.h`` file and add a couple of functions that +will be used as the callbacks for the Godot API: + +.. code-block:: c + + void *gdexample_class_get_virtual_with_data(void *p_class_userdata, GDExtensionConstStringNamePtr p_name); + void gdexample_class_call_virtual_with_data(GDExtensionClassInstancePtr p_instance, GDExtensionConstStringNamePtr p_name, void *p_virtual_call_userdata, const GDExtensionConstTypePtr *p_args, GDExtensionTypePtr r_ret); + +There are actually two ways of registering virtual methods. Only one has the +``get`` part, in which you give Godot a properly crafted function pointer which +will be called. For this we would need to create another helper for each virtual +method, something that is not very convenient. Instead, we use the second method +which allows us to return any data, and then Godot will call a second callback +and give us back this data along with the call information. We can simply give +our own function pointer as custom data and then have a single callback for all +virtual methods. Although in this example we will only use it for one method, +this way is simpler to expand. + +So let's implement those two functions in the ``gdexample.c`` file: + +.. code-block:: c + + void *gdexample_class_get_virtual_with_data(void *p_class_userdata, GDExtensionConstStringNamePtr p_name) + { + // If it is the "_process" method, return a pointer to the gdexample_class_process function. + if (is_string_name_equal(p_name, "_process")) + { + return (void *)gdexample_class_process; + } + // Otherwise, return NULL. + return NULL; + } + + void gdexample_class_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 (p_virtual_call_userdata == &gdexample_class_process) + { + ptrcall_1_float_arg_no_ret(p_virtual_call_userdata, p_instance, p_args, r_ret); + } + } + +Those functions are also quite straightforward after making all the helpers +previously. + +For the first one, we simply check if the function name requested is +``_process`` and if it is we return a function pointer to our implementation of +it. Otherwise we return ``NULL``, signaling that the method is not being +overridden. We don't use the ``p_class_userdata`` here since this function is +meant only for one class and we don't have any data associated with it. + +The second one is similar. If it is the ``_process()`` method, it uses the given +function pointer to call the ``ptrcall`` helper, passing the call arguments +forward. Otherwise it simply does nothing, since we don't have any other virtual +methods being implemented. + +The only thing missing is using those callbacks when the class is registered. Go +to the ``init.c`` file and change the ``class_info`` initialization to include +those, replacing the ``NULL`` value used previously: + +.. code-block:: c + + void initialize_gdexample_module(void *p_userdata, GDExtensionInitializationLevel p_level) + { + ... + + GDExtensionClassCreationInfo2 class_info = { + ... + .get_virtual_call_data_func = gdexample_class_get_virtual_with_data, + .call_virtual_with_data_func = gdexample_class_call_virtual_with_data, + ... + }; + + ... + } + +This is enough to bind the virtual method. If you build the extension and run +the demo project again, the ``_process()`` function will be called. You just won't +be able to tell since the function itself does nothing visible. We will solve +this now by making the custom node move following a pattern. + +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. + +First, let's add :ref:`class_Vector2` to our ``defs.h`` file, so we +can use it in our method: + +.. code-block:: c + + // The sizes can be obtained from the extension_api.json file. + ... + #ifdef REAL_T_IS_DOUBLE + #define VECTOR2_SIZE 16 + #else + #define VECTOR2_SIZE 8 + #endif + + ... + + // Types. + + ... + + typedef struct + { + uint8_t data[VECTOR2_SIZE]; + } Vector2; + +The ``REAL_T_IS_DOUBLE`` define is only needed if your Godot version was built +with double precision support, which is not the default. + +Now, in the ``api.h`` file, we'll add few things to the API structs, including a +new one for holding engine methods to call. + +.. code-block:: c + + struct Constructors + { + ... + GDExtensionPtrConstructor vector2_constructor_x_y; + } constructors; + + ... + + struct Methods + { + GDExtensionMethodBindPtr node2d_set_position; + } methods; + + struct API + { + ... + GDExtensionInterfaceClassdbGetMethodBind classdb_get_method_bind; + GDExtensionInterfaceObjectMethodBindPtrcall object_method_bind_ptrcall; + } api; + +Then in the ``api.c`` file we can grab the function pointers from Godot: + +.. code-block:: + + void load_api(GDExtensionInterfaceGetProcAddress p_get_proc_address) + { + // Get helper functions first. + ... + GDExtensionInterfaceVariantGetPtrConstructor variant_get_ptr_constructor = (GDExtensionInterfaceVariantGetPtrConstructor)p_get_proc_address("variant_get_ptr_constructor"); + + // API. + ... + api.classdb_get_method_bind = (GDExtensionInterfaceClassdbGetMethodBind)p_get_proc_address("classdb_get_method_bind"); + api.object_method_bind_ptrcall = p_get_proc_address("object_method_bind_ptrcall"); + + // Constructors. + ... + constructors.vector2_constructor_x_y = variant_get_ptr_constructor(GDEXTENSION_VARIANT_TYPE_VECTOR2, 3); // See extension_api.json for indices. + + ... + } + +The only noteworthy part here is the ``Vector2`` constructor, for which we request the +index ``3``. Since there are multiple constructors with different kinds of +arguments, we need to specify which one we want. In this case we're getting the +one that takes two float numbers as the ``x`` and ``y`` coordinates, hence the +name. This index can be retrieved from the ``extension_api.json`` file. Note we +also need a new local helper to get it. + +Be aware that we don't get anything for the methods struct here. This is because +this function is called too early in the initialization process, so classes +won't be properly registered yet. + +Instead, we're gonna use the initialization level callback to grab those when we +are registering our custom class. Add this to the ``init.c`` file: + +.. code-block:: c + + void initialize_gdexample_module(void *p_userdata, GDExtensionInitializationLevel p_level) + { + if (p_level != GDEXTENSION_INITIALIZATION_SCENE) + { + return; + } + + // Get ClassDB methods here because the classes we need are all properly registered now. + // See extension_api.json for hashes. + StringName native_class_name; + StringName method_name; + + constructors.string_name_new_with_latin1_chars(&native_class_name, "Node2D", false); + constructors.string_name_new_with_latin1_chars(&method_name, "set_position", false); + methods.node2d_set_position = api.classdb_get_method_bind(&native_class_name, &method_name, 743155724); + destructors.string_name_destructor(&native_class_name); + destructors.string_name_destructor(&method_name); + + ... + } + +Here we create ``StringName``'s for the class and method we want to get, then use +the GDExtension API to retrieve their ``MethodBind``, which is an object that +represents the bound method. We get the ``set_position`` method from ``Node2D`` +since this is where it was registered, even though we're going to use it in a +``Sprite2D``, a derived class. + +The seemingly random number for getting the bind is actually a hash of the +method signature. This allows Godot to match the method you're requesting even +if in a future Godot version this signature changes, by providing a +compatibility method that matches what you're asking for. This is one of the +systems that allow the engine to load extensions made for previous versions. You +can get the value of this hash from the ``extension_api.json`` file. + +With all that, we can finally implement our custom ``_process()`` method in the +``gdexample.c`` file: + +.. code-block:: c + + ... + + #include + + ... + + void gdexample_class_process(GDExample *self, double delta) + { + self->time_passed += self->speed * delta; + + Vector2 new_position; + + // Set up the arguments for the Vector2 constructor. + double x = self->amplitude + (self->amplitude * sin(self->time_passed * 2.0)); + double y = self->amplitude + (self->amplitude * cos(self->time_passed * 1.5)); + GDExtensionConstTypePtr args[] = {&x, &y}; + // Call the Vector2 constructor. + constructors.vector2_constructor_x_y(&new_position, args); + + // Set up the arguments for the set_position method. + GDExtensionConstTypePtr args2[] = {&new_position}; + // Call the set_position method. + api.object_method_bind_ptrcall(methods.node2d_set_position, self->object, args2, NULL); + } + +After updating the time passed scaled by the ``speed`` property, it creates +``x`` and ``y`` values based on that, also modulated by the ``amplitude`` +property. This is what will give the pattern effect. The ``math.h`` header is +needed for the ``sin()`` and ``cos()`` functions used here. + +Then it sets up an array of arguments to construct a ``Vector2``, followed by +calling the constructor. It sets up another array of arguments and use it to +call the ``set_position()`` method via the bind we acquired previously. + +Since nothing here allocates any memory, there's not a need to cleanup. + +Now we can build the extension again and reopen Godot. Even in the editor you'll +see the custom sprite moving. + +.. image:: img/gdextension_c_moving_sprite.gif + +Try changing the **Speed** and **Amplitude** properties and see how the sprite +react. + +Registering and emitting a signal +--------------------------------- + +To complete this tutorial, let's see how you can register a custom signal and +emit it when appropriate. As you might have guessed, we'll need a few more +function pointers from the API and more helper functions. + +In the ``api.h`` file we're adding two things. One is a an API function to +register a signal, the other is a helper function to wrap the signal binding. + +.. code-block:: c + + struct API + { + ... + GDExtensionInterfaceClassdbRegisterExtensionClassSignal classdb_register_extension_class_signal; + } api; + + ... + + // Version for 1 argument. + void bind_signal_1( + const char *class_name, + const char *signal_name, + const char *arg1_name, + GDExtensionVariantType arg1_type); + +In this case we only have a version for one argument, since it's what we're +going to use. + +Moving to the ``api.c`` file, we can load this new function pointer and +implement the helper: + +.. code-block:: c + + void load_api(GDExtensionInterfaceGetProcAddress p_get_proc_address) + { + // API. + ... + api.classdb_register_extension_class_signal = p_get_proc_address("classdb_register_extension_class_signal"); + + ... + } + + void bind_signal_1( + const char *class_name, + const char *signal_name, + const char *arg1_name, + GDExtensionVariantType arg1_type) + { + StringName class_string_name; + constructors.string_name_new_with_latin1_chars(&class_string_name, class_name, false); + StringName signal_string_name; + constructors.string_name_new_with_latin1_chars(&signal_string_name, signal_name, false); + + GDExtensionPropertyInfo args_info[] = { + make_property(arg1_type, arg1_name), + }; + + api.classdb_register_extension_class_signal(class_library, &class_string_name, &signal_string_name, args_info, 1); + + // Destruct things. + destructors.string_name_destructor(&class_string_name); + destructors.string_name_destructor(&signal_string_name); + destruct_property(&args_info[0]); + } + +This one is very similar to the function to bind methods. The main difference is +that we don't need to fill another struct, we just pass the needed names and the +array of arguments. The ``1`` at the end means the amount of arguments the +signal provides. + +With this we can bind the signal in ``gdexample.c``: + +.. code-block:: c + + void gdexample_class_bind_methods() + { + ... + bind_signal_1("GDExample", "position_changed", "new_position", GDEXTENSION_VARIANT_TYPE_VECTOR2); + } + +In order to emit a signal, we need to call the +:ref:`emit_signal() ` method on our custom node. +Since this is a ``vararg`` function (meaning it takes any amount of arguments), +we cannot use ``ptrcall``. To do a regular call, we have to create Variants, +which require a few more steps of plumbing to get done. + +First, in the ``defs.h`` file we create a definition for Variant: + +.. code-block:: c + + ... + + // The sizes can be obtained from the extension_api.json file. + ... + #ifdef REAL_T_IS_DOUBLE + #define VARIANT_SIZE 40 + #define VECTOR2_SIZE 16 + #else + #define VARIANT_SIZE 24 + #define VECTOR2_SIZE 8 + #endif + + ... + + // Types. + + ... + + typedef struct + { + uint8_t data[VARIANT_SIZE]; + } Variant; + + +We first set the size of Variant together with the size of Vector2 that we added +before. Then we use it to create an opaque struct that is enough to hold the +Variant data. Again, we set the size for double precision builds as a fallback, +since by the official Godot builds use single precision. + +The ``emit_signal()`` function will be called with two arguments. The first is +the name of the signal to be emitted and the second is the argument we're +passing to the signal connections, which is a Vector2 as we declared when +binding it. So we're gonna create a helper function that can call a MethodBind +with these types. Even though it does return something (an error code), we don't +need to deal with it, so for now we're just going to ignore it. + +In the ``api.h``, we're adding a few things to the existing structs, plus a new +helper function for the call: + +.. code-block:: c + + struct Constructors + { + ... + GDExtensionVariantFromTypeConstructorFunc variant_from_string_name_constructor; + GDExtensionVariantFromTypeConstructorFunc variant_from_vector2_constructor; + } constructors; + + struct Destructors + { + .. + GDExtensionInterfaceVariantDestroy variant_destroy; + } destructors; + + ... + + struct Methods + { + ... + GDExtensionMethodBindPtr object_emit_signal; + } methods; + + struct API + { + ... + GDExtensionInterfaceObjectMethodBindCall object_method_bind_call; + } api; + + ... + + // Helper to call with Variant arguments. + void call_2_args_stringname_vector2_no_ret_variant( + GDExtensionMethodBindPtr p_method_bind, + GDExtensionObjectPtr p_instance, + const GDExtensionTypePtr p_arg1, + const GDExtensionTypePtr p_arg2); + +Now let's switch to the ``api.c`` file to load these new function pointers and +implement the helper function. + +.. code-block:: c + + void load_api(GDExtensionInterfaceGetProcAddress p_get_proc_address) + { + // API. + ... + api.object_method_bind_call = p_get_proc_address("object_method_bind_call"); + + // Constructors. + ... + constructors.variant_from_string_name_constructor = api.get_variant_from_type_constructor(GDEXTENSION_VARIANT_TYPE_STRING_NAME); + constructors.variant_from_vector2_constructor = api.get_variant_from_type_constructor(GDEXTENSION_VARIANT_TYPE_VECTOR2); + + // Destructors. + ... + destructors.variant_destroy = p_get_proc_address("variant_destroy"); + + ... + } + + ... + + void call_2_args_stringname_vector2_no_ret_variant(GDExtensionMethodBindPtr p_method_bind, GDExtensionObjectPtr p_instance, const GDExtensionTypePtr p_arg1, const GDExtensionTypePtr p_arg2) + { + // Set up the arguments for the call. + Variant arg1; + constructors.variant_from_string_name_constructor(&arg1, p_arg1); + Variant arg2; + constructors.variant_from_vector2_constructor(&arg2, p_arg2); + GDExtensionConstVariantPtr args[] = {&arg1, &arg2}; + + // Add dummy return value storage. + Variant ret; + + // Call the function. + api.object_method_bind_call(p_method_bind, p_instance, args, 2, &ret, NULL); + + // Destroy the arguments that need it. + destructors.variant_destroy(&arg1); + destructors.variant_destroy(&ret); + } + +This helper function has some boilerplate code but is quite straightforward. It sets up the +two arguments inside stack allocated Variants, then creates an array with +pointers to those. It also sets up another Variant to keep the return value, +which we don't need to construct since the call expects it to be uninitialized. + +Then it actually calls the MethodBind using the instance we provided and the +arguments. The ``NULL`` at the end would be a pointer to a +``GDExtensionCallError`` struct. This can be used to treat potential errors when +calling the functions (such as wrong arguments). For the sake of simplicity +we're not gonna handle that here. + +At the end we need to destruct the Variants we created. While technically the +Vector2 one does not require destructing, it is clearer to cleanup everything. + +We also need to load the MethodBind, which we'll do in the ``init.c`` file, +right after loading the one for the ``set_position`` method we did before: + +.. code-block:: c + + void initialize_gdexample_module(void *p_userdata, GDExtensionInitializationLevel p_level) + { + ... + + constructors.string_name_new_with_latin1_chars(&native_class_name, "Object", false); + constructors.string_name_new_with_latin1_chars(&method_name, "emit_signal", false); + methods.object_emit_signal = api.classdb_get_method_bind(&native_class_name, &method_name, 4047867050); + destructors.string_name_destructor(&native_class_name); + destructors.string_name_destructor(&method_name); + + // Register class. + ... + } + +Note that we reuse the ``native_class_name`` and ``method_name`` variables here, +so we don't need to declare new ones. + +Now go to the ``gdexample.h`` file where we're going to add a couple of fields: + +.. code-block:: c + + typedef struct + { + // Private properties. + .. + double time_emit; + .. + // Metadata. + StringName position_changed; // For signal. + } GDExample; + +The first one will store the time passed since the last signal was emitted, +since we'll be doing so at regular intervals. The other is just to cache the +signal name so we don't need to create a new StringName every time. + +In the source ``gdexample.c`` file we can change the constructor and destructor +to deal with the new fields: + +.. code-block:: c + + void gdexample_class_constructor(GDExample *self) + { + ... + self->time_emit = 0.0; + + // Construct the StringName for the signal. + constructors.string_name_new_with_latin1_chars(&self->position_changed, "position_changed", false); + } + + void gdexample_class_destructor(GDExample *self) + { + // Destruct the StringName for the signal. + destructors.string_name_destructor(&self->position_changed); + } + +It is important to destruct the StringName to avoid memory leaks. + +Now we can add to the ``gdexample_class_process()`` function to actually emit the +signal: + +.. code-block:: c + + void gdexample_class_process(GDExample *self, double delta) + { + ... + + self->time_emit += delta; + if (self->time_emit >= 1.0) + { + // Call the emit_signal method. + call_2_args_stringname_vector2_no_ret_variant(methods.object_emit_signal, self->object, &self->position_changed, &new_position); + self->time_emit = 0.0; + } + } + +This updates the time passed for the signal emission and, if it is over one +second it calls the ``emit_signal()`` function on the current instance, passing +the name of the signal and the new position as arguments. + +Now we're done with our C GDExtension. Build it once more and reopen the demo +project in the editor. + +In the documentation page for ``GDExample`` you can see the new signal we bound: + +.. image:: img/gdextension_c_signal_doc.webp + +To check it's working, let's add a small script to the root node, parent of our +custom one, that prints the position to the output every time it receives the +signal: + +.. code-block:: gdscript + + extends Node2D + + func _ready(): + $GDExample.position_changed.connect(on_position_changed) + + func on_position_changed(new_position): + prints("New position:", new_position) + +Run the project and you can observe the values being printed in the Output dock +in the editor: + +.. image:: img/gdextension_c_signal_print.webp + +Conclusion +---------- + +This tutorial shows a basic extension with custom methods, properties, and +signals. While it does require a good amount of boilerplate, it can scale well +by creating helper functions to handle the tedious tasks. + +This should serve as a good basis to understand the GDExtension API and as a +starting point to create custom binding generators. In fact, it would be +possible to create bindings for C using such type of generator, making the +actual coding look more like the ``gdexample.c`` file in this example, which is +quite straightforward and not very verbose. + +If you want to create actual extensions, it is preferred to use the C++ bindings +instead, as it takes away all of the boilerplate from your code. Check the +:ref:`GDExtension C++ example ` to see how you can +do this. diff --git a/tutorials/scripting/gdextension/img/gdextension_c_inspector_properties.webp b/tutorials/scripting/gdextension/img/gdextension_c_inspector_properties.webp new file mode 100644 index 00000000000..532b0887252 Binary files /dev/null and b/tutorials/scripting/gdextension/img/gdextension_c_inspector_properties.webp differ diff --git a/tutorials/scripting/gdextension/img/gdextension_c_methods_doc.webp b/tutorials/scripting/gdextension/img/gdextension_c_methods_doc.webp new file mode 100644 index 00000000000..160faf2cda0 Binary files /dev/null and b/tutorials/scripting/gdextension/img/gdextension_c_methods_doc.webp differ diff --git a/tutorials/scripting/gdextension/img/gdextension_c_moving_sprite.gif b/tutorials/scripting/gdextension/img/gdextension_c_moving_sprite.gif new file mode 100644 index 00000000000..63f367d622d Binary files /dev/null and b/tutorials/scripting/gdextension/img/gdextension_c_moving_sprite.gif differ diff --git a/tutorials/scripting/gdextension/img/gdextension_c_running.webp b/tutorials/scripting/gdextension/img/gdextension_c_running.webp new file mode 100644 index 00000000000..2f9fa5ab3ee Binary files /dev/null and b/tutorials/scripting/gdextension/img/gdextension_c_running.webp differ diff --git a/tutorials/scripting/gdextension/img/gdextension_c_signal_doc.webp b/tutorials/scripting/gdextension/img/gdextension_c_signal_doc.webp new file mode 100644 index 00000000000..e8faa39c534 Binary files /dev/null and b/tutorials/scripting/gdextension/img/gdextension_c_signal_doc.webp differ diff --git a/tutorials/scripting/gdextension/img/gdextension_c_signal_print.webp b/tutorials/scripting/gdextension/img/gdextension_c_signal_print.webp new file mode 100644 index 00000000000..7955336b6d0 Binary files /dev/null and b/tutorials/scripting/gdextension/img/gdextension_c_signal_print.webp differ diff --git a/tutorials/scripting/gdextension/index.rst b/tutorials/scripting/gdextension/index.rst index 952145334da..7aec6a9c749 100644 --- a/tutorials/scripting/gdextension/index.rst +++ b/tutorials/scripting/gdextension/index.rst @@ -9,5 +9,6 @@ GDExtension what_is_gdextension gdextension_cpp_example + gdextension_c_example gdextension_file gdextension_docs_system diff --git a/tutorials/scripting/gdextension/what_is_gdextension.rst b/tutorials/scripting/gdextension/what_is_gdextension.rst index cdcb789b2d3..f2a10be882d 100644 --- a/tutorials/scripting/gdextension/what_is_gdextension.rst +++ b/tutorials/scripting/gdextension/what_is_gdextension.rst @@ -101,6 +101,8 @@ The bindings below are developed and maintained by the community: Also, double-check whether the binding is compatible with the Godot version you're using. +.. _doc_what_is_gdextension_version_compatibility: + Version compatibility ---------------------