Split "module API" articles into their own engine details section, away from "architecture".

This commit is contained in:
Lukas Tenbrink
2026-01-18 18:48:27 +01:00
parent 40bb648ad1
commit 7e6dd93616
10 changed files with 46 additions and 32 deletions

View File

@@ -0,0 +1,223 @@
.. _doc_binding_to_external_libraries:
Binding to external libraries
=============================
Modules
-------
The Summator example in :ref:`doc_custom_modules_in_cpp` is great for small,
custom modules, but what if you want to use a larger, external library?
Let's look at an example using `Festival <https://www.cstr.ed.ac.uk/projects/festival/>`_,
a speech synthesis (text-to-speech) library written in C++.
To bind to an external library, set up a module directory similar to the Summator example:
.. code-block:: none
godot/modules/tts/
Next, you will create a header file with a TTS class:
.. code-block:: cpp
:caption: godot/modules/tts/tts.h
#pragma once
#include "core/object/ref_counted.h"
class TTS : public RefCounted {
GDCLASS(TTS, RefCounted);
protected:
static void _bind_methods();
public:
bool say_text(String p_txt);
TTS();
};
And then you'll add the cpp file.
.. code-block:: cpp
:caption: godot/modules/tts/tts.cpp
#include "tts.h"
#include <festival.h>
bool TTS::say_text(String p_txt) {
//convert Godot String to Godot CharString to C string
return festival_say_text(p_txt.ascii().get_data());
}
void TTS::_bind_methods() {
ClassDB::bind_method(D_METHOD("say_text", "txt"), &TTS::say_text);
}
TTS::TTS() {
festival_initialize(true, 210000); //not the best way to do it as this should only ever be called once.
}
Just as before, the new class needs to be registered somehow, so two more files
need to be created:
.. code-block:: none
register_types.h
register_types.cpp
.. important::
These files must be in the top-level folder of your module (next to your
``SCsub`` and ``config.py`` files) for the module to be registered properly.
These files should contain the following:
.. code-block:: cpp
:caption: godot/modules/tts/register_types.h
void initialize_tts_module(ModuleInitializationLevel p_level);
void uninitialize_tts_module(ModuleInitializationLevel p_level);
/* yes, the word in the middle must be the same as the module folder name */
.. code-block:: cpp
:caption: godot/modules/tts/register_types.cpp
#include "register_types.h"
#include "core/object/class_db.h"
#include "tts.h"
void initialize_tts_module(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return;
}
ClassDB::register_class<TTS>();
}
void uninitialize_tts_module(ModuleInitializationLevel p_level) {
// Nothing to do here in this example.
}
Next, you need to create an ``SCsub`` file so the build system compiles
this module:
.. code-block:: python
:caption: godot/modules/tts/SCsub
Import('env')
env_tts = env.Clone()
env_tts.add_source_files(env.modules_sources, "*.cpp") # Add all cpp files to the build
You'll need to install the external library on your machine to get the .a library files. See the library's official
documentation for specific instructions on how to do this for your operating system. We've included the
installation commands for Linux below, for reference.
.. code-block:: shell
sudo apt-get install festival festival-dev # Installs festival and speech_tools libraries
apt-cache search festvox-* # Displays list of voice packages
sudo apt-get install festvox-don festvox-rablpc16k festvox-kallpc16k festvox-kdlpc16k # Installs voices
.. important::
The voices that Festival uses (and any other potential external/3rd-party
resource) all have varying licenses and terms of use; some (if not most) of them may be
be problematic with Godot, even if the Festival Library itself is MIT License compatible.
Please be sure to check the licenses and terms of use.
The external library will also need to be installed inside your module to make the source
files accessible to the compiler, while also keeping the module code self-contained. The
festival and speech_tools libraries can be installed from the modules/tts/ directory via
git using the following commands:
.. code-block:: shell
git clone https://github.com/festvox/festival
git clone https://github.com/festvox/speech_tools
If you don't want the external repository source files committed to your repository, you
can link to them instead by adding them as submodules (from within the modules/tts/ directory), as seen below:
.. code-block:: shell
git submodule add https://github.com/festvox/festival
git submodule add https://github.com/festvox/speech_tools
.. important::
Please note that Git submodules are not used in the Godot repository. If
you are developing a module to be merged into the main Godot repository, you should not
use submodules. If your module doesn't get merged in, you can always try to implement
the external library as a GDExtension.
To add include directories for the compiler to look at you can append it to the
environment's paths:
.. code-block:: python
:caption: godot/modules/tts/SCsub
# These paths are relative to /modules/tts/
env_tts.Append(CPPPATH=["speech_tools/include", "festival/src/include"])
# LIBPATH and LIBS need to be set on the real "env" (not the clone)
# to link the specified libraries to the Godot executable.
# This is an absolute path where your .a libraries reside.
# If using a relative path, you must convert it to a
# full path using a utility function, such as `Dir('...').abspath`.
env.Append(LIBPATH=[Dir('libpath').abspath])
# Check with the documentation of the external library to see which library
# files should be included/linked.
env.Append(LIBS=['Festival', 'estools', 'estbase', 'eststring'])
If you want to add custom compiler flags when building your module, you need to clone
`env` first, so it won't add those flags to whole Godot build (which can cause errors).
Example `SCsub` with custom flags:
.. code-block:: python
:caption: godot/modules/tts/SCsub
Import('env')
env_tts = env.Clone()
env_tts.add_source_files(env.modules_sources, "*.cpp")
# Append CCFLAGS flags for both C and C++ code.
env_tts.Append(CCFLAGS=['-O2'])
# If you need to, you can:
# - Append CFLAGS for C code only.
# - Append CXXFLAGS for C++ code only.
The final module should look like this:
.. code-block:: none
godot/modules/tts/festival/
godot/modules/tts/libpath/libestbase.a
godot/modules/tts/libpath/libestools.a
godot/modules/tts/libpath/libeststring.a
godot/modules/tts/libpath/libFestival.a
godot/modules/tts/speech_tools/
godot/modules/tts/config.py
godot/modules/tts/tts.h
godot/modules/tts/tts.cpp
godot/modules/tts/register_types.h
godot/modules/tts/register_types.cpp
godot/modules/tts/SCsub
Using the module
----------------
You can now use your newly created module from any script:
::
var t = TTS.new()
var script = "Hello world. This is a test!"
var is_spoken = t.say_text(script)
print('is_spoken: ', is_spoken)
And the output will be ``is_spoken: True`` if the text is spoken.

View File

