Attention: Here be dragons
This is the latest
(unstable) version of this documentation, which may document features
not available in or compatible with released stable versions of Godot.
Checking the stable version of the documentation...
맞춤형 Godot 서버
소개
Godot는 멀티스레딩을 서버로 구현합니다. 서버는 데이터를 관리하고, 처리하고, 결과를 푸시하는 데몬입니다. 서버는 엔진 및 기타 모듈의 리소스 ID와 프로세스 데이터를 해석하는 중재자 패턴을 구현합니다. 또한 서버는 RID 할당에 대한 소유권을 주장합니다.
이 가이드는 독자가 C++ 모듈과 Godot 데이터 유형을 만드는 방법을 알고 있다고 가정합니다. 그렇지 않은 경우 :ref:`doc_custom_modules_in_cpp`를 참조하세요.
참조
무엇을 위해?
인공지능을 추가합니다.
사용자 정의 비동기 스레드를 추가합니다.
새로운 입력 장치에 대한 지원을 추가합니다.
쓰기 스레드를 추가합니다.
사용자 정의 VoIP 프로토콜 추가.
그리고 더...
Godot 서버 만들기
최소한 서버에는 정적 인스턴스, 절전 타이머, 스레드 루프, 초기화 상태 및 정리 절차가 있어야 합니다.
#pragma once
#include "core/object/object.h"
#include "core/os/thread.h"
#include "core/os/mutex.h"
#include "core/templates/list.h"
#include "core/templates/rid.h"
#include "core/templates/set.h"
#include "core/variant/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/master/core/templates/rid.h
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();
};
#include "hilbert_hotel.h"
#include "core/variant/dictionary.h"
#include "core/os/os.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/master/core/templates/rid.h
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;
}
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
};
커스텀 관리형 리소스 데이터
Godot 서버는 중재자 패턴을 구현합니다. 모든 데이터 유형은 ``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() {};
}
참조
GDScript에서 클래스 등록
서버는 ``register_types.cpp``에 할당됩니다. 생성자는 정적 인스턴스를 설정하고 ``init()``는 관리되는 스레드를 생성합니다. ``unregister_types.cpp``는 서버를 정리합니다.
Godot 서버 클래스는 인스턴스를 생성하고 이를 정적 싱글톤에 바인딩하므로 클래스 바인딩은 올바른 인스턴스를 참조하지 않을 수 있습니다. 그러므로 적절한 Godot 서버를 참조하려면 더미 클래스를 생성해야 합니다.
``register_server_types()``에서 ``Engine::get_singleton()->add_singleton``는 GDScript에 더미 클래스를 등록하는 데 사용됩니다.
/* 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();
#include "register_types.h"
#include "core/object/class_db.h"
#include "core/config/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);
}
}
바인딩 방법
더미 클래스는 싱글톤 메서드를 GDScript에 바인딩합니다. 대부분의 경우 더미 클래스 메서드가 래핑됩니다.
Variant _HilbertHotel::get_bus_info(RID id) {
return HilbertHotel::get_singleton()->get_bus_info(id);
}
바인딩 시그널
GDScript 더미 객체를 호출하여 시그널를 GDScript로 내보낼 수 있습니다.
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() {
}
메시지큐
SceneTree에 명령을 보내기 위해 MessageQueue는 다른 스레드에 대한 메서드를 대기열에 설정하고 호출하는 스레드로부터 안전한 버퍼입니다. 명령을 대기열에 추가하려면 대상 개체 RID를 얻고 push_call, push_set 또는 push_notification``를 사용하여 원하는 동작을 실행합니다. ``SceneTree::idle 또는 ``SceneTree::iteration``가 실행될 때마다 대기열이 플러시됩니다.
참고자료:
요약하자면
GDScript 샘플 코드입니다:
extends Node
func _ready():
print("Start debugging")
HilbertHotel.occupy_room.connect(_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))
메모
실제 `힐베르트 호텔 <https://en.wikipedia.org/wiki/Hilbert%27s_paradox_of_the_Grand_Hotel>`__은 불가능합니다.
시그널 예제 코드를 연결하는 것은 매우 해킹적입니다.