mirror of
https://github.com/godotengine/godot-docs.git
synced 2026-09-03 18:14:53 +03:00
Split "module API" articles into their own engine details section, away from "architecture".
This commit is contained in:
223
engine_details/engine_api/binding_to_external_libraries.rst
Normal file
223
engine_details/engine_api/binding_to_external_libraries.rst
Normal 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.
|
||||
349
engine_details/engine_api/custom_audiostreams.rst
Normal file
349
engine_details/engine_api/custom_audiostreams.rst
Normal 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>`__
|
||||
502
engine_details/engine_api/custom_godot_servers.rst
Normal file
502
engine_details/engine_api/custom_godot_servers.rst
Normal 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.
|
||||
605
engine_details/engine_api/custom_modules_in_cpp.rst
Normal file
605
engine_details/engine_api/custom_modules_in_cpp.rst
Normal 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.
|
||||
191
engine_details/engine_api/custom_platform_ports.rst
Normal file
191
engine_details/engine_api/custom_platform_ports.rst
Normal 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.
|
||||
374
engine_details/engine_api/custom_resource_format_loaders.rst
Normal file
374
engine_details/engine_api/custom_resource_format_loaders.rst
Normal 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())
|
||||
28
engine_details/engine_api/index.rst
Normal file
28
engine_details/engine_api/index.rst
Normal 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
|
||||
Reference in New Issue
Block a user