@@ -0,0 +1,349 @@
.. _doc_custom_audiostreams:
Custom AudioStreams
===================
Introduction
------------
AudioStream is the base class of all audio emitting objects.
AudioStreamPlayer binds onto an AudioStream to emit PCM data
into an AudioServer which manages audio drivers.
All audio resources require two audio based classes: AudioStream
and AudioStreamPlayback. As a data container, AudioStream contains
the resource and exposes itself to GDScript. AudioStream references
its own internal custom AudioStreamPlayback which translates
AudioStream into PCM data.
This guide assumes the reader knows how to create C++ modules. If not, refer to this guide
:ref:`doc_custom_modules_in_cpp`.
References:
~~~~~~~~~~~
- `servers/audio/audio_stream.h <https://github.com/godotengine/godot/blob/master/servers/audio/audio_stream.h>`__
- `scene/audio/audio_stream_player.cpp <https://github.com/godotengine/godot/blob/master/scene/audio/audio_stream_player.cpp>`__
What for?
---------
- Binding external libraries (like Wwise, FMOD, etc).
- Adding custom audio queues
- Adding support for more audio formats
Create an AudioStream
---------------------
An AudioStream consists of three components: data container, stream name,
and an AudioStreamPlayback friend class generator. Audio data can be
loaded in a number of ways such as with an internal counter for a tone generator,
internal/external buffer, or a file reference.
Some AudioStreams need to be stateless such as objects loaded from
ResourceLoader. ResourceLoader loads once and references the same
object regardless how many times ``load`` is called on a specific resource.
Therefore, playback state must be self-contained in AudioStreamPlayback.
.. code-block:: cpp
:caption: audiostream_mytone.h
#include "core/reference.h"
#include "core/resource.h"
#include "servers/audio/audio_stream.h"
class AudioStreamMyTone : public AudioStream {
GDCLASS(AudioStreamMyTone, AudioStream)
private:
friend class AudioStreamPlaybackMyTone;
uint64_t pos;
int mix_rate;
bool stereo;
int hz;
public:
void reset();
void set_position(uint64_t pos);
virtual Ref<AudioStreamPlayback> instance_playback();
virtual String get_stream_name() const;
void gen_tone(int16_t *pcm_buf, int size);
virtual float get_length() const { return 0; } // if supported, otherwise return 0
AudioStreamMyTone();
protected:
static void _bind_methods();
};
.. code-block:: cpp
:caption: audiostream_mytone.cpp
#include "audiostream_mytone.h"
AudioStreamMyTone::AudioStreamMyTone()
: mix_rate(44100), stereo(false), hz(639) {
}
Ref<AudioStreamPlayback> AudioStreamMyTone::instance_playback() {
Ref<AudioStreamPlaybackMyTone> talking_tree;
talking_tree.instantiate();
talking_tree->base = Ref<AudioStreamMyTone>(this);
return talking_tree;
}
String AudioStreamMyTone::get_stream_name() const {
return "MyTone";
}
void AudioStreamMyTone::reset() {
set_position(0);
}
void AudioStreamMyTone::set_position(uint64_t p) {
pos = p;
}
void AudioStreamMyTone::gen_tone(int16_t *pcm_buf, int size) {
for (int i = 0; i < size; i++) {
pcm_buf[i] = 32767.0 * sin(2.0 * Math_PI * double(pos + i) / (double(mix_rate) / double(hz)));
}
pos += size;
}
void AudioStreamMyTone::_bind_methods() {
ClassDB::bind_method(D_METHOD("reset"), &AudioStreamMyTone::reset);
ClassDB::bind_method(D_METHOD("get_stream_name"), &AudioStreamMyTone::get_stream_name);
}
References:
~~~~~~~~~~~
- `servers/audio/audio_stream.h <https://github.com/godotengine/godot/blob/master/servers/audio/audio_stream.h>`__
Create an AudioStreamPlayback
-----------------------------
AudioStreamPlayer uses ``mix`` callback to obtain PCM data. The callback must match sample rate and fill the buffer.
Since AudioStreamPlayback is controlled by the audio thread, i/o and dynamic memory allocation are forbidden.
.. code-block:: cpp
:caption: audiostreamplayer_mytone.h
#include "core/reference.h"
#include "core/resource.h"
#include "servers/audio/audio_stream.h"
class AudioStreamPlaybackMyTone : public AudioStreamPlayback {
GDCLASS(AudioStreamPlaybackMyTone, AudioStreamPlayback)
friend class AudioStreamMyTone;
private:
enum {
PCM_BUFFER_SIZE = 4096
};
enum {
MIX_FRAC_BITS = 13,
MIX_FRAC_LEN = (1 << MIX_FRAC_BITS),
MIX_FRAC_MASK = MIX_FRAC_LEN - 1,
};
void *pcm_buffer;
Ref<AudioStreamMyTone> base;
bool active;
public:
virtual void start(float p_from_pos = 0.0);
virtual void stop();
virtual bool is_playing() const;
virtual int get_loop_count() const; // times it looped
virtual float get_playback_position() const;
virtual void seek(float p_time);
virtual void mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames);
virtual float get_length() const; // if supported, otherwise return 0
AudioStreamPlaybackMyTone();
~AudioStreamPlaybackMyTone();
};
.. code-block:: cpp
:caption: audiostreamplayer_mytone.cpp
#include "audiostreamplayer_mytone.h"
#include "core/math/math_funcs.h"
#include "core/print_string.h"
AudioStreamPlaybackMyTone::AudioStreamPlaybackMyTone()
: active(false) {
AudioServer::get_singleton()->lock();
pcm_buffer = AudioServer::get_singleton()->audio_data_alloc(PCM_BUFFER_SIZE);
zeromem(pcm_buffer, PCM_BUFFER_SIZE);
AudioServer::get_singleton()->unlock();
}
AudioStreamPlaybackMyTone::~AudioStreamPlaybackMyTone() {
if(pcm_buffer) {
AudioServer::get_singleton()->audio_data_free(pcm_buffer);
pcm_buffer = NULL;
}
}
void AudioStreamPlaybackMyTone::stop() {
active = false;
base->reset();
}
void AudioStreamPlaybackMyTone::start(float p_from_pos) {
seek(p_from_pos);
active = true;
}
void AudioStreamPlaybackMyTone::seek(float p_time) {
float max = get_length();
if (p_time < 0) {
p_time = 0;
}
base->set_position(uint64_t(p_time * base->mix_rate) << MIX_FRAC_BITS);
}
void AudioStreamPlaybackMyTone::mix(AudioFrame *p_buffer, float p_rate, int p_frames) {
ERR_FAIL_COND(!active);
if (!active) {
return;
}
zeromem(pcm_buffer, PCM_BUFFER_SIZE);
int16_t *buf = (int16_t *)pcm_buffer;
base->gen_tone(buf, p_frames);
for(int i = 0; i < p_frames; i++) {
float sample = float(buf[i]) / 32767.0;
p_buffer[i] = AudioFrame(sample, sample);
}
}
int AudioStreamPlaybackMyTone::get_loop_count() const {
return 0;
}
float AudioStreamPlaybackMyTone::get_playback_position() const {
return 0.0;
}
float AudioStreamPlaybackMyTone::get_length() const {
return 0.0;
}
bool AudioStreamPlaybackMyTone::is_playing() const {
return active;
}
Resampling
~~~~~~~~~~
Godot's AudioServer currently uses 44100 Hz sample rate. When other sample rates are
needed such as 48000, either provide one or use AudioStreamPlaybackResampled.
Godot provides cubic interpolation for audio resampling.
Instead of overloading ``mix``, AudioStreamPlaybackResampled uses ``_mix_internal`` to
query AudioFrames and ``get_stream_sampling_rate`` to query current mix rate.
.. code-block:: cpp
:caption: mytone_audiostream_resampled.h
#include "core/reference.h"
#include "core/resource.h"
#include "servers/audio/audio_stream.h"
class AudioStreamMyToneResampled;
class AudioStreamPlaybackResampledMyTone : public AudioStreamPlaybackResampled {
GDCLASS(AudioStreamPlaybackResampledMyTone, AudioStreamPlaybackResampled)
friend class AudioStreamMyToneResampled;
private:
enum {
PCM_BUFFER_SIZE = 4096
};
enum {
MIX_FRAC_BITS = 13,
MIX_FRAC_LEN = (1 << MIX_FRAC_BITS),
MIX_FRAC_MASK = MIX_FRAC_LEN - 1,
};
void *pcm_buffer;
Ref<AudioStreamMyToneResampled> base;
bool active;
protected:
virtual void _mix_internal(AudioFrame *p_buffer, int p_frames);
public:
virtual void start(float p_from_pos = 0.0);
virtual void stop();
virtual bool is_playing() const;
virtual int get_loop_count() const; // times it looped
virtual float get_playback_position() const;
virtual void seek(float p_time);
virtual float get_length() const; // if supported, otherwise return 0
virtual float get_stream_sampling_rate();
AudioStreamPlaybackResampledMyTone();
~AudioStreamPlaybackResampledMyTone();
};
.. code-block:: cpp
:caption: mytone_audiostream_resampled.cpp
#include "mytone_audiostream_resampled.h"
#include "core/math/math_funcs.h"
#include "core/print_string.h"
AudioStreamPlaybackResampledMyTone::AudioStreamPlaybackResampledMyTone()
: active(false) {
AudioServer::get_singleton()->lock();
pcm_buffer = AudioServer::get_singleton()->audio_data_alloc(PCM_BUFFER_SIZE);
zeromem(pcm_buffer, PCM_BUFFER_SIZE);
AudioServer::get_singleton()->unlock();
}
AudioStreamPlaybackResampledMyTone::~AudioStreamPlaybackResampledMyTone() {
if (pcm_buffer) {
AudioServer::get_singleton()->audio_data_free(pcm_buffer);
pcm_buffer = NULL;
}
}
void AudioStreamPlaybackResampledMyTone::stop() {
active = false;
base->reset();
}
void AudioStreamPlaybackResampledMyTone::start(float p_from_pos) {
seek(p_from_pos);
active = true;
}
void AudioStreamPlaybackResampledMyTone::seek(float p_time) {
float max = get_length();
if (p_time < 0) {
p_time = 0;
}
base->set_position(uint64_t(p_time * base->mix_rate) << MIX_FRAC_BITS);
}
void AudioStreamPlaybackResampledMyTone::_mix_internal(AudioFrame *p_buffer, int p_frames) {
ERR_FAIL_COND(!active);
if (!active) {
return;
}
zeromem(pcm_buffer, PCM_BUFFER_SIZE);
int16_t *buf = (int16_t *)pcm_buffer;
base->gen_tone(buf, p_frames);
for(int i = 0; i < p_frames; i++) {
float sample = float(buf[i]) / 32767.0;
p_buffer[i] = AudioFrame(sample, sample);
}
}
float AudioStreamPlaybackResampledMyTone::get_stream_sampling_rate() {
return float(base->mix_rate);
}
int AudioStreamPlaybackResampledMyTone::get_loop_count() const {
return 0;
}
float AudioStreamPlaybackResampledMyTone::get_playback_position() const {
return 0.0;
}
float AudioStreamPlaybackResampledMyTone::get_length() const {
return 0.0;
}
bool AudioStreamPlaybackResampledMyTone::is_playing() const {
return active;
}
References:
~~~~~~~~~~~
- `core/math/audio_frame.h <https://github.com/godotengine/godot/blob/master/core/math/audio_frame.h>`__
- `servers/audio/audio_stream.h <https://github.com/godotengine/godot/blob/master/servers/audio/audio_stream.h>`__
- `scene/audio/audio_stream_player.cpp <https://github.com/godotengine/godot/blob/master/scene/audio/audio_stream_player.cpp>`__

