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.