Servidores Godot personalizados¶
Introducción¶
Godot implementa el multihilo como servidores. Los servidores son demonios que gestionan datos, los procesan y envían el resultado. Los servidores implementan el patrón mediador, que interpreta el ID del recurso y procesa datos para el motor y otros módulos. Además, el servidor reclama la propiedad de sus asignaciones de RID (Resource ID).
Esta guía asume que el lector sabe cómo crear módulos en C++ y tipos de datos de Godot. Si no es así, consulta Módulos personalizados en C++ para obtener más información al respecto.
Referencias¶
¿Para qué?¶
Agregando inteligencia artificial.
Agregando hilos asincronos personalizados.
Agregando soporte para un nuevo dispositivo de entrada.
Añadiendo hilos de escritura.
Agregando protocolo VoIP personalizado.
Y mas...
Creando un servidor Godot¶
Como mínimo, un servidor debe tener una instancia estática, un temporizador de pausa (sleep timer), un bucle de hilo, un estado de inicialización y un procedimiento de limpieza.
#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#L187
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;
}
/* 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
};
Datos de recursos personalizados gestionados¶
Los servidores de Godot implementan un patrón mediador. Todos los tipos de datos heredan de RID_Data
. RID_Owner<MyRID_Data>
es el propietario del objeto cuando se llama a make_rid
. Durante el modo de depuración solamente, RID_Owner
mantiene una lista de RIDs. En la práctica, los RIDs son similares a escribir código en C orientado a objetos.
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() {};
}
Referencias¶
Registrar una clase en GDScript¶
Los servidores se asignan en register_types.cpp
. El constructor establece la instancia estática y init()
crea el hilo gestionado; mientras que unregister_types.cpp
limpia el servidor.
Ya que una clase de servidor Godot crea una instancia y la enlaza a un singleton estático, enlazar la clase podría no hacer referencia a la instancia correcta. Por lo tanto, se debe crear una clase dummy para hacer referencia al servidor Godot apropiado.
En register_server_types()
, se utiliza Engine::get_singleton()->add_singleton
para registrar la clase ficticia (dummy class) en 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();
Métodos de vinculación¶
La clase dummy une los métodos singleton a GDScript. En la mayoría de los casos, los métodos de la clase dummy se utilizan.
Variant _HilbertHotel::get_bus_info(RID id) {
return HilbertHotel::get_singleton()->get_bus_info(id);
}
Señales Binding
Es posible emitir señales a GDScript llamando al objeto ficticio de 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() {
}
MessageQueue¶
Para enviar comandos al SceneTree, MessageQueue es un búfer seguro para hilos que encola métodos set y call para otros hilos. Para encolar un comando, obtén el RID del objeto destino y utiliza push_call
, push_set
, o push_notification
para ejecutar el comportamiento deseado. La cola se vaciará siempre que se ejecute SceneTree::idle
o SceneTree::iteration
.
Referencias:¶
En resumen¶
Aquí está el código de ejemplo de GDScript:
extends Node
func _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))
Notas¶
El `Hotel de Hilbert<https://en.wikipedia.org/wiki/Hilbert%27s_paradox_of_the_Grand_Hotel>`__ real es imposible.
El código de ejemplo para conectar señales es bastante chapucero (poco elegante o poco limpio).