View File

@@ -0,0 +1,502 @@
.. _doc_custom_godot_servers:
Custom Godot servers
====================
Introduction
------------
Godot implements multi-threading as servers. Servers are daemons which
manage data, process it, and push the result. Servers implement the
mediator pattern which interprets resource ID and process data for the
engine and other modules. In addition, the server claims ownership for
its RID allocations.
This guide assumes the reader knows how to create C++ modules and Godot
data types. If not, refer to :ref:`doc_custom_modules_in_cpp`.
References
~~~~~~~~~~~
- `Why does Godot use servers and RIDs? <https://godotengine.org/article/why-does-godot-use-servers-and-rids>`__
- `Singleton pattern <https://en.wikipedia.org/wiki/Singleton_pattern>`__
- `Mediator pattern <https://en.wikipedia.org/wiki/Mediator_pattern>`__
What for?
---------
- Adding artificial intelligence.
- Adding custom asynchronous threads.
- Adding support for a new input device.
- Adding writing threads.
- Adding a custom VoIP protocol.
- And more...
Creating a Godot server
-----------------------
At minimum, a server must have a static instance, a sleep timer, a thread loop,
an initialization state and a cleanup procedure.
.. code-block:: cpp
:caption: hilbert_hotel.h
#pragma once
#include "core/object/object.h"
#include "core/os/thread.h"
#include "core/os/mutex.h"
#include "core/templates/list.h"
#include "core/templates/rid.h"
#include "core/templates/set.h"
#include "core/variant/variant.h"
class HilbertHotel : public Object {
GDCLASS(HilbertHotel, Object);
static HilbertHotel *singleton;
static void thread_func(void *p_udata);
private:
bool thread_exited;
mutable bool exit_thread;
Thread *thread;
Mutex *mutex;
public:
static HilbertHotel *get_singleton();
Error init();
void lock();
void unlock();
void finish();
protected:
static void _bind_methods();
private:
uint64_t counter;
RID_Owner<InfiniteBus> bus_owner;
// https://github.com/godotengine/godot/blob/master/core/templates/rid.h
Set<RID> buses;
void _emit_occupy_room(uint64_t room, RID rid);
public:
RID create_bus();
Variant get_bus_info(RID id);
bool empty();
bool delete_bus(RID id);
void clear();
void register_rooms();
HilbertHotel();
};
.. code-block:: cpp
:caption: hilbert_hotel.cpp
#include "hilbert_hotel.h"
#include "core/variant/dictionary.h"
#include "core/os/os.h"
#include "prime_225.h"
void HilbertHotel::thread_func(void *p_udata) {
HilbertHotel *ac = (HilbertHotel *) p_udata;
uint64_t msdelay = 1000;
while (!ac->exit_thread) {
if (!ac->empty()) {
ac->lock();
ac->register_rooms();
ac->unlock();
}
OS::get_singleton()->delay_usec(msdelay * 1000);
}
}
Error HilbertHotel::init() {
thread_exited = false;
counter = 0;
mutex = Mutex::create();
thread = Thread::create(HilbertHotel::thread_func, this);
return OK;
}
HilbertHotel *HilbertHotel::singleton = NULL;
HilbertHotel *HilbertHotel::get_singleton() {
return singleton;
}
void HilbertHotel::register_rooms() {
for (Set<RID>::Element *e = buses.front(); e; e = e->next()) {
auto bus = bus_owner.getornull(e->get());
if (bus) {
uint64_t room = bus->next_room();
_emit_occupy_room(room, bus->get_self());
}
}
}
void HilbertHotel::unlock() {
if (!thread || !mutex) {
return;
}
mutex->unlock();
}
void HilbertHotel::lock() {
if (!thread || !mutex) {
return;
}
mutex->lock();
}
void HilbertHotel::_emit_occupy_room(uint64_t room, RID rid) {
_HilbertHotel::get_singleton()->_occupy_room(room, rid);
}
Variant HilbertHotel::get_bus_info(RID id) {
InfiniteBus *bus = bus_owner.getornull(id);
if (bus) {
Dictionary d;
d["prime"] = bus->get_bus_num();
d["current_room"] = bus->get_current_room();
return d;
}
return Variant();
}
void HilbertHotel::finish() {
if (!thread) {
return;
}
exit_thread = true;
Thread::wait_to_finish(thread);
memdelete(thread);
if (mutex) {
memdelete(mutex);
}
thread = NULL;
}
RID HilbertHotel::create_bus() {
lock();
InfiniteBus *ptr = memnew(InfiniteBus(PRIME[counter++]));
RID ret = bus_owner.make_rid(ptr);
ptr->set_self(ret);
buses.insert(ret);
unlock();
return ret;
}
// https://github.com/godotengine/godot/blob/master/core/templates/rid.h
bool HilbertHotel::delete_bus(RID id) {
if (bus_owner.owns(id)) {
lock();
InfiniteBus *b = bus_owner.get(id);
bus_owner.free(id);
buses.erase(id);
memdelete(b);
unlock();
return true;
}
return false;
}
void HilbertHotel::clear() {
for (Set<RID>::Element *e = buses.front(); e; e = e->next()) {
delete_bus(e->get());
}
}
bool HilbertHotel::empty() {
return buses.size() <= 0;
}
void HilbertHotel::_bind_methods() {
}
HilbertHotel::HilbertHotel() {
singleton = this;
}
.. code-block:: cpp
:caption: prime_255.h
const uint64_t PRIME[225] = {
2,3,5,7,11,13,17,19,23,
29,31,37,41,43,47,53,59,61,
67,71,73,79,83,89,97,101,103,
107,109,113,127,131,137,139,149,151,
157,163,167,173,179,181,191,193,197,
199,211,223,227,229,233,239,241,251,
257,263,269,271,277,281,283,293,307,
311,313,317,331,337,347,349,353,359,
367,373,379,383,389,397,401,409,419,
421,431,433,439,443,449,457,461,463,
467,479,487,491,499,503,509,521,523,
541,547,557,563,569,571,577,587,593,
599,601,607,613,617,619,631,641,643,
647,653,659,661,673,677,683,691,701,
709,719,727,733,739,743,751,757,761,
769,773,787,797,809,811,821,823,827,
829,839,853,857,859,863,877,881,883,
887,907,911,919,929,937,941,947,953,
967,971,977,983,991,997,1009,1013,1019,
1021,1031,1033,1039,1049,1051,1061,1063,1069,
1087,1091,1093,1097,1103,1109,1117,1123,1129,
1151,1153,1163,1171,1181,1187,1193,1201,1213,
1217,1223,1229,1231,1237,1249,1259,1277,1279,
1283,1289,1291,1297,1301,1303,1307,1319,1321,
1327,1361,1367,1373,1381,1399,1409,1423,1427
};
Custom managed resource data
----------------------------
Godot servers implement a mediator pattern. All data types inherit ``RID_Data``.
``RID_Owner<MyRID_Data>`` owns the object when ``make_rid`` is called. During debug mode only,
RID_Owner maintains a list of RIDs. In practice, RIDs are similar to writing
object-oriented C code.
.. code-block:: cpp
:caption: infinite_bus.h
class InfiniteBus : public RID_Data {
RID self;
private:
uint64_t prime_num;
uint64_t num;
public:
uint64_t next_room() {
return prime_num * num++;
}
uint64_t get_bus_num() const {
return prime_num;
}
uint64_t get_current_room() const {
return prime_num * num;
}
_FORCE_INLINE_ void set_self(const RID &p_self) {
self = p_self;
}
_FORCE_INLINE_ RID get_self() const {
return self;
}
InfiniteBus(uint64_t prime) : prime_num(prime), num(1) {};
~InfiniteBus() {};
}
References
~~~~~~~~~~~
- :ref:`RID<class_rid>`
- `core/templates/rid.h <https://github.com/godotengine/godot/blob/master/core/templates/rid.h>`__
Registering the class in GDScript
---------------------------------
Servers are allocated in ``register_types.cpp``. The constructor sets the static
instance and ``init()`` creates the managed thread; ``unregister_types.cpp``
cleans up the server.
Since a Godot server class creates an instance and binds it to a static singleton,
binding the class might not reference the correct instance. Therefore, a dummy
class must be created to reference the proper Godot server.
In ``register_server_types()``, ``Engine::get_singleton()->add_singleton``
is used to register the dummy class in GDScript.
.. code-block:: cpp
:caption: register_types.h
/* Yes, the word in the middle must be the same as the module folder name */
void register_hilbert_hotel_types();
void unregister_hilbert_hotel_types();
.. code-block:: cpp
:caption: register_types.cpp
#include "register_types.h"
#include "core/object/class_db.h"
#include "core/config/engine.h"
#include "hilbert_hotel.h"
static HilbertHotel *hilbert_hotel = NULL;
static _HilbertHotel *_hilbert_hotel = NULL;
void register_hilbert_hotel_types() {
hilbert_hotel = memnew(HilbertHotel);
hilbert_hotel->init();
_hilbert_hotel = memnew(_HilbertHotel);
ClassDB::register_class<_HilbertHotel>();
Engine::get_singleton()->add_singleton(Engine::Singleton("HilbertHotel", _HilbertHotel::get_singleton()));
}
void unregister_hilbert_hotel_types() {
if (hilbert_hotel) {
hilbert_hotel->finish();
memdelete(hilbert_hotel);
}
if (_hilbert_hotel) {
memdelete(_hilbert_hotel);
}
}
- `servers/register_server_types.cpp <https://github.com/godotengine/godot/blob/master/servers/register_server_types.cpp>`__
Bind methods
~~~~~~~~~~~~
The dummy class binds singleton methods to GDScript. In most cases, the dummy class methods wraps around.
.. code-block:: cpp
Variant _HilbertHotel::get_bus_info(RID id) {
return HilbertHotel::get_singleton()->get_bus_info(id);
}
Binding Signals
It is possible to emit signals to GDScript by calling the GDScript dummy object.
.. code-block:: cpp
void HilbertHotel::_emit_occupy_room(uint64_t room, RID rid) {
_HilbertHotel::get_singleton()->_occupy_room(room, rid);
}
.. code-block:: cpp
class _HilbertHotel : public Object {
GDCLASS(_HilbertHotel, Object);
friend class HilbertHotel;
static _HilbertHotel *singleton;
protected:
static void _bind_methods();
private:
void _occupy_room(int room_number, RID bus);
public:
RID create_bus();
void connect_signals();
bool delete_bus(RID id);
static _HilbertHotel *get_singleton();
Variant get_bus_info(RID id);
_HilbertHotel();
~_HilbertHotel();
};
#endif
.. code-block:: cpp
_HilbertHotel *_HilbertHotel::singleton = NULL;
_HilbertHotel *_HilbertHotel::get_singleton() { return singleton; }
RID _HilbertHotel::create_bus() {
return HilbertHotel::get_singleton()->create_bus();
}
bool _HilbertHotel::delete_bus(RID rid) {
return HilbertHotel::get_singleton()->delete_bus(rid);
}
void _HilbertHotel::_occupy_room(int room_number, RID bus) {
emit_signal("occupy_room", room_number, bus);
}
Variant _HilbertHotel::get_bus_info(RID id) {
return HilbertHotel::get_singleton()->get_bus_info(id);
}
void _HilbertHotel::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_bus_info", "r_id"), &_HilbertHotel::get_bus_info);
ClassDB::bind_method(D_METHOD("create_bus"), &_HilbertHotel::create_bus);
ClassDB::bind_method(D_METHOD("delete_bus"), &_HilbertHotel::delete_bus);
ADD_SIGNAL(MethodInfo("occupy_room", PropertyInfo(Variant::INT, "room_number"), PropertyInfo(Variant::_RID, "r_id")));
}
void _HilbertHotel::connect_signals() {
HilbertHotel::get_singleton()->connect("occupy_room", _HilbertHotel::get_singleton(), "_occupy_room");
}
_HilbertHotel::_HilbertHotel() {
singleton = this;
}
_HilbertHotel::~_HilbertHotel() {
}
MessageQueue
------------
In order to send commands into SceneTree, MessageQueue is a thread-safe buffer
to queue set and call methods for other threads. To queue a command, obtain
the target object RID and use either ``push_call``, ``push_set``, or ``push_notification``
to execute the desired behavior. The queue will be flushed whenever either
``SceneTree::idle`` or ``SceneTree::iteration`` is executed.
References:
~~~~~~~~~~~
- `core/object/message_queue.cpp <https://github.com/godotengine/godot/blob/master/core/object/message_queue.cpp>`__
Summing it up
-------------
Here is the GDScript sample code:
::
extends Node
func _ready():
print("Start debugging")
HilbertHotel.occupy_room.connect(_print_occupy_room)
var rid = HilbertHotel.create_bus()
OS.delay_msec(2000)
HilbertHotel.create_bus()
OS.delay_msec(2000)
HilbertHotel.create_bus()
OS.delay_msec(2000)
print(HilbertHotel.get_bus_info(rid))
HilbertHotel.delete_bus(rid)
print("Ready done")
func _print_occupy_room(room_number, r_id):
print("Room number: " + str(room_number) + ", RID: " + str(r_id))
print(HilbertHotel.get_bus_info(r_id))
Notes
~~~~~
- The actual `Hilbert Hotel <https://en.wikipedia.org/wiki/Hilbert%27s_paradox_of_the_Grand_Hotel>`__ is impossible.
- Connecting signal example code is pretty hacky.

