Custom Godot servers

    This guide assumes the reader knows how to create C++ modules and Godot data types. If not, refer to .

    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…

    At minimum, a server must have a static instance, a sleep timer, a thread loop, an initialization state and a cleanup procedure.

    1. #include "core/dictionary.h"
    2. #include "core/list.h"
    3. #include "core/os/os.h"
    4. #include "core/variant.h"
    5. #include "prime_225.h"
    6. void HilbertHotel::thread_func(void *p_udata) {
    7. HilbertHotel *ac = (HilbertHotel *) p_udata;
    8. uint64_t msdelay = 1000;
    9. while (!ac->exit_thread) {
    10. if (!ac->empty()) {
    11. ac->lock();
    12. ac->register_rooms();
    13. ac->unlock();
    14. }
    15. OS::get_singleton()->delay_usec(msdelay * 1000);
    16. }
    17. }
    18. Error HilbertHotel::init() {
    19. thread_exited = false;
    20. counter = 0;
    21. mutex = Mutex::create();
    22. thread = Thread::create(HilbertHotel::thread_func, this);
    23. return OK;
    24. }
    25. HilbertHotel *HilbertHotel::singleton = NULL;
    26. HilbertHotel *HilbertHotel::get_singleton() {
    27. return singleton;
    28. }
    29. void HilbertHotel::register_rooms() {
    30. for (Set<RID>::Element *e = buses.front(); e; e = e->next()) {
    31. auto bus = bus_owner.getornull(e->get());
    32. if (bus) {
    33. uint64_t room = bus->next_room();
    34. _emit_occupy_room(room, bus->get_self());
    35. }
    36. }
    37. }
    38. void HilbertHotel::unlock() {
    39. if (!thread || !mutex) {
    40. return;
    41. }
    42. mutex->unlock();
    43. }
    44. void HilbertHotel::lock() {
    45. if (!thread || !mutex) {
    46. return;
    47. }
    48. mutex->lock();
    49. }
    50. void HilbertHotel::_emit_occupy_room(uint64_t room, RID rid) {
    51. _HilbertHotel::get_singleton()->_occupy_room(room, rid);
    52. }
    53. Variant HilbertHotel::get_bus_info(RID id) {
    54. InfiniteBus *)bus = bus_owner.getornull(id);
    55. if (bus) {
    56. Dictionary d;
    57. d["prime"] = bus->get_bus_num();
    58. d["current_room"] = bus->get_current_room();
    59. return d;
    60. }
    61. return Variant();
    62. }
    63. void HilbertHotel::finish() {
    64. if (!thread) {
    65. return;
    66. }
    67. exit_thread = true;
    68. memdelete(thread);
    69. if (mutex) {
    70. memdelete(mutex);
    71. }
    72. thread = NULL;
    73. }
    74. RID HilbertHotel::create_bus() {
    75. lock();
    76. InfiniteBus *ptr = memnew(InfiniteBus(PRIME[counter++]));
    77. RID ret = bus_owner.make_rid(ptr);
    78. ptr->set_self(ret);
    79. buses.insert(ret);
    80. unlock();
    81. return ret;
    82. }
    83. // https://github.com/godotengine/godot/blob/master/core/rid.h#L187
    84. bool HilbertHotel::delete_bus(RID id) {
    85. if (bus_owner.owns(id)) {
    86. lock();
    87. InfiniteBus *b = bus_owner.get(id);
    88. bus_owner.free(id);
    89. buses.erase(id);
    90. memdelete(b);
    91. unlock();
    92. return true;
    93. }
    94. return false;
    95. }
    96. void HilbertHotel::clear() {
    97. for (Set<RID>::Element *e = buses.front(); e; e = e->next()) {
    98. delete_bus(e->get());
    99. }
    100. }
    101. bool HilbertHotel::empty() {
    102. return buses.size() <= 0;
    103. }
    104. void HilbertHotel::_bind_methods() {
    105. }
    106. HilbertHotel::HilbertHotel() {
    107. singleton = this;
    108. }
    1. /* prime_225.h */
    2. #include "core/int_types.h"
    3. const uint64_t PRIME[225] = {
    4. 2,3,5,7,11,13,17,19,23,
    5. 29,31,37,41,43,47,53,59,61,
    6. 67,71,73,79,83,89,97,101,103,
    7. 107,109,113,127,131,137,139,149,151,
    8. 157,163,167,173,179,181,191,193,197,
    9. 199,211,223,227,229,233,239,241,251,
    10. 257,263,269,271,277,281,283,293,307,
    11. 311,313,317,331,337,347,349,353,359,
    12. 367,373,379,383,389,397,401,409,419,
    13. 421,431,433,439,443,449,457,461,463,
    14. 467,479,487,491,499,503,509,521,523,
    15. 541,547,557,563,569,571,577,587,593,
    16. 599,601,607,613,617,619,631,641,643,
    17. 647,653,659,661,673,677,683,691,701,
    18. 709,719,727,733,739,743,751,757,761,
    19. 769,773,787,797,809,811,821,823,827,
    20. 829,839,853,857,859,863,877,881,883,
    21. 887,907,911,919,929,937,941,947,953,
    22. 1021,1031,1033,1039,1049,1051,1061,1063,1069,
    23. 1087,1091,1093,1097,1103,1109,1117,1123,1129,
    24. 1151,1153,1163,1171,1181,1187,1193,1201,1213,
    25. 1217,1223,1229,1231,1237,1249,1259,1277,1279,
    26. 1283,1289,1291,1297,1301,1303,1307,1319,1321,
    27. 1327,1361,1367,1373,1381,1399,1409,1423,1427
    28. };

    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.

    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.

    1. /* register_types.cpp */
    2. #include "register_types.h"
    3. #include "core/class_db.h"
    4. #include "core/engine.h"
    5. #include "hilbert_hotel.h"
    6. static HilbertHotel *hilbert_hotel = NULL;
    7. static _HilbertHotel *_hilbert_hotel = NULL;
    8. void register_hilbert_hotel_types() {
    9. hilbert_hotel = memnew(HilbertHotel);
    10. hilbert_hotel->init();
    11. _hilbert_hotel = memnew(_HilbertHotel);
    12. ClassDB::register_class<_HilbertHotel>();
    13. Engine::get_singleton()->add_singleton(Engine::Singleton("HilbertHotel", _HilbertHotel::get_singleton()));
    14. }
    15. void unregister_hilbert_hotel_types() {
    16. if (hilbert_hotel) {
    17. hilbert_hotel->finish();
    18. memdelete(hilbert_hotel);
    19. }
    20. if (_hilbert_hotel) {
    21. memdelete(_hilbert_hotel);
    22. }
    23. }
    1. /* register_types.h */
    2. /* Yes, the word in the middle must be the same as the module folder name */
    3. void register_hilbert_hotel_types();
    4. void unregister_hilbert_hotel_types();

    The dummy class binds singleton methods to GDScript. In most cases, the dummy class methods wraps around.

    It is possible to emit signals to GDScript by calling the GDScript dummy object.

    1. void HilbertHotel::_emit_occupy_room(uint64_t room, RID rid) {
    2. _HilbertHotel::get_singleton()->_occupy_room(room, rid);
    3. }
    1. class _HilbertHotel : public Object {
    2. GDCLASS(_HilbertHotel, Object);
    3. friend class HilbertHotel;
    4. static _HilbertHotel *singleton;
    5. protected:
    6. static void _bind_methods();
    7. private:
    8. void _occupy_room(int room_number, RID bus);
    9. public:
    10. RID create_bus();
    11. void connect_singals();
    12. bool delete_bus(RID id);
    13. static _HilbertHotel *get_singleton();
    14. Variant get_bus_info(RID id);
    15. _HilbertHotel();
    16. ~_HilbertHotel();
    17. };
    18. #endif

    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.

    Here is the GDScript sample code:

    1. extends Node
    2. func _ready():
    3. print("Start debugging")
    4. HilbertHotel.connect("occupy_room", self, "_print_occupy_room")
    5. var rid = HilbertHotel.create_bus()
    6. OS.delay_msec(2000)
    7. HilbertHotel.create_bus()
    8. OS.delay_msec(2000)
    9. HilbertHotel.create_bus()
    10. OS.delay_msec(2000)
    11. print(HilbertHotel.get_bus_info(rid))
    12. HilbertHotel.delete_bus(rid)
    13. print("Ready done")
    14. func _print_occupy_room(room_number, r_id):
    15. print("Room number: " + str(room_number) + ", RID: " + str(r_id))
    • The actual is impossible.