自定义Godot服务

简介

Godot implements multi-threading as servers. Servers are daemons which manage data, process it, and push the result. Servers implement the mediator pattern which interprets resource ID and process data for the engine and other modules. In addition, the server claims ownership for its RID allocations.

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

可以做什么?

  • 添加人工智能。
  • 添加自定义异步线程。
  • 添加对新输入设备的支持。
  • 添加写线程。
  • 添加自定义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/master/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/master/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
};

自定义托管资源数据

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中注册虚拟类。

/* 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();

绑定方法

虚拟类将单例方法绑定到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() {
}

MessageQueue

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

总结

这是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))

注意