自定义 Godot 服务器

前言

Godot将多线程实现为服务器. 服务器是管理数据, 处理数据和推送结果的守护进程. 服务器实现中介模式, 该模式解释引擎和其他模块的资源ID和处理数据. 此外, 服务器声明其RID分配的所有权.

本指南假设读者知道如何创建C++模块和Godot数据类型. 如果没有, 请参考 自定义 C++ 模块.

参考

可以做什么?

  • 添加人工智能.

  • 添加自定义异步线程.

  • 添加对新输入设备的支持.

  • 添加写线程.

  • 添加自定义 VoIP 协议。

  • 以及更多……

创建 Godot 服务器

服务器至少必须拥有静态的实例、睡眠计时器、线程循环、初始化状态、清理过程。

  1. #ifndef HILBERT_HOTEL_H
  2. #define HILBERT_HOTEL_H
  3. #include "core/list.h"
  4. #include "core/object.h"
  5. #include "core/os/thread.h"
  6. #include "core/os/mutex.h"
  7. #include "core/rid.h"
  8. #include "core/set.h"
  9. #include "core/variant.h"
  10. class HilbertHotel : public Object {
  11. GDCLASS(HilbertHotel, Object);
  12. static HilbertHotel *singleton;
  13. static void thread_func(void *p_udata);
  14. private:
  15. bool thread_exited;
  16. mutable bool exit_thread;
  17. Thread *thread;
  18. Mutex *mutex;
  19. public:
  20. static HilbertHotel *get_singleton();
  21. Error init();
  22. void lock();
  23. void unlock();
  24. void finish();
  25. protected:
  26. static void _bind_methods();
  27. private:
  28. uint64_t counter;
  29. RID_Owner<InfiniteBus> bus_owner;
  30. // https://github.com/godotengine/godot/blob/3.x/core/rid.h#L196
  31. Set<RID> buses;
  32. void _emit_occupy_room(uint64_t room, RID rid);
  33. public:
  34. RID create_bus();
  35. Variant get_bus_info(RID id);
  36. bool empty();
  37. bool delete_bus(RID id);
  38. void clear();
  39. void register_rooms();
  40. HilbertHotel();
  41. };
  42. #endif
  1. #include "hilbert_hotel.h"
  2. #include "core/dictionary.h"
  3. #include "core/list.h"
  4. #include "core/os/os.h"
  5. #include "core/variant.h"
  6. #include "prime_225.h"
  7. void HilbertHotel::thread_func(void *p_udata) {
  8. HilbertHotel *ac = (HilbertHotel *) p_udata;
  9. uint64_t msdelay = 1000;
  10. while (!ac->exit_thread) {
  11. if (!ac->empty()) {
  12. ac->lock();
  13. ac->register_rooms();
  14. ac->unlock();
  15. }
  16. OS::get_singleton()->delay_usec(msdelay * 1000);
  17. }
  18. }
  19. Error HilbertHotel::init() {
  20. thread_exited = false;
  21. counter = 0;
  22. mutex = Mutex::create();
  23. thread = Thread::create(HilbertHotel::thread_func, this);
  24. return OK;
  25. }
  26. HilbertHotel *HilbertHotel::singleton = NULL;
  27. HilbertHotel *HilbertHotel::get_singleton() {
  28. return singleton;
  29. }
  30. void HilbertHotel::register_rooms() {
  31. for (Set<RID>::Element *e = buses.front(); e; e = e->next()) {
  32. auto bus = bus_owner.getornull(e->get());
  33. if (bus) {
  34. uint64_t room = bus->next_room();
  35. _emit_occupy_room(room, bus->get_self());
  36. }
  37. }
  38. }
  39. void HilbertHotel::unlock() {
  40. if (!thread || !mutex) {
  41. return;
  42. }
  43. mutex->unlock();
  44. }
  45. void HilbertHotel::lock() {
  46. if (!thread || !mutex) {
  47. return;
  48. }
  49. mutex->lock();
  50. }
  51. void HilbertHotel::_emit_occupy_room(uint64_t room, RID rid) {
  52. _HilbertHotel::get_singleton()->_occupy_room(room, rid);
  53. }
  54. Variant HilbertHotel::get_bus_info(RID id) {
  55. InfiniteBus *)bus = bus_owner.getornull(id);
  56. if (bus) {
  57. Dictionary d;
  58. d["prime"] = bus->get_bus_num();
  59. d["current_room"] = bus->get_current_room();
  60. return d;
  61. }
  62. return Variant();
  63. }
  64. void HilbertHotel::finish() {
  65. if (!thread) {
  66. return;
  67. }
  68. exit_thread = true;
  69. Thread::wait_to_finish(thread);
  70. memdelete(thread);
  71. if (mutex) {
  72. memdelete(mutex);
  73. }
  74. thread = NULL;
  75. }
  76. RID HilbertHotel::create_bus() {
  77. lock();
  78. InfiniteBus *ptr = memnew(InfiniteBus(PRIME[counter++]));
  79. RID ret = bus_owner.make_rid(ptr);
  80. ptr->set_self(ret);
  81. buses.insert(ret);
  82. unlock();
  83. return ret;
  84. }
  85. // https://github.com/godotengine/godot/blob/3.x/core/rid.h#L187
  86. bool HilbertHotel::delete_bus(RID id) {
  87. if (bus_owner.owns(id)) {
  88. lock();
  89. InfiniteBus *b = bus_owner.get(id);
  90. bus_owner.free(id);
  91. buses.erase(id);
  92. memdelete(b);
  93. unlock();
  94. return true;
  95. }
  96. return false;
  97. }
  98. void HilbertHotel::clear() {
  99. for (Set<RID>::Element *e = buses.front(); e; e = e->next()) {
  100. delete_bus(e->get());
  101. }
  102. }
  103. bool HilbertHotel::empty() {
  104. return buses.size() <= 0;
  105. }
  106. void HilbertHotel::_bind_methods() {
  107. }
  108. HilbertHotel::HilbertHotel() {
  109. singleton = this;
  110. }
  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. 967,971,977,983,991,997,1009,1013,1019,
  23. 1021,1031,1033,1039,1049,1051,1061,1063,1069,
  24. 1087,1091,1093,1097,1103,1109,1117,1123,1129,
  25. 1151,1153,1163,1171,1181,1187,1193,1201,1213,
  26. 1217,1223,1229,1231,1237,1249,1259,1277,1279,
  27. 1283,1289,1291,1297,1301,1303,1307,1319,1321,
  28. 1327,1361,1367,1373,1381,1399,1409,1423,1427
  29. };

自定义托管资源数据

Godot 服务器实现了中介模式。所有数据类型都继承 RID_DataRID_Owner <MyRID_Data> 在调用 make_rid 时拥有对象。仅在调试模式期间,RID_Owner 维护 RID 列表。实际上,RID 类似于编写面向对象的 C 代码。

  1. class InfiniteBus : public RID_Data {
  2. RID self;
  3. private:
  4. uint64_t prime_num;
  5. uint64_t num;
  6. public:
  7. uint64_t next_room() {
  8. return prime_num * num++;
  9. }
  10. uint64_t get_bus_num() const {
  11. return prime_num;
  12. }
  13. uint64_t get_current_room() const {
  14. return prime_num * num;
  15. }
  16. _FORCE_INLINE_ void set_self(const RID &p_self) {
  17. self = p_self;
  18. }
  19. _FORCE_INLINE_ RID get_self() const {
  20. return self;
  21. }
  22. InfiniteBus(uint64_t prime) : prime_num(prime), num(1) {};
  23. ~InfiniteBus() {};
  24. }

参考

在 GDScript 中注册类

服务器在 register_types.cpp 中分配。构造函数设置静态实例,init() 创建托管线程;unregister_types.cpp 清理服务器。

由于Godot服务类创建了一个实例并将其绑定到静态单例, 因此绑定该类可能不会引用正确的实例. 因此, 必须创建一个虚拟类来引用正确的Godot服务.

register_server_types() 中,Engine :: get_singleton() -> add_singleton 用于在 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();

绑定方法

虚拟类将单例方法绑定到GDScript. 在大多数情况下, 虚拟类方法封装在内.

  1. Variant _HilbertHotel::get_bus_info(RID id) {
  2. return HilbertHotel::get_singleton()->get_bus_info(id);
  3. }

绑定信号

可以通过调用GDScript虚拟对象向GDScript发出信号.

  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_signals();
  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
  1. _HilbertHotel *_HilbertHotel::singleton = NULL;
  2. _HilbertHotel *_HilbertHotel::get_singleton() { return singleton; }
  3. RID _HilbertHotel::create_bus() {
  4. return HilbertHotel::get_singleton()->create_bus();
  5. }
  6. bool _HilbertHotel::delete_bus(RID rid) {
  7. return HilbertHotel::get_singleton()->delete_bus(rid);
  8. }
  9. void _HilbertHotel::_occupy_room(int room_number, RID bus) {
  10. emit_signal("occupy_room", room_number, bus);
  11. }
  12. Variant _HilbertHotel::get_bus_info(RID id) {
  13. return HilbertHotel::get_singleton()->get_bus_info(id);
  14. }
  15. void _HilbertHotel::_bind_methods() {
  16. ClassDB::bind_method(D_METHOD("get_bus_info", "r_id"), &_HilbertHotel::get_bus_info);
  17. ClassDB::bind_method(D_METHOD("create_bus"), &_HilbertHotel::create_bus);
  18. ClassDB::bind_method(D_METHOD("delete_bus"), &_HilbertHotel::delete_bus);
  19. ADD_SIGNAL(MethodInfo("occupy_room", PropertyInfo(Variant::INT, "room_number"), PropertyInfo(Variant::_RID, "r_id")));
  20. }
  21. void _HilbertHotel::connect_signals() {
  22. HilbertHotel::get_singleton()->connect("occupy_room", _HilbertHotel::get_singleton(), "_occupy_room");
  23. }
  24. _HilbertHotel::_HilbertHotel() {
  25. singleton = this;
  26. }
  27. _HilbertHotel::~_HilbertHotel() {
  28. }

MessageQueue

为了将命令发送到SceneTree中,MessageQueue是线程安全的缓冲区, 用于将其他线程的设置和调用方法排队. 要对命令进行排队, 请获取目标对象RID并使用 push_call, push_set, 或 push_notification 执行所需的行为. 每当执行 SceneTree::idleSceneTree::iteration 时, 都会刷新队列.

参考:

总结

这是GDScript示例代码:

  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))
  16. print(HilbertHotel.get_bus_info(r_id))

注意