1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167 |
- .. _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 <https://github.com/godotengine/godot-cpp>`__.
- 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 <stdbool.h>
- #include <stddef.h>
- #include <stdint.h>
- #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 <class_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 <class_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 <doc_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 <doc_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() <class_Node_private_method__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 <math.h>
- ...
- 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() <class_Object_method_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 <doc_gdextension_cpp_example>` to see how you can
- do this.
|