View File

@@ -0,0 +1,605 @@
.. _doc_custom_modules_in_cpp:
Custom modules in C++
=====================
Modules
-------
Godot allows extending the engine in a modular way. New modules can be
created and then enabled/disabled. This allows for adding new engine
functionality at every level without modifying the core, which can be
split for use and reuse in different modules.
Modules are located in the ``modules/`` subdirectory of the build system.
By default, dozens of modules are enabled, such as GDScript (which, yes,
is not part of the base engine), GridMap support, a regular expressions
module, and others. As many new modules as desired can be
created and combined. The SCons build system will take care of it
transparently.
What for?
---------
While it's recommended that most of a game be written in scripting (as
it is an enormous time saver), it's perfectly possible to use C++
instead. Adding C++ modules can be useful in the following scenarios:
- Binding an external library to Godot (like PhysX, FMOD, etc).
- Optimize critical parts of a game.
- Adding new functionality to the engine and/or editor.
- Porting an existing game to Godot.
- Write a whole, new game in C++ because you can't live without C++.
.. note::
While it is possible to use modules for custom game logic,
:ref:`GDExtension <doc_gdextension>` is generally more suited as it doesn't
require recompiling the engine after every code change.
C++ modules are mainly needed when GDExtension doesn't suffice and deeper engine
integration is required.
Creating a new module
---------------------
Before creating a module, make sure to :ref:`download the source code of Godot
and compile it <toc-devel-compiling>`.
To create a new module, the first step is creating a directory inside
``modules/``. If you want to maintain the module separately, you can checkout
a different VCS into modules and use it.
The example module will be called "summator" (``godot/modules/summator``).
Inside we will create a summator class:
.. code-block:: cpp
:caption: godot/modules/summator/summator.h
#pragma once
#include "core/object/ref_counted.h"
class Summator : public RefCounted {
GDCLASS(Summator, RefCounted);
int count;
protected:
static void _bind_methods();
public:
void add(int p_value);
void reset();
int get_total() const;
Summator();
};
And then the cpp file.
.. code-block:: cpp
:caption: godot/modules/summator/summator.cpp
#include "summator.h"
void Summator::add(int p_value) {
count += p_value;
}
void Summator::reset() {
count = 0;
}
int Summator::get_total() const {
return count;
}
void Summator::_bind_methods() {
ClassDB::bind_method(D_METHOD("add", "value"), &Summator::add);
ClassDB::bind_method(D_METHOD("reset"), &Summator::reset);
ClassDB::bind_method(D_METHOD("get_total"), &Summator::get_total);
}
Summator::Summator() {
count = 0;
}
Then, the new class needs to be registered somehow, so two more files
need to be created:
.. code-block:: none
register_types.h
register_types.cpp
.. important::
These files must be in the top-level folder of your module (next to your
``SCsub`` and ``config.py`` files) for the module to be registered properly.
These files should contain the following:
.. code-block:: cpp
:caption: godot/modules/summator/register_types.h
#include "modules/register_module_types.h"
void initialize_summator_module(ModuleInitializationLevel p_level);
void uninitialize_summator_module(ModuleInitializationLevel p_level);
/* yes, the word in the middle must be the same as the module folder name */
.. code-block:: cpp
:caption: godot/modules/summator/register_types.cpp
#include "register_types.h"
#include "core/object/class_db.h"
#include "summator.h"
void initialize_summator_module(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return;
}
ClassDB::register_class<Summator>();
}
void uninitialize_summator_module(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return;
}
// Nothing to do here in this example.
}
Next, we need to create an ``SCsub`` file so the build system compiles
this module:
.. code-block:: python
:caption: godot/modules/summator/SCsub
# SCsub
Import('env')
env.add_source_files(env.modules_sources, "*.cpp") # Add all cpp files to the build
With multiple sources, you can also add each file individually to a Python
string list:
.. code-block:: python
src_list = ["summator.cpp", "other.cpp", "etc.cpp"]
env.add_source_files(env.modules_sources, src_list)
This allows for powerful possibilities using Python to construct the file list
using loops and logic statements. Look at some modules that ship with Godot by
default for examples.
To add include directories for the compiler to look at you can append it to the
environment's paths:
.. code-block:: python
env.Append(CPPPATH=["mylib/include"]) # this is a relative path
env.Append(CPPPATH=["#myotherlib/include"]) # this is an 'absolute' path
If you want to add custom compiler flags when building your module, you need to clone
``env`` first, so it won't add those flags to whole Godot build (which can cause errors).
Example ``SCsub`` with custom flags:
.. code-block:: python
:caption: godot/modules/summator/SCsub
Import('env')
module_env = env.Clone()
module_env.add_source_files(env.modules_sources, "*.cpp")
# Append CCFLAGS flags for both C and C++ code.
module_env.Append(CCFLAGS=['-O2'])
# If you need to, you can:
# - Append CFLAGS for C code only.
# - Append CXXFLAGS for C++ code only.
And finally, the configuration file for the module, this is a
Python script that must be named ``config.py``:
.. code-block:: python
:caption: godot/modules/summator/config.py
# config.py
def can_build(env, platform):
return True
def configure(env):
pass
The module is asked if it's OK to build for the specific platform (in
this case, ``True`` means it will build for every platform).
And that's it. Hope it was not too complex! Your module should look like
this:
.. code-block:: none
godot/modules/summator/config.py
godot/modules/summator/summator.h
godot/modules/summator/summator.cpp
godot/modules/summator/register_types.h
godot/modules/summator/register_types.cpp
godot/modules/summator/SCsub
You can then zip it and share the module with everyone else. When
building for every platform (instructions in the previous sections),
your module will be included.
Using the module
----------------
You can now use your newly created module from any script:
.. tabs::
.. code-tab:: gdscript GDScript
var s = Summator.new()
s.add(10)
s.add(20)
s.add(30)
print(s.get_total())
s.reset()
The output will be ``60``.
.. seealso:: The previous Summator example is great for small, custom modules,
but what if you want to use a larger, external library? Refer to
:ref:`doc_binding_to_external_libraries` for details about binding to
external libraries.
.. warning:: If your module is meant to be accessed from the running project
(not just from the editor), you must also recompile every export
template you plan to use, then specify the path to the custom
template in each export preset. Otherwise, you'll get errors when
running the project as the module isn't compiled in the export
template. See the :ref:`Compiling <toc-devel-compiling>` pages
for more information.
Compiling a module externally
-----------------------------
Compiling a module involves moving the module's sources directly under the
engine's ``modules/`` directory. While this is the most straightforward way to
compile a module, there are a couple of reasons as to why this might not be a
practical thing to do:
1. Having to manually copy modules sources every time you want to compile the
engine with or without the module, or taking additional steps needed to
manually disable a module during compilation with a build option similar to
``module_summator_enabled=no``. Creating symbolic links may also be a solution,
but you may additionally need to overcome OS restrictions like needing the
symbolic link privilege if doing this via script.
2. Depending on whether you have to work with the engine's source code, the
module files added directly to ``modules/`` changes the working tree to the
point where using a VCS (like ``git``) proves to be cumbersome as you need to
make sure that only the engine-related code is committed by filtering
changes.
So if you feel like the independent structure of custom modules is needed, lets
take our "summator" module and move it to the engine's parent directory:
.. code-block:: shell
mkdir ../modules
mv modules/summator ../modules
Compile the engine with our module by providing ``custom_modules`` build option
which accepts a comma-separated list of directory paths containing custom C++
modules, similar to the following:
.. code-block:: shell
scons custom_modules=../modules
The build system shall detect all modules under the ``../modules`` directory
and compile them accordingly, including our "summator" module.
.. warning::
Any path passed to ``custom_modules`` will be converted to an absolute path
internally as a way to distinguish between custom and built-in modules. It
means that things like generating module documentation may rely on a
specific path structure on your machine.
.. seealso::
:ref:`Introduction to the buildsystem - Custom modules build option <doc_buildsystem_custom_modules>`.
Customizing module types initialization
---------------------------------------
Modules can interact with other built-in engine classes during runtime and even
affect the way core types are initialized. So far, we've been using
``register_summator_types`` as a way to bring in module classes to be available
within the engine.
A crude order of the engine setup can be summarized as a list of the following
type registration methods:
.. code-block:: cpp
preregister_module_types();
preregister_server_types();
register_core_singletons();
register_server_types();
register_scene_types();
EditorNode::register_editor_types();
register_platform_apis();
register_module_types();
initialize_physics();
initialize_navigation_server();
register_server_singletons();
register_driver_types();
ScriptServer::init_languages();
Our ``Summator`` class is initialized during the ``register_module_types()``
call. Imagine that we need to satisfy some common module runtime dependency
(like singletons), or allow us to override existing engine method callbacks
before they can be assigned by the engine itself. In that case, we want to
ensure that our module classes are registered *before* any other built-in type.
This is where we can define an optional ``preregister_summator_types()``
method which will be called before anything else during the
``preregister_module_types()`` engine setup stage.
We now need to add this method to ``register_types`` header and source files:
.. code-block:: cpp
:caption: godot/modules/summator/register_types.h
#define MODULE_SUMMATOR_HAS_PREREGISTER
void preregister_summator_types();
void register_summator_types();
void unregister_summator_types();
.. note:: Unlike other register methods, we have to explicitly define
``MODULE_SUMMATOR_HAS_PREREGISTER`` to let the build system know what
relevant method calls to include at compile time. The module's name
has to be converted to uppercase as well.
.. code-block:: cpp
:caption: godot/modules/summator/register_types.cpp
#include "register_types.h"
#include "core/object/class_db.h"
#include "summator.h"
void preregister_summator_types() {
// Called before any other core types are registered.
// Nothing to do here in this example.
}
void register_summator_types() {
ClassDB::register_class<Summator>();
}
void unregister_summator_types() {
// Nothing to do here in this example.
}
Writing custom documentation
----------------------------
Writing documentation may seem like a boring task, but it is highly recommended
to document your newly created module to make it easier for users to benefit
from it. Not to mention that the code you've written one year ago may become
indistinguishable from the code that was written by someone else, so be kind to
your future self!
There are several steps in order to setup custom docs for the module:
1. Make a new directory in the root of the module. The directory name can be
anything, but we'll be using the ``doc_classes`` name throughout this section.
2. Now, we need to edit ``config.py``, add the following snippet:
.. code-block:: python
def get_doc_path():
return "doc_classes"
def get_doc_classes():
return [
"Summator",
]
The ``get_doc_path()`` function is used by the build system to determine
the location of the docs. In this case, they will be located in the
``modules/summator/doc_classes`` directory. If you don't define this,
the doc path for your module will fall back to the main ``doc/classes``
directory.
The ``get_doc_classes()`` method is necessary for the build system to
know which registered classes belong to the module. You need to list all of your
classes here. The classes that you don't list will end up in the
main ``doc/classes`` directory.
.. tip::
You can use Git to check if you have missed some of your classes by checking the
untracked files with ``git status``. For example:
::
git status
Example output:
::
Untracked files:
(use "git add <file>..." to include in what will be committed)
doc/classes/MyClass2D.xml
doc/classes/MyClass4D.xml
doc/classes/MyClass5D.xml
doc/classes/MyClass6D.xml
...
3. Now we can generate the documentation:
We can do this via running Godot's doctool i.e. ``godot --doctool <path>``,
which will dump the engine API reference to the given ``<path>`` in XML format.
In our case we'll point it to the root of the cloned repository. You can point it
to an another folder, and just copy over the files that you need.
Run command:
::
bin/<godot_binary> --doctool .
Now if you go to the ``godot/modules/summator/doc_classes`` folder, you will see
that it contains a ``Summator.xml`` file, or any other classes, that you referenced
in your ``get_doc_classes`` function.
Edit the file(s) following the `class reference primer <https://contributing.godotengine.org/en/latest/documentation/class_reference/class_reference_primer.html>`__ and recompile the engine.
Once the compilation process is finished, the docs will become accessible within
the engine's built-in documentation system.
In order to keep documentation up-to-date, all you'll have to do is simply modify
one of the XML files and recompile the engine from now on.
If you change your module's API, you can also re-extract the docs, they will contain
the things that you previously added. Of course if you point it to your godot
folder, make sure you don't lose work by extracting older docs from an older engine build
on top of the newer ones.
Note that if you don't have write access rights to your supplied ``<path>``,
you might encounter an error similar to the following:
.. code-block:: console
ERROR: Can't write doc file: docs/doc/classes/@GDScript.xml
At: editor/doc/doc_data.cpp:956
.. _doc_custom_module_unit_tests:
Writing custom unit tests
-------------------------
It's possible to write self-contained unit tests as part of a C++ module. If you
are not familiar with the unit testing process in Godot yet, please refer to
:ref:`doc_unit_testing`.
The procedure is the following:
1. Create a new directory named ``tests/`` under your module's root:
.. code-block:: console
cd modules/summator
mkdir tests
cd tests
2. Create a new test suite: ``test_summator.h``. The header must be prefixed
with ``test_`` so that the build system can collect it and include it as part
of the ``tests/test_main.cpp`` where the tests are run.
3. Write some test cases. Here's an example:
.. code-block:: cpp
:caption: godot/modules/summator/tests/test_summator.h
#pragma once
#include "tests/test_macros.h"
#include "modules/summator/summator.h"
namespace TestSummator {
TEST_CASE("[Modules][Summator] Adding numbers") {
Ref<Summator> s = memnew(Summator);
CHECK(s->get_total() == 0);
s->add(10);
CHECK(s->get_total() == 10);
s->add(20);
CHECK(s->get_total() == 30);
s->add(30);
CHECK(s->get_total() == 60);
s->reset();
CHECK(s->get_total() == 0);
}
} // namespace TestSummator
4. Compile the engine with ``scons tests=yes``, and run the tests with the
following command:
.. code-block:: console
./bin/<godot_binary> --test --source-file="*test_summator*" --success
You should see the passing assertions now.
.. _doc_custom_module_icons:
Adding custom editor icons
--------------------------
Similarly to how you can write self-contained documentation within a module,
you can also create your own custom icons for classes to appear in the editor.
For the actual process of creating editor icons to be integrated within the engine,
please refer to :ref:`doc_editor_icons` first.
Once you've created your icon(s), proceed with the following steps:
1. Make a new directory in the root of the module named ``icons``. This is the
default path for the engine to look for module's editor icons.
2. Move your newly created ``svg`` icons (optimized or not) into that folder.
3. Recompile the engine and run the editor. Now the icon(s) will appear in
editor's interface where appropriate.
If you'd like to store your icons somewhere else within your module,
add the following code snippet to ``config.py`` to override the default path:
.. code-block:: python
def get_icons_path():
return "path/to/icons"
Summing up
----------
Remember to:
- Use ``GDCLASS`` macro for inheritance, so Godot can wrap it.
- Use ``_bind_methods`` to bind your functions to scripting, and to
allow them to work as callbacks for signals.
- **Avoid multiple inheritance for classes exposed to Godot**, as ``GDCLASS``
doesn't support this. You can still use multiple inheritance in your own
classes as long as they're not exposed to Godot's scripting API.
But this is not all, depending what you do, you will be greeted with
some (hopefully positive) surprises.
- If you inherit from :ref:`class_Node` (or any derived node type, such as
Sprite2D), your new class will appear in the editor, in the inheritance
tree in the "Add Node" dialog.
- If you inherit from :ref:`class_Resource`, it will appear in the resource
list, and all the exposed properties can be serialized when
saved/loaded.
- By this same logic, you can extend the Editor and almost any area of
the engine.

