diff --git a/development/cpp/custom_godot_servers.rst b/development/cpp/custom_godot_servers.rst index 1af72f7d9..0a4e974e1 100644 --- a/development/cpp/custom_godot_servers.rst +++ b/development/cpp/custom_godot_servers.rst @@ -1,45 +1,44 @@ .. _doc_custom_godot_servers: -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 +Godot implements multi-threading as servers. Servers are daemons which +manages data, processes, and pushes 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_c++`. -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: +References ~~~~~~~~~~~ -- `Why does Godot use Servers and RIDs? `__ +- `Why does Godot use servers and RIDs? `__ -- `Sigleton Pattern `__ +- `Singleton pattern `__ -- `Mediator Pattern `__ +- `Mediator pattern `__ What for? --------- -- Adding AI -- Adding a custom asynchronous threads -- Adding Input support -- Adding writing threads -- Adding custom VOIP protocol -- etc. +- 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 +Creating a Godot server ----------------------- -At minimum, a server must to have: a static instance, sleep timer, thread loop, -initialize state, and cleanup. +At minimum, a server must have a static instance, a sleep timer, a thread loop, +an initialization state and a cleanup procedure. .. code:: cpp @@ -79,10 +78,10 @@ initialize state, and cleanup. private: uint64_t counter; RID_Owner bus_owner; - //https://github.com/godotengine/godot/blob/master/core/rid.h#L196 + // https://github.com/godotengine/godot/blob/master/core/rid.h#L196 Set buses; void _emit_occupy_room(uint64_t room, RID rid); - + public: RID create_bus(); Variant get_bus_info(RID id); @@ -92,8 +91,9 @@ initialize state, and cleanup. void register_rooms(); HilbertHotel(); }; + #endif - + .. code:: cpp #include "hilbert_hotel.h" @@ -103,12 +103,13 @@ initialize state, and cleanup. #include "dictionary.h" #include "prime_225.h" - oid HilbertHotel::thread_func(void *p_udata){ - HilbertHotel *ac = (HilbertHotel *) p_udata; + oid HilbertHotel::thread_func(void *p_udata) { + HilbertHotel *ac = (HilbertHotel *) p_udata; uint64_t msdelay = 1000; - while(!ac -> exit_thread){ - if(!ac -> empty()) { + + while (!ac -> exit_thread) { + if (!ac -> empty()) { ac->lock(); ac->register_rooms(); ac->unlock(); @@ -117,60 +118,81 @@ initialize state, and cleanup. } } - Error HilbertHotel::init(){ + 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; } + + HilbertHotel *HilbertHotel::get_singleton() { + return singleton; + } + void HilbertHotel::register_rooms() { - for( Set::Element *e = buses.front(); e; e = e->next()) { + for (Set::Element *e = buses.front(); e; e = e->next()) { auto bus = bus_owner.getornull(e->get()); - if(bus){ + + if (bus) { uint64_t room = bus->next_room(); _emit_occupy_room(room, bus->get_self()); - } + } } } void HilbertHotel::unlock() { - if (!thread || !mutex) + if (!thread || !mutex) { return; + } + mutex->unlock(); } + void HilbertHotel::lock() { - if (!thread || !mutex) + 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){ + + 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) + if (!thread) { return; + } exit_thread = true; Thread::wait_to_finish(thread); - + memdelete(thread); - if (mutex) + + if (mutex) { memdelete(mutex); + } + thread = NULL; } + RID HilbertHotel::create_bus() { lock(); InfiniteBus *ptr = memnew(InfiniteBus(PRIME[counter++])); @@ -178,9 +200,11 @@ initialize state, and cleanup. ptr->set_self(ret); buses.insert(ret); unlock(); + return ret; } - //https://github.com/godotengine/godot/blob/master/core/rid.h#L187 + + // https://github.com/godotengine/godot/blob/master/core/rid.h#L187 bool HilbertHotel::delete_bus(RID id) { if (bus_owner.owns(id)) { lock(); @@ -191,17 +215,20 @@ initialize state, and cleanup. unlock(); return true; } + return false; } + void HilbertHotel::clear() { - - for( Set::Element *e = buses.front(); e; e = e->next()) { + for (Set::Element *e = buses.front(); e; e = e->next()) { delete_bus(e->get()); } } + bool HilbertHotel::empty() { return buses.size() <= 0; } + void HilbertHotel::_bind_methods() { } @@ -209,12 +236,12 @@ initialize state, and cleanup. 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, @@ -240,14 +267,14 @@ initialize state, and cleanup. 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 + +Custom managed resource data ---------------------------- -Godot servers implement a mediator pattern. All data types inherit ``RID_Data``. -`RID_Owner`` 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. +Godot servers implement a mediator pattern. All data types inherit ``RID_Data``. +``RID_Owner`` 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:: cpp @@ -262,39 +289,46 @@ object oriented C code. 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; } + + _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: + +References ~~~~~~~~~~~ - :ref:`RID` - `core/rid.h `__ - -Registering the class to GDScript +Registering the class in 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 +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 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. +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:: cpp @@ -308,39 +342,39 @@ In ``register_godotserver_types()``, ``Engine::get_singleton()->add_singleton`` 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())); + 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); - } + if (hilbert_hotel) { + hilbert_hotel->finish(); + memdelete(hilbert_hotel); + } + + if (_hilbert_hotel) { + memdelete(_hilbert_hotel); + } } .. code:: cpp /* 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(); - /* yes, the word in the middle must be the same as the module folder name */ - - `servers/register_server_types.cpp `__ -Bind methods +Bind methods ~~~~~~~~~~~~ -The dummy class binds singleton methods to gdscript. In most cases, the dummy class methods wraps around. +The dummy class binds singleton methods to GDScript. In most cases, the dummy class methods wraps around. .. code:: cpp @@ -350,7 +384,7 @@ The dummy class binds singleton methods to gdscript. In most cases, the dummy cl Binding Signals -It is possible to emit signals to gdscript but calling the GDScript dummy object. +It is possible to emit signals to GDScript but calling the GDScript dummy object. .. code:: cpp @@ -362,7 +396,7 @@ It is possible to emit signals to gdscript but calling the GDScript dummy object class _HilbertHotel : public Object { GDCLASS(_HilbertHotel, Object); - + friend class HilbertHotel; static _HilbertHotel *singleton; @@ -371,19 +405,20 @@ It is possible to emit signals to gdscript but calling the GDScript dummy object 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; @@ -392,38 +427,45 @@ It is possible to emit signals to gdscript but calling the GDScript dummy object 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. +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: ~~~~~~~~~~~ @@ -433,19 +475,13 @@ References: Summing it up ------------- -Here is the GDScript sample code +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() @@ -457,18 +493,13 @@ Here is the GDScript sample code 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 `__ is impossible - -- Connecting signal example code is pretty hacky - +- The actual `Hilbert Hotel `__ is impossible. +- Connecting signal example code is pretty hacky.