Question for Godot devs: How to add a custom server?

Godot Version

3.5.3

Question

I am talking about Godot servers that are used for engine singletons, as described here: Custom Godot servers — Godot Engine (3.5) documentation in English

The instructions are not very clear:

  • it isn’t obvious in what file some code should go,
  • some code has typos,
  • whether it should be put in the modules or servers folder,
  • mentioning some files that probably shouldn’t exist (unregister_types.cpp), but I could be wrong.

If I put it in the modules folder, then it fails to build, and if I put it in the servers folder in hilbert_hotel subfolder, then it doesn’t even recognize it.

Also, if it’s supposed to go in the servers folder, then there is register_server_types.cpp, not register_types.cpp, and so on.

I just need a simple server that is able to send a signal with an integer value.

Update:

I found the reason it fails to build. The reason is that the provided code uses Mutex::create() and Thread::create() methods, but they don’t exist anymore. The last time they appeared in Godot source code was in version 3.2.3, and that was almost 5 years ago. Therefore, the provided code couldn’t work. For the same reason (and also for some other reasons), the code for Godot 4.x won’t work, either. So, I’ll add godot-4 tag as well.

Now, I built it with the version 3.2.3, and it seems to be working there. So, I’ll see where else they used those Mutex and Thread functions and what replaced them in newer versions of Godot. Hopefully, that will show what has to be changed in a custom server to work in newer versions.

1 Like

I have it working now for Godot 3.5.3. If you want to try it yourself, first create the hilbert_hotel subfolder in the modules folder. Next, you’ll need 7 files in that hilbert_hotel subfolder:

  • prime_225.h
  • hilbert_hotel.h
  • hilbert_hotel.cpp
  • register_types.h
  • register_types.cpp
  • config.py
  • SCsub

Now, put the following code in those files.

prime_225.h:

/* 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
};

hilbert_hotel.h:

/* hilbert_hotel.h */

#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 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() {};
};

/*----------------------------------------------------------------------------*/

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();
};

/*----------------------------------------------------------------------------*/

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

hilbert_hotel.cpp:

/* hilbert_hotel.cpp */

#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;
    thread.start(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() {
    mutex.unlock();
}

void HilbertHotel::lock() {
    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() {
    exit_thread = true;
    thread.wait_to_finish();
}

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;
}

/*----------------------------------------------------------------------------*/

_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() {
}

register_types.h:

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

register_types.cpp:

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

config.py:

# config.py

def can_build(env, platform):
    return True

def configure(env):
    pass

SCsub:

Import('env')

env.add_source_files(env.modules_sources, "*.cpp") # Add all cpp files to the build

You should be able to build Godot now with the Hilbert Hotel server.