View File

@@ -0,0 +1,191 @@
.. _doc_custom_platform_ports:
Custom platform ports
=====================
Similar to :ref:`doc_custom_modules_in_cpp`, Godot's multi-platform architecture
is designed in a way that allows creating platform ports without modifying any
existing source code.
An example of a custom platform port distributed independently from the engine
is `FRT <https://github.com/efornara/frt>`__, which targets single-board
computers. Note that this platform port currently targets Godot 3.x; therefore,
it does not use the :ref:`class_DisplayServer` abstraction that is new in Godot 4.
Some reasons to create custom platform ports might be:
- You want to port your game to consoles
(see also the `Godot website on console support <https://godotengine.org/consoles/>`_),
but wish to write the platform layer yourself. This is a long and arduous process, as it
requires signing NDAs with console manufacturers, but it allows you to have
full control over the console porting process.
- You want to port Godot to an exotic platform that isn't currently supported.
If you have questions about creating a custom platform port, feel free to ask in
the ``#platforms`` channel of the
`Godot Contributors Chat <https://chat.godotengine.org/channel/platforms>`__.
.. note::
Godot is a modern engine with modern requirements. Even if you only
intend to run simple 2D projects on the target platform, it still requires
an amount of memory that makes it unviable to run on most retro consoles.
For reference, in Godot 4, an empty project with nothing visible requires
about 100 MB of RAM to run on Linux (50 MB in headless mode).
If you want to run Godot on heavily memory-constrained platforms, older
Godot versions have lower memory requirements. The porting process is
similar, with the exception of :ref:`class_DisplayServer` not being split
from the :ref:`class_OS` singleton.
Official platform ports
-----------------------
The official platform ports can be used as a reference when creating a custom platform port:
- `Windows <https://github.com/godotengine/godot/tree/master/platform/windows>`__
- `macOS <https://github.com/godotengine/godot/tree/master/platform/macos>`__
- `Linux/\*BSD <https://github.com/godotengine/godot/tree/master/platform/linuxbsd>`__
- `Android <https://github.com/godotengine/godot/tree/master/platform/android>`__
- `iOS <https://github.com/godotengine/godot/tree/master/platform/ios>`__
- `Web <https://github.com/godotengine/godot/tree/master/platform/web>`__
While platform code is usually self-contained, there are exceptions to this
rule. For instance, audio drivers that are shared across several platforms and
rendering drivers are located in the
`drivers/ folder <https://github.com/godotengine/godot/tree/master/drivers>`__
of the Godot source code.
Creating a custom platform port
-------------------------------
Creating a custom platform port is a large undertaking which requires prior
knowledge of the platform's SDKs. Depending on what features you need, the
amount of work needed varies:
Required features of a platform port
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
At the very least, a platform port must have methods from the :ref:`class_OS`
singleton implemented to be buildable and usable for headless operation.
A ``logo.svg`` (32×32) vector image must also be present within the platform
folder. This logo is displayed in the Export dialog for each export preset
targeting the platform in question.
See `this implementation <https://github.com/godotengine/godot/blob/master/platform/linuxbsd/os_linuxbsd.cpp>`__
for the Linux/\*BSD platform as an example. See also the
`OS singleton header <https://github.com/godotengine/godot/blob/master/core/os/os.h>`__
for reference.
.. note::
If your target platform is UNIX-like, consider inheriting from the ``OS_Unix``
class to get much of the work done automatically.
If the platform is not UNIX-like, you might use the
`Windows port <https://github.com/godotengine/godot/blob/master/platform/windows/os_windows.cpp>`__
as a reference.
**detect.py file**
A ``detect.py`` file must be created within the platform's folder with all
methods implemented. This file is required for SCons to detect the platform as a
valid option for compiling. See the
`detect.py file <https://github.com/godotengine/godot/blob/master/platform/linuxbsd/detect.py>`__
for the Linux/\*BSD platform as an example.
All methods should be implemented within ``detect.py`` as follows:
- ``is_active()``: Can be used to temporarily disable building for a platform.
This should generally always return ``True``.
- ``get_name()``: Returns the platform's user-visible name as a string.
- ``can_build()``: Return ``True`` if the host system is able to build for the
target platform, ``False`` otherwise. Do not put slow checks here, as this is
queried when the list of platforms is requested by the user. Use
``configure()`` for extensive dependency checks instead.
- ``get_opts()``: Returns the list of SCons build options that can be defined by
the user for this platform.
- ``get_flags()``: Returns the list of overridden SCons flags for this platform.
- ``configure()``: Perform build configuration, such as selecting compiler
options depending on SCons options chosen.
Optional features of a platform port
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In practice, headless operation doesn't suffice if you want to see anything on
screen and handle input devices. You may also want audio output for most
games.
*Some links on this list point to the Linux/\*BSD platform implementation as a reference.*
- One or more `DisplayServers <https://github.com/godotengine/godot/blob/master/platform/linuxbsd/x11/display_server_x11.cpp>`__,
with the windowing methods implemented. DisplayServer also covers features such
as mouse support, touchscreen support and tablet driver (for pen input).
See the
`DisplayServer singleton header <https://github.com/godotengine/godot/blob/master/servers/display_server.h>`__
for reference.
- For platforms not featuring full windowing support (or if it's not relevant
for the port you are making), most windowing functions can be left mostly
unimplemented. These functions can be made to only check if the window ID is
``MAIN_WINDOW_ID`` and specific operations like resizing may be tied to the
platform's screen resolution feature (if relevant). Any attempt to create
or manipulate other window IDs can be rejected.
- *If the target platform supports the graphics APIs in question:* Rendering
context for `Vulkan <https://github.com/godotengine/godot/blob/master/platform/linuxbsd/x11/rendering_context_driver_vulkan_x11.cpp>`__,
`Direct3D 12 <https://github.com/godotengine/godot/blob/master/drivers/d3d12/rendering_context_driver_d3d12.cpp>`__
`OpenGL 3.3 or OpenGL ES 3.0 <https://github.com/godotengine/godot/blob/master/platform/linuxbsd/x11/gl_manager_x11.cpp>`__.
- Input handlers for `keyboard <https://github.com/godotengine/godot/blob/master/platform/linuxbsd/x11/key_mapping_x11.cpp>`__
and `controller <https://github.com/godotengine/godot/blob/master/platform/linuxbsd/joypad_linux.cpp>`__.
- One or more `audio drivers <https://github.com/godotengine/godot/blob/master/drivers/pulseaudio/audio_driver_pulseaudio.cpp>`__.
The audio driver can be located in the ``platform/`` folder (this is done for
the Android and Web platforms), or in the ``drivers/`` folder if multiple
platforms may be using this audio driver. See the
`AudioServer singleton header <https://github.com/godotengine/godot/blob/master/servers/audio_server.h>`__
for reference.
- `Crash handler <https://github.com/godotengine/godot/blob/master/platform/linuxbsd/crash_handler_linuxbsd.cpp>`__,
for printing crash backtraces when the game crashes. This allows for easier
troubleshooting on platforms where logs aren't readily accessible.
- `Text-to-speech driver <https://github.com/godotengine/godot/blob/master/platform/linuxbsd/tts_linux.cpp>`__
(for accessibility).
- `Export handler <https://github.com/godotengine/godot/tree/master/platform/linuxbsd/export>`__
(for exporting from the editor, including :ref:`doc_one-click_deploy`).
Not required if you intend to export only a PCK from the editor, then run the
export template binary directly by renaming it to match the PCK file. See the
`EditorExportPlatform header <https://github.com/godotengine/godot/blob/master/editor/export/editor_export_platform.h>`__
for reference.
``run_icon.svg`` (16×16) should be present within the platform folder if
:ref:`doc_one-click_deploy` is implemented for the target platform. This icon
is displayed at the top of the editor when one-click deploy is set up for the
target platform.
If the target platform doesn't support running Vulkan, Direct3D 12, OpenGL 3.3,
or OpenGL ES 3.0, you have two options:
- Use a library at runtime to translate Vulkan or OpenGL calls to another graphics API.
For example, `MoltenVK <https://moltengl.com/moltenvk/>`__ is used on macOS
to translate Vulkan to Metal at runtime.
- Create a new renderer from scratch. This is a large undertaking, especially if
you want to support both 2D and 3D rendering with advanced features.
Distributing a custom platform port
-----------------------------------
.. danger::
Before distributing a custom platform port, make sure you're allowed to
distribute all the code that is being linked against. Console SDKs are
typically under NDAs which prevent redistribution to the public.
Platform ports are designed to be as self-contained as possible. Most of the
code can be kept within a single folder located in ``platform/``. Like
:ref:`doc_custom_modules_in_cpp`, this allows for streamlining the build process
by making it possible to ``git clone`` a platform folder within a Godot repository
clone's ``platform/`` folder, then run ``scons platform=<name>``. No other steps are
necessary for building, unless third-party platform-specific dependencies need
to be installed first.
However, when a custom rendering driver is needed, another folder must be added
in ``drivers/``. In this case, the platform port can be distributed as a fork of
the Godot repository, or as a collection of several folders that can be added
over a Godot Git repository clone.

View File

@@ -0,0 +1,374 @@
.. _doc_custom_resource_format_loaders:
Custom resource format loaders
==============================
Introduction
------------
ResourceFormatLoader is a factory interface for loading file assets.
Resources are primary containers. When load is called on the same file
path again, the previous loaded Resource will be referenced. Naturally,
loaded resources must be stateless.
This guide assumes the reader knows how to create C++ modules and Godot
data types. If not, refer to this guide: :ref:`doc_custom_modules_in_cpp`
References
~~~~~~~~~~
- :ref:`ResourceLoader<class_resourceloader>`
- `core/io/resource_loader.cpp <https://github.com/godotengine/godot/blob/master/core/io/resource_loader.cpp>`_
What for?
---------
- Adding new support for many file formats
- Audio formats
- Video formats
- Machine learning models
What not?
---------
- Raster images
ImageFormatLoader should be used to load images.
References
~~~~~~~~~~
- `core/io/image_loader.h <https://github.com/godotengine/godot/blob/master/core/io/image_loader.h>`_
Creating a ResourceFormatLoader
-------------------------------
Each file format consist of a data container and a ``ResourceFormatLoader``.
ResourceFormatLoaders are classes which return all the
necessary metadata for supporting new extensions in Godot. The
class must return the format name and the extension string.
In addition, ResourceFormatLoaders must convert file paths into
resources with the ``load`` function. To load a resource, ``load`` must
read and handle data serialization.
.. code-block:: cpp
:caption: resource_loader_json.h
#pragma once
#include "core/io/resource_loader.h"
class ResourceFormatLoaderJson : public ResourceFormatLoader {
GDCLASS(ResourceFormatLoaderJson, ResourceFormatLoader);
public:
virtual RES load(const String &p_path, const String &p_original_path, Error *r_error = NULL);
virtual void get_recognized_extensions(List<String> *r_extensions) const;
virtual bool handles_type(const String &p_type) const;
virtual String get_resource_type(const String &p_path) const;
};
.. code-block:: cpp
:caption: resource_loader_json.cpp
#include "resource_loader_json.h"
#include "resource_json.h"
RES ResourceFormatLoaderJson::load(const String &p_path, const String &p_original_path, Error *r_error) {
Ref<JsonResource> json = memnew(JsonResource);
if (r_error) {
*r_error = OK;
}
Error err = json->load_file(p_path);
return json;
}
void ResourceFormatLoaderJson::get_recognized_extensions(List<String> *r_extensions) const {
if (!r_extensions->find("json")) {
r_extensions->push_back("json");
}
}
String ResourceFormatLoaderJson::get_resource_type(const String &p_path) const {
return "Resource";
}
bool ResourceFormatLoaderJson::handles_type(const String &p_type) const {
return ClassDB::is_parent_class(p_type, "Resource");
}
Creating a ResourceFormatSaver
------------------------------
If you'd like to be able to edit and save a resource, you can implement a
``ResourceFormatSaver``:
.. code-block:: cpp
:caption: resource_saver_json.h
#pragma once
#include "core/io/resource_saver.h"
class ResourceFormatSaverJson : public ResourceFormatSaver {
GDCLASS(ResourceFormatSaverJson, ResourceFormatSaver);
public:
virtual Error save(const String &p_path, const RES &p_resource, uint32_t p_flags = 0);
virtual bool recognize(const RES &p_resource) const;
virtual void get_recognized_extensions(const RES &p_resource, List<String> *r_extensions) const;
};
.. code-block:: cpp
:caption: resource_saver_json.cpp
#include "resource_saver_json.h"
#include "resource_json.h"
#include "scene/resources/resource_format_text.h"
Error ResourceFormatSaverJson::save(const String &p_path, const RES &p_resource, uint32_t p_flags) {
Ref<JsonResource> json = memnew(JsonResource);
Error error = json->save_file(p_path, p_resource);
return error;
}
bool ResourceFormatSaverJson::recognize(const RES &p_resource) const {
return Object::cast_to<JsonResource>(*p_resource) != NULL;
}
void ResourceFormatSaverJson::get_recognized_extensions(const RES &p_resource, List<String> *r_extensions) const {
if (Object::cast_to<JsonResource>(*p_resource)) {
r_extensions->push_back("json");
}
}
Creating custom data types
--------------------------
Godot may not have a proper substitute within its :ref:`doc_core_types`
or managed resources. Godot needs a new registered data type to
understand additional binary formats such as machine learning models.
Here is an example of creating a custom datatype:
.. code-block:: cpp
:caption: resource_json.h
#pragma once
#include "core/io/json.h"
#include "core/variant_parser.h"
class JsonResource : public Resource {
GDCLASS(JsonResource, Resource);
protected:
static void _bind_methods() {
ClassDB::bind_method(D_METHOD("set_dict", "dict"), &JsonResource::set_dict);
ClassDB::bind_method(D_METHOD("get_dict"), &JsonResource::get_dict);
ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "content"), "set_dict", "get_dict");
}
private:
Dictionary content;
public:
Error load_file(const String &p_path);
Error save_file(const String &p_path, const RES &p_resource);
void set_dict(const Dictionary &p_dict);
Dictionary get_dict();
};
.. code-block:: cpp
:caption: resource_json.cpp
#include "resource_json.h"
Error JsonResource::load_file(const String &p_path) {
Error error;
FileAccess *file = FileAccess::open(p_path, FileAccess::READ, &error);
if (error != OK) {
if (file) {
file->close();
}
return error;
}
String json_string = String("");
while (!file->eof_reached()) {
json_string += file->get_line();
}
file->close();
String error_string;
int error_line;
JSON json;
Variant result;
error = json.parse(json_string, result, error_string, error_line);
if (error != OK) {
file->close();
return error;
}
content = Dictionary(result);
return OK;
}
Error JsonResource::save_file(const String &p_path, const RES &p_resource) {
Error error;
FileAccess *file = FileAccess::open(p_path, FileAccess::WRITE, &error);
if (error != OK) {
if (file) {
file->close();
}
return error;
}
Ref<JsonResource> json_ref = p_resource.get_ref_ptr();
JSON json;
file->store_string(json.print(json_ref->get_dict(), " "));
file->close();
return OK;
}
void JsonResource::set_dict(const Dictionary &p_dict) {
content = p_dict;
}
Dictionary JsonResource::get_dict() {
return content;
}
Considerations
~~~~~~~~~~~~~~
Some libraries may not define certain common routines such as IO handling.
Therefore, Godot call translations are required.
For example, here is the code for translating ``FileAccess``
calls into ``std::istream``.
.. code-block:: cpp
#include "core/io/file_access.h"
#include <istream>
#include <streambuf>
class GodotFileInStreamBuf : public std::streambuf {
public:
GodotFileInStreamBuf(FileAccess *fa) {
_file = fa;
}
int underflow() {
if (_file->eof_reached()) {
return EOF;
} else {
size_t pos = _file->get_position();
uint8_t ret = _file->get_8();
_file->seek(pos); // Required since get_8() advances the read head.
return ret;
}
}
int uflow() {
return _file->eof_reached() ? EOF : _file->get_8();
}
private:
FileAccess *_file;
};
References
~~~~~~~~~~
- `istream <https://cplusplus.com/reference/istream/istream/>`_
- `streambuf <https://cplusplus.com/reference/streambuf/streambuf/?kw=streambuf>`_
- `core/io/file_access.h <https://github.com/godotengine/godot/blob/master/core/io/file_access.h>`_
Registering the new file format
-------------------------------
Godot registers ``ResourcesFormatLoader`` with a ``ResourceLoader``
handler. The handler selects the proper loader automatically
when ``load`` is called.
.. code-block:: cpp
:caption: register_types.h
void register_json_types();
void unregister_json_types();
.. code-block:: cpp
:caption: register_types.cpp
#include "register_types.h"
#include "core/class_db.h"
#include "resource_loader_json.h"
#include "resource_saver_json.h"
#include "resource_json.h"
static Ref<ResourceFormatLoaderJson> json_loader;
static Ref<ResourceFormatSaverJson> json_saver;
void register_json_types() {
ClassDB::register_class<JsonResource>();
json_loader.instantiate();
ResourceLoader::add_resource_format_loader(json_loader);
json_saver.instantiate();
ResourceSaver::add_resource_format_saver(json_saver);
}
void unregister_json_types() {
ResourceLoader::remove_resource_format_loader(json_loader);
json_loader.unref();
ResourceSaver::remove_resource_format_saver(json_saver);
json_saver.unref();
}
References
~~~~~~~~~~
- `core/io/resource_loader.cpp <https://github.com/godotengine/godot/blob/master/core/io/resource_loader.cpp>`_
Loading it on GDScript
----------------------
Save a file called ``demo.json`` with the following contents and place it in the
project's root folder:
.. code-block:: json
{
"savefilename": "demo.json",
"demo": [
"welcome",
"to",
"godot",
"resource",
"loaders"
]
}
Then attach the following script to any node:
::
extends Node
@onready var json_resource = load("res://demo.json")
func _ready():
print(json_resource.get_dict())

View File

@@ -0,0 +1,28 @@
:allow_comments: False
.. _doc_engine_module_api:
Engine extension APIs
=====================
This section introduces various ways in which you can extend the engine with C++ code.
You can use these APIs by creating a :ref:`module <doc_custom_modules_in_cpp>`.
Note that you can change the engine in many more ways than presented here — this section just presents
a subselection of common and useful ways to do it.
Alternatively, some of the functions presented here are also available through the
:ref:`GDExtension <doc_what_is_gdextension>` API.
You can use them in C++ by using creating a :ref:`godot-cpp <doc_about_godot_cpp>` based GDExtension,
or with any of the :ref:`community-created GDExtension implementations <doc_scripting_languages>`. Note though
that some aspects of the code or directory structures may be different in GDExtension compared to the module APIs.
.. toctree::
:maxdepth: 1
:name: toc-devel-cpp-source-advanced
custom_modules_in_cpp
binding_to_external_libraries
custom_godot_servers
custom_resource_format_loaders
custom_audiostreams
custom_platform_ports