Implement all virtual Node methods in my GDExtension?

Godot Version

Godot_v4.5.1-stable_linux

Question

Hi everyone,

I’m developing a GDExtension in C++ for Godot 4, and my project compiles successfully using scons. However, as soon as I open the Godot editor and it tries to load the extension, I get a series of undefined symbol errors:

undefined symbol: _ZN5godot4Node16_physics_processEd
undefined symbol: _ZN5godot4Node11_enter_treeEv
undefined symbol: _ZNK5godot4Node27_get_configuration_warningsEv

this errors appears one at a time as soon as I add the override for the relative function in my code

It’s important to note that compilation and linking work perfectly fine, and the .so (on Linux) gets built without any warnings. These errors only appear when Godot tries to import and load the .gdextension file.

The godot-cpp version is from branch 4.5 and up to date with ‘origin/4.5’

this is the code of my main project files:

// src/redis_client.h
#ifndef REDIS_CLIENT_H
#define REDIS_CLIENT_H

#include <godot_cpp/classes/node.hpp>
#include <sw/redis++/redis.h>
#include <memory>
#include <thread>

namespace godot {
    class RedisClient : public Node {
        GDCLASS(RedisClient, Node)

    private:
        std::unique_ptr<sw::redis::Redis> _redis_client;
        String host = "127.0.0.1";
        int port = 6379;

    protected:
        static void _bind_methods();

    public:
        RedisClient();
        ~RedisClient();

        void _ready() override;
        void _exit_tree() override;
	void _enter_tree() override;
	void _process(double delta) override;
	void _physics_process(double delta) override;

        void connect_to_redis(const String& p_host = "127.0.0.1", int p_port = 6379);
        bool set_value(const String& key, const String& value);
        String get_value(const String& key);
        int64_t increment_value(const String& key, int64_t amount = 1);
        bool is_connected();

        void _connection_finished(bool success, const String& message);
    };
}
#endif // REDIS_CLIENT_H
// src/redis_client.cpp
#include "redis_client.h"
#include <godot_cpp/core/class_db.hpp>
#include <godot_cpp/variant/utility_functions.hpp>

using namespace godot;

RedisClient::RedisClient() {}
RedisClient::~RedisClient() {}

void RedisClient::_bind_methods() {
    // Metodi esposti a GDScript
    ClassDB::bind_method(D_METHOD("connect_to_redis", "host", "port"), &RedisClient::connect_to_redis, DEFVAL("127.0.0.1"), DEFVAL(6379));
    ClassDB::bind_method(D_METHOD("set_value", "key", "value"), &RedisClient::set_value);
    ClassDB::bind_method(D_METHOD("get_value", "key"), &RedisClient::get_value);
    ClassDB::bind_method(D_METHOD("increment_value", "key", "amount"), &RedisClient::increment_value, DEFVAL(1));
    ClassDB::bind_method(D_METHOD("is_connected"), &RedisClient::is_connected);
    ClassDB::bind_method(D_METHOD("_process", "delta"), &RedisClient::_process);
    ClassDB::bind_method(D_METHOD("_physics_process", "delta"), &RedisClient::_physics_process);

    // Segnale per notificare il risultato della connessione
    ADD_SIGNAL(MethodInfo("connection_status_changed", PropertyInfo(Variant::BOOL, "is_connected"), PropertyInfo(Variant::STRING, "message")));

    // Metodo privato chiamato tramite call_deferred
    ClassDB::bind_method(D_METHOD("_connection_finished", "success", "message"), &RedisClient::_connection_finished);
}

void RedisClient::_ready() {
    UtilityFunctions::print("[Redis C++] Inizializzazione, tentativo di connessione...");
    connect_to_redis(host, port);
}
void RedisClient::_enter_tree() {
    // Puoi lasciare vuoto se non ti serve alcuna logica qui
}
void RedisClient::_exit_tree() {
    // Chiude la connessione se l'oggetto viene distrutto
    if (_redis_client) {
        _redis_client.reset();
        UtilityFunctions::print("[Redis C++] Connessione a Redis chiusa.");
    }
}

void RedisClient::_process(double delta) {
    // Lasciato vuoto intenzionalmente.
}
void RedisClient::_physics_process(double delta) {
    // codice del frame fisico
}
bool RedisClient::is_connected() {
    return _redis_client != nullptr;
}

void RedisClient::connect_to_redis(const String& p_host, int p_port) {
    this->host = p_host;
    this->port = p_port;

    // Eseguiamo la connessione in un thread separato per non bloccare mai il gioco.
    std::thread connect_thread([this]() {
        try {
            sw::redis::ConnectionOptions opts;
            opts.host = this->host.utf8().get_data();
            opts.port = this->port;
            opts.socket_timeout = std::chrono::milliseconds(2000); // Timeout 2 sec

            _redis_client = std::make_unique<sw::redis::Redis>(opts);
            
            // Un ping è il modo migliore per verificare se la connessione è viva.
            _redis_client->ping();
            
            // Usa call_deferred per eseguire il codice sul thread principale di Godot
            this->call_deferred("_connection_finished", true, "Connesso a Redis con successo!");

        } catch (const sw::redis::Error &e) {
            String error_message = "[Redis C++] Errore di connessione: ";
            error_message += e.what();
            this->call_deferred("_connection_finished", false, error_message);
        }
    });
    connect_thread.detach(); // Il thread continuerà l'esecuzione in background
}

void RedisClient::_connection_finished(bool success, const String& message) {
    if (!success) {
        _redis_client.reset(); // Assicura che il puntatore sia nullo in caso di fallimento
    }
    UtilityFunctions::print(message);
    emit_signal("connection_status_changed", success, message);
}

bool RedisClient::set_value(const String& key, const String& value) {
    if (!is_connected()) return false;
    try {
        return _redis_client->set(key.utf8().get_data(), value.utf8().get_data());
    } catch (const sw::redis::Error &e) {
        UtilityFunctions::print("[Redis C++] Errore in set_value: ", e.what());
        return false;
    }
}

String RedisClient::get_value(const String& key) {
    if (!is_connected()) return "";
    try {
        auto val = _redis_client->get(key.utf8().get_data());
        return val ? String(val->c_str()) : String(); // Restituisce stringa vuota se non esiste
    } catch (const sw::redis::Error &e) {
        UtilityFunctions::print("[Redis C++] Errore in get_value: ", e.what());
        return "";
    }
}

int64_t RedisClient::increment_value(const String& key, int64_t amount) {
    if (!is_connected()) return 0;
    try {
        return _redis_client->incrby(key.utf8().get_data(), amount);
    } catch (const sw::redis::Error &e) {
        UtilityFunctions::print("[Redis C++] Errore in increment_value: ", e.what());
        return 0;
    }
}

in this situation, the project compiles good but when the extension is imported in godot i get the following error:

ERROR: Can't open dynamic library: /home/fabio/cms-redis/addons/godot_redis_cpp/bin/libGodotRedis.so. Error: /home/fabio/cms-redis/addons/godot_redis_cpp/bin/libGodotRedis.so: undefined symbol: _ZNK5godot4Node27_get_configuration_warningsEv.
   at: open_dynamic_library (drivers/unix/os_unix.cpp:1055)
ERROR: Can't open GDExtension dynamic library: 'res://addons/godot_redis_cpp/godot_redis_cpp.gdextension'.
   at: open_library (core/extension/gdextension.cpp:739)
ERROR: Can't open dynamic library: /home/fabio/cms-redis/addons/godot_redis_cpp/bin/libGodotRedis.so. Error: /home/fabio/cms-redis/addons/godot_redis_cpp/bin/libGodotRedis.so: undefined symbol: _ZNK5godot4Node27_get_configuration_warningsEv.
   at: open_dynamic_library (drivers/unix/os_unix.cpp:1055)

I can’t go on blindly adding override definitions one at a time, is there any fix for this, or at least a documentation on the Node object where I can find all the virtual functions I need to add?

Thanks for any help — I’d love to understand what’s different between the simple tutorial examples and my setup so I can fix this properly.

Do errors appear as you add overrides, or do you add overrides to make them go away? You seem to describe two opposite situations.
I feel like most of this code might be unrelated to the issue, and it would be easier to investigate and fix with a minimized example.

It happens wher i do NOT override

this is the first error i got

ERROR: Can't open dynamic library: /home/fabio/cms-redis/addons/godot_redis_cpp/bin/libGodotRedis.so. Error: /home/fabio/cms-redis/addons/godot_redis_cpp/bin/libGodotRedis.so: undefined symbol: _ZN5godot4Node8_processEd.
   at: open_dynamic_library (drivers/unix/os_unix.cpp:1055)
ERROR: Can't open GDExtension dynamic library: 'res://addons/godot_redis_cpp/godot_redis_cpp.gdextension'.
   at: open_library (core/extension/gdextension.cpp:739)
ERROR: Error loading extension: 'res://addons/godot_redis_cpp/godot_redis_cpp.gdextension'.
   at: load_extensions (core/extension/gdextension_manager.cpp:321)

when i did not override

void _process(double delta) override;

then when I override _process, the next error appeared, after overriding some other functions I realized that was not the way to go, but what is the correct way to go?

I see. So it only reports the first missing symbol when it fails.
It’s not normal that a library would be missing a base implementation for a virtual method. I’d look into the built lib and confirmed it’s actually not there, and not just listed with a different signature or something. Then it is probably the problem with the build configuration if it drops the necessary parts.

Basically, step back and figure out the basics. Plugging missing symbols with dummy placeholders is not the way. Check your register_types.cpp . Maybe try this with Godot 4.4, as there is not a GDExtension C++ example for 4.5 and it might just require different steps.

1 Like

Thank you for your help, stepping back was helpful to find a fix: the problem was in SConstruct of my project, i did not use this command

env = SConscript("godot-cpp/SConstruct")

so the godot-cpp libraries was not included in the final .so library

next i got the same problem with redis libraries and i had to apend the correct sources

redis_plus_plus_sources = Glob('thirdparty/redis-plus-plus/src/sw/redis++/*.cpp')

I’m quite new to cpp, and I also hate it a bit, but this trip of suffering was pretty enlightening in the end.

Thanks again for your help

1 Like