自定义Godot服务
前言
Godot将多线程实现为服务器. 服务器是管理数据, 处理数据和推送结果的守护进程. 服务器实现中介模式, 该模式解释引擎和其他模块的资源ID和处理数据. 此外, 服务器声明其RID分配的所有权. 自定义 C++ 模块.
参考
- 为什么Godot使用服务与RID ?
- 单例模式
- 中介模式
可以做什么?
- 添加人工智能.
- 添加自定义异步线程.
- 添加对新输入设备的支持.
- 添加写线程.
- 添加自定义VoIP协议.
- 以及更多…
创建Godot服务
至少, 服务必须具有静态实例, 睡眠计时器, 线程循环, 初始化状态, 和清理过程.#ifndef HILBERT_HOTEL_H#define HILBERT_HOTEL_H#include "core/list.h"#include "core/object.h"#include "core/os/thread.h"#include "core/os/mutex.h"#include "core/rid.h"#include "core/set.h"#include "core/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/3.x/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
#include "hilbert_hotel.h"#include "core/dictionary.h"#include "core/list.h"#include "core/os/os.h"#include "core/variant.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/3.x/core/rid.h#L187bool 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;}
/* prime_225.h */#include "core/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};
自定义托管资源数据
RID_Data.RID_Owner <MyRID_Data>在调用make_rid时拥有对象. 仅在调试模式期间,RID_Owner维护RID列表. 实际上,RID类似于编写面向对象的C代码.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() {};}
参考
- RID
- core/rid.h
在GDScript中注册类
register_types.cpp中分配. 构造函数设置静态实例,init()创建托管线程;unregister_types.cpp清理服务. 由于Godot服务类创建了一个实例并将其绑定到静态单例, 因此绑定该类可能不会引用正确的实例. 因此, 必须创建一个虚拟类来引用正确的Godot服务.register_server_types()中,Engine :: get_singleton() -> add_singleton用于在 GDScript 中注册虚拟类。/* register_types.cpp */#include "register_types.h"#include "core/class_db.h"#include "core/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); }}
/* 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();
- servers/register_server_types.cpp
绑定方法
虚拟类将单例方法绑定到GDScript. 在大多数情况下, 虚拟类方法封装在内.
绑定信号 可以通过调用GDScript虚拟对象向GDScript发出信号.Variant _HilbertHotel::get_bus_info(RID id) { return HilbertHotel::get_singleton()->get_bus_info(id);}
void HilbertHotel::_emit_occupy_room(uint64_t room, RID rid) { _HilbertHotel::get_singleton()->_occupy_room(room, rid);}
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
_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
push_call,push_set, 或push_notification执行所需的行为. 每当执行SceneTree::idle或SceneTree::iteration时, 都会刷新队列.参考:
- core/message_queue.cpp
总结
这是GDScript示例代码:extends Nodefunc _ready(): 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")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))
注意
- 希尔伯特旅馆悖论 是不可能的.
- 连接信号的示例代码是很旁门左道的.
