Skip to content

Instantly share code, notes, and snippets.

@stkptr
Created April 21, 2024 19:21
Show Gist options
  • Save stkptr/190c8a3bd174fdb0551f4c44c5af14df to your computer and use it in GitHub Desktop.
Save stkptr/190c8a3bd174fdb0551f4c44c5af14df to your computer and use it in GitHub Desktop.
Overview of Godot modules and GDExtension

Extending Godot: Modules and Extensions

Godot is an extensible open source game engine. Typically the custom GDScript language is used to develop games and tools with the engine, but Godot supports two methods for extending the engine using C and C++ (and Rust, etc. through bindings).

Modules are integrated as core parts of the engine. Most of Godot is implemented as modules. GDScript for instance is a module. Modules are typically statically compiled into the engine, meaning that the core of Godot and all of the modules will be compiled together into a single executable. Modules can be compiled as shared libraries, but this is intended for debugging and not recommended for production.

Extensions use the GDExtension API and are not compiled with the engine. Instead, extensions are compiled separately into shared libraries. When the engine starts it links up with any shared libraries in the same directory as the executable.

Module advantages

  • Allows for more optimizations
  • Supports all platforms that Godot supports, including web
  • Has access to more interfaces, deeper engine integration

Extension advantages

  • Godot itself does not need to be recompiled, so use with PCK allows for the final export to not change the signature of the Godot executable so executable signing is not required
  • Updating games with extensions is easier, the extension SO/DLL/framework files can be freely swapped out in tandem with e.g. PCK patching, allowing for incremental updates. Modules compiled into the engine will require the entire executable to be replaced.
  • Users can easily use multiple extensions at the same time by simply adding them to their project. With modules all of the modules need to be compiled in, and finding a precompiled build for a specific combination of modules can be difficult.
Feature Modules Extensions
Use C/C++/Rust/etc. to extend the engine Yes Yes
Easily use non-C++ to extend the engine No Yes
Bind libraries to GDScript Yes Yes
Wide platform support Yes Almost
Web support Yes Chrome only
Allows for more optimizations Yes No
Supports incremental updates No Yes
Doesn't change the engine's signature No Yes
Simple export process for end users Depends Yes
Simple export process for users using multiple No Yes

In this context, end users are people who use your module/extension in their game or other Godot project. A simple export process is one which doesn't involve compilation.

Modules and extensions are different technologies which have different features. However, in many situations their source code looks the same. Extensions which are made in C++ using godot-cpp will have source code that looks very similar to source code for a module which serves the same function.

For certain extensions/modules it may be possible to create a hybrid module-extension. Hybrids are both modules and extensions, they can be compiled into either depending on the context. This allows for users of the hybrid to choose if they want to use it as a module or extension. Users requiring optimized exports can compile it into the engine as a module, users desiring a simple export process can use it as an extension, etc.

Structure of a module

Modules have a specific structure, if the structure is not adhered to the engine will not compile the module.

- MODULE_DIRECTORY
+- register_types.h
+- register_types.cpp
+- config.py
+- SCsub

Before attempting to compile the module, Godot will call into config.py with the target platform it's building to in order to check that the module can be built for that platform. If it can be, it will run the SCsub file, which is an SConstruct build instruction file. SCsub will add a list of source files that Godot will compile and link with. The file can also add additional libraries to link with, in addition.

Inside register_types there must be the following functions:

void initialize_MODULE_DIRECTORY_module(ModuleInitializationLevel p_level);
void uninitialize_MODULE_DIRECTORY_module(ModuleInitializationLevel p_level);

Where MODULE_DIRECTORY is the name of the directory the module is in.

Structure of an extension

Extensions have no concrete structure, instead every extension is defined by a .gdextension file. This file looks something like this:

[configuration]

entry_symbol = "EXTENSION_init"
compatibility_minimum = "4.2"

[libraries]

windows.debug.x86_64 = "windows/libextension.windows.template_debug.x86_64.dll"
windows.release.x86_64 = "windows/libextension.windows.template_release.x86_64.dll"
linux.debug.x86_64 = "linux/libextension.linux.template_debug.x86_64.so"
linux.release.x86_64 = "linux/libextension.linux.template_release.x86_64.so"

In the configuration section, the entry_symbol key tells Godot which function to call to determine how to initialize the extension. This is absolutely required and if it doesn't match the name of your entry function your extension will never be called by the engine, meaning it can't register anything. Of course, the symbol must be callable using C calling conventions. The entry function is not your initialization function, instead it should define the initialization and deinitialization (terminator) functions with the engine.

Also in the configuration section is the compatibility_minimum key. Godot should check this, and if the number is higher than its version it should refuse to initialize the extension. You should set this to the GDExtension API version you've compiled against.

The libraries section is a list of library files. Each key is a dot-separated list of features. The two most important features are the platform and target. In the above example, library files for release and debug (targets) exports and windows and linux platforms are defined. Additional features can be specified, like architecture (x86_64, x86_32, arm64, ppc32, etc.). Godot will pick the most specific library available, e.g. if exporting a release build to Linux on x86_64 and there's just a linux key then it will use that. If there's a linux key and a linux.x86_32 key then it will use the linux library. However, if there's a linux and linux.release key then it will use linux.release. Generally you will only specify a release and debug build for each platform.

The values for the libraries keys are the paths to the corresponding library files. The path can be any type of path supported by Godot, however res:// and relative are recommended. res:// paths are absolute (or relative to the project directory) if your extension uses res:// to locate the library files the user must put the extension in a specific location in their project. Relative paths can also be used, as in the above example. Relative paths locate the library files relative to the directory that the .gdextension file exists in. E.g.:

- My Project
+- addons
 +- extension
  +- extension.gdextension
  +- linux
   +- linux.so
+- assets
+- ...

In this example, the Linux binary can be referenced in two ways:

# absolute
linux = "res://addons/extension/linux/linux.so"
# relative
linux = "linux/linux.so"

In the absolute case if the user decides to rename the addons/extension directory to custom_extension Godot will be unable to find the Linux binary. If they did the same in the relative case Godot would be able to find the binary in the new location.

Structure of a hybrid

Since a hybrid must be able to be compiled as a module and extension the structure is determined by the most strict of the two. Currently modules have the strictest requirements, so a hybrid has the same basic structure of a module.

Hybrids must also compile to a shared library which can be referenced by a .gdextension file.

Both modules and extensions require an initialization function to be defined. In the case of modules this function has a specific, required name. In the case of extensions, the initialization function is registered by the entry function which is defined in the .gdextension file.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment