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 compile time optimizations since the module is statically linked
- 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.
Modules and extensions do have different capabilities as well as ideal use cases, but they also share a lot of functionality. If a C/C++, Rust, or other precompiled library is needed in GDScript, either a module or an extension can be used to bind the library to GDScript by creating call through methods. In general, either can be used to interface with native or otherwise non-GDScript code, making things like raw network requests, device-specific behaviors, or anything else requiring native code available to GDScript.
Feature (view above for details) | 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 users | Depends | Yes |
Simple export process for users using multiple | No | Yes |
In this context, 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.
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.
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.
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.
And what does the last line of the comparison say:
Simple export process for users using multiple
.. multiple what? ๐