123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474 |
- .. _doc_custom_godot_servers:
- Custom Godot Servers
- ====================
- Introduction
- ------------
- Godot implements multi threading as servers. Servers are daemons which
- manages data, processes, and pushes the result. Server implements 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 this guide :ref:`doc_custom_modules_in_c++`.
- References:
- ~~~~~~~~~~~
- - `Why does Godot use Servers and RIDs? <https://godotengine.org/article/why-does-godot-use-servers-and-rids>`__
- - `Sigleton Pattern <https://en.wikipedia.org/wiki/Singleton_pattern>`__
- - `Mediator Pattern <https://en.wikipedia.org/wiki/Mediator_pattern>`__
- What for?
- ---------
- - Adding AI
- - Adding a custom asynchronous threads
- - Adding Input support
- - Adding writing threads
- - Adding custom VOIP protocol
- - etc.
- Creating a Godot Server
- -----------------------
- At minimum, a server must to have: a static instance, sleep timer, thread loop,
- initialize state, and cleanup.
- .. code:: cpp
- #ifndef HILBERT_HOTEL_H
- #define HILBERT_HOTEL_H
- #include "object.h"
- #include "list.h"
- #include "rid.h"
- #include "set.h"
- #include "variant.h"
- #include "os/thread.h"
- #include "os/mutex.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/rid.h#L196
- 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();
- };
- #endif
-
- .. code:: cpp
- #include "hilbert_hotel.h"
- #include "variant.h"
- #include "os/os.h"
- #include "list.h"
- #include "dictionary.h"
- #include "prime_225.h"
- oid 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/rid.h#L187
- 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:: cpp
-
- /* prime_225.h */
-
- #include "int_types.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. Only during debug mode,
- RID_Owner maintains a list of RID. In practice, RID is similar to writing
- object oriented C code.
- .. code:: cpp
- 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/rid.h <https://github.com/godotengine/godot/blob/master/core/rid.h>`__
- Registering the class to GDScript
- ---------------------------------
- Server 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 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_godotserver_types()``, ``Engine::get_singleton()->add_singleton`` is used to register the dummy class to GDScript.
- .. code:: cpp
- /* register_types.cpp */
- #include "register_types.h"
- #include "class_db.h"
- #include "hilbert_hotel.h"
- #include "engine.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);
- }
- }
- .. code:: cpp
- /* register_types.h */
- void register_hilbert_hotel_types();
- void unregister_hilbert_hotel_types();
- /* yes, the word in the middle must be the same as the module folder name */
- - `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:: 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 but calling the GDScript dummy object.
- .. code:: cpp
- void HilbertHotel::_emit_occupy_room(uint64_t room, RID rid) {
- _HilbertHotel::get_singleton()->_occupy_room(room, rid);
- }
- .. code:: 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_singals();
- bool delete_bus(RID id);
- static _HilbertHotel *get_singleton();
- Variant get_bus_info(RID id);
-
- _HilbertHotel();
- ~_HilbertHotel();
- };
- #endif
-
- .. code:: 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_singals() {
- 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. Queue will be flushed whenever either
- ``SceneTree::idle`` or ``SceneTree::iteration`` are executed.
- References:
- ~~~~~~~~~~~
- - `core/message_queue.cpp <https://github.com/godotengine/godot/blob/master/core/message_queue.cpp>`__
- Summing it up
- -------------
- Here is the GDScript sample code
- .. code::
-
- extends Node
- # class member variables go here, for example:
- # var a = 2
- # var b = "textvar"
- func _ready():
- # Called when the node is added to the scene for the first time.
- # Initialization here
- print("start Debugging")
- HilbertHotel.connect("occupy_room", self, "_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")
- pass
- func _print_occupy_room(room_number, r_id):
- print("room_num: " + str(room_number) + " rid: " + str(r_id))
- print(HilbertHotel.get_bus_info(r_id))
-
- Notes
- ~~~~~
- - Actual `Hilbert Hotel <https://en.wikipedia.org/wiki/Hilbert%27s_paradox_of_the_Grand_Hotel>`__ is impossible
- - Connecting signal example code is pretty hacky
|