I been following the official tutorial but can not call even a simple C++ function from GDScript. I can not figure out what the problem is.
I had this code working for Godot 3.5, what I am doing now is trying to convert is to 4.1.
The error i get when trying to call any function, in this example close() is this:
Invalid call. Nonexistent function 'close' in base 'TcpSocket'.
Here is the code for my class:
/*************************************************************************/
/* TcpSocket.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT TCPSOCK PLUGIN */
/* https://github.com/flodihn/GodotTcp ocket */
/*************************************************************************/
/* Copyright (c) 2023 Christian Flodihn. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#include <stdio.h>
#include "tcp_socket.h"
#include <godot_cpp/core/class_db.hpp>
#include <godot_cpp/variant/utility_functions.hpp>
using namespace std;
using namespace godot;
using namespace boost::asio;
using error_code = boost::system::error_code;
/*
* TODO: Use shared or unique pointers for these variables.
*
* Normally it is bad practice to put variables like this
* outside the class in a C++ project. But because of
* issues BOOST it seems these can only be put in a class
* using shared or unique pointers.
*
* Implications of putting variables here is that MULTIPLE
* INSTANCES of TcpSocket class WILL PROBABLY NOT WORK.
* In practice this is not as problem as long as the code
* is only used as a client.
*/
boost::asio::io_service ioService;
boost::asio::io_context ioContext;
boost::asio::ssl::context sslContext(boost::asio::ssl::context::tls);
boost::asio::ip::tcp::socket tcpSocket(ioService);
boost::asio::ssl::stream<ip::tcp::socket> secureSocket(ioContext, sslContext);
void TcpSocket::_bind_methods() {
UtilityFunctions::print("Binding methods...");
ClassDB::bind_method(D_METHOD("connect_to_host", "host", "port"), &TcpSocket::connect_to_host);
ClassDB::bind_method(D_METHOD("close"), &TcpSocket::close);
ClassDB::bind_method(D_METHOD("set_message_header_size"), &TcpSocket::set_message_header_size);
ClassDB::bind_method(D_METHOD("set_message_buffer"), &TcpSocket::set_message_buffer);
ClassDB::bind_method(D_METHOD("set_ssl"), &TcpSocket::set_ssl);
ClassDB::bind_method(D_METHOD("set_blocking"), &TcpSocket::set_blocking);
ClassDB::bind_method(D_METHOD("set_debug", "trueOrFalse"), &TcpSocket::set_debug);
ClassDB::bind_method(D_METHOD("receive_message"), &TcpSocket::receive_message);
ClassDB::bind_method(D_METHOD("send_message"), &TcpSocket::send_message);
//ClassDB::bind_method(D_METHOD("ntohs"), &TcpSocket::ntohs);
//ClassDB::bind_method(D_METHOD("htonl"), &TcpSocket::htonl);
//ClassDB::bind_property<TcpSocket, bool>(D_METHOD("debug"), &TcpSocket::debug, false);
}
TcpSocket::TcpSocket() {
receiveData.numBytesReceived = 0;
// The state is ignored if we are in blocking mode.
receiveData.state = NonBlockingState::RECEIVE_HEADER;
}
TcpSocket::~TcpSocket() {
// Add your cleanup here.
}
void TcpSocket::_process(double delta) {
}
int TcpSocket::connect_to_host(Variant host, int port)
{
debug_print("Connecting to host...");
int error = _connect(host.stringify().utf8().get_data(), port);
if(error) {
debug_print("Failed to connect.");
} else {
debug_print("Connected successfully.");
}
return error;
}
void TcpSocket::close()
{
::free(receiveData.header_buffer);
::free(receiveData.packet_buffer);
//socketWrapper.close();
}
void TcpSocket::set_ssl(bool trueOrFalse)
{
//socketWrapper.sslEnabled = trueOrFalse;
}
void TcpSocket::set_blocking(bool trueOrFalse)
{
blocking = trueOrFalse;
//socketWrapper.blocking = trueOrFalse;
}
void TcpSocket::set_debug(bool trueOrFalse)
{
debug = trueOrFalse;
}
void TcpSocket::set_message_header_size(int size)
{
if (!(size == 2 || size == 4)) {
debug_print("RawSocket.set_message_header_size error: Size must be 2 or 4 bytes.\n");
return;
}
headerSize = size;
receiveData.header_buffer = (char*) malloc(headerSize);
receiveData.numBytesToReceive = headerSize;
}
void TcpSocket::set_message_buffer(godot::Ref<godot::StreamPeerBuffer> messageBufferRef)
{
messageBuffer = messageBufferRef.ptr();
receiveData.packet_buffer = (char*) malloc(messageBuffer->get_size());
}
void TcpSocket::debug_print(const char* output)
{
if (!debug) return;
UtilityFunctions::print(output);
}
int TcpSocket::receive_message()
{
if(blocking) {
return blocking_receive_message();
}
return non_blocking_receive_message();
}
int TcpSocket::blocking_receive_message()
{
debug_print("blocking_receive_message: Waiting for header...\n");
unsigned short rawHeader = 0;
while(rawHeader == 0) {
int result = receive_ushort(rawHeader);
if(result == -1) {
char debug_msg[128];
sprintf(debug_msg, "blocking_receive_message: Warning, received header size %i\n.", rawHeader);
debug_print(debug_msg);
return -1;
}
}
int messageSize = ntohs(rawHeader);
char debug_msg[128];
sprintf(debug_msg, "blocking_receive_message: Header with size %i received, waiting for message...\n", messageSize);
debug_print(debug_msg);
int result = blocking_receive(messageSize);
sprintf(debug_msg, "blocking_receive_message: Message received, returning %i.\n", result);
debug_print(debug_msg);
return result;
}
int TcpSocket::blocking_receive(int numBytes)
{
int result = receive_bytes(numBytes, receiveData.packet_buffer);
if(result != -1)
{
fill_message_buffer(receiveData.packet_buffer, numBytes);
char debug_msg[128];
sprintf(debug_msg, "blocking_receive: Filled godot message buffer with %i bytes.\n", numBytes);
debug_print(debug_msg);
} else {
debug_print("blocking_receive: Error receiving message!\n");
}
return result;
}
/*
* Returns:
* -1: Socket disconnceted.
* 0: More data to receive, call again.
* 1..n: The size of the message received. Godot StreamPeerBuffer contains a full message.
*/
int TcpSocket::non_blocking_receive_message()
{
if(receiveData.state == NonBlockingState::RECEIVE_HEADER) {
return non_blocking_receive_header();
}
return non_blocking_receive_packet();
}
int TcpSocket::non_blocking_receive_header() {
int result = receive_bytes(
receiveData.numBytesToReceive,
receiveData.header_buffer + receiveData.numBytesReceived);
if(result > 0) {
receiveData.numBytesToReceive -= result;
receiveData.numBytesReceived += result;
char debug_msg[128];
sprintf(
debug_msg,
"non_blocking_receive_header: Received %i/%i bytes header.\n",
receiveData.numBytesReceived,
headerSize);
debug_print(debug_msg);
}
if(receiveData.numBytesReceived == headerSize) {
if(headerSize == 2) {
unsigned short tmpHeader = 0;
memcpy(&tmpHeader, receiveData.header_buffer, 2);
receiveData.packet_size = ntohs(tmpHeader);
cout << "non_blocking_receive_header: receiveData.header_buffer" << endl;
for(int i = 0; i < headerSize; i++) {
cout << receiveData.header_buffer[i];
}
cout << endl;
cout << "packet_size: " << receiveData.packet_size << endl;
} else if (headerSize == 4) {
receiveData.packet_size = ntohl((unsigned int) receiveData.header_buffer);
}
memset(receiveData.header_buffer, 0, headerSize);
receiveData.state = NonBlockingState::RECEIVE_PACKET;
receiveData.numBytesReceived = 0;
receiveData.numBytesToReceive = receiveData.packet_size;
char debug_msg[128];
sprintf(
debug_msg,
"non_blocking_receive_header: Full header received containing messageSize: %i.\n",
receiveData.packet_size);
debug_print(debug_msg);
}
return result;
}
int TcpSocket::non_blocking_receive_packet() {
int result = receive_bytes(
receiveData.numBytesToReceive,
receiveData.packet_buffer + receiveData.numBytesReceived);
if(result > 0) {
receiveData.numBytesToReceive -= result;
receiveData.numBytesReceived += result;
}
if(receiveData.numBytesReceived == receiveData.packet_size) {
fill_message_buffer(receiveData.packet_buffer, receiveData.packet_size);
memset(receiveData.packet_buffer, 0, receiveData.packet_size);
receiveData.state = NonBlockingState::RECEIVE_HEADER;
receiveData.packet_size = 0;
receiveData.numBytesReceived = 0;
receiveData.numBytesToReceive = headerSize;
}
return result;
}
int TcpSocket::non_blocking_receive(int numBytes)
{
int result = receive_bytes(numBytes, receiveData.packet_buffer);
if(result == -1 || result == 0) {
return result;
}
fill_message_buffer(receiveData.packet_buffer, numBytes);
char debug_msg[128];
sprintf(debug_msg, "blocking_receive: Filled godot message buffer with %i bytes.", numBytes);
debug_print(debug_msg);
return result;
}
int TcpSocket::send_message(godot::PackedByteArray sendBuffer)
{
int numBytes = sendBuffer.size();
// TODO: Implement header size 4.
if(headerSize == 2 || headerSize == 4) {
unsigned short header = numBytes;
send_ushort(numBytes);
}
const char* bytes = (const char*) sendBuffer.ptr();
_send_bytes(bytes, numBytes);
return 1;
}
void TcpSocket::fill_message_buffer(char* sourceBuffer, int numBytes)
{
messageBuffer->seek(0);
godot::PackedByteArray* poolByteArray = &messageBuffer->get_data_array();
for (int i = 0; i < numBytes; i++) {
//poolByteArray->set(i, sourceBuffer[i]);
}
}
short TcpSocket::ntohs(short var)
{
return(::ntohs(var));
}
int TcpSocket::htonl(int var)
{
return(::htonl(var));
}
/*
* These underscore methods should probably not exist (here), either rename, move them out from this class or merge
* them into the existing methods.
*/
int TcpSocket::_connect(const char* hostname, int port)
{
try {
boost::system::error_code ec;
std::string portAsString = std::to_string(port);
ip::tcp::resolver resolver(ioService);
auto endpoints = resolver.resolve(hostname, portAsString);
if(sslEnabled) {
boost::asio::connect(secureSocket.next_layer(), endpoints);
secureSocket.set_verify_mode(ssl::verify_none);
secureSocket.handshake(ssl::stream_base::client);
secureSocket.next_layer().non_blocking(!blocking, ec);
} else {
boost::asio::connect(tcpSocket, endpoints);
tcpSocket.non_blocking(!blocking, ec);
if(ec) {
cout << "Error setting non-blocking to " << !blocking << " error: " << ec.message() << endl;
} else {
cout << "Socket non-blocking set to " << !blocking << endl;
}
}
} catch(boost::system::error_code &error) {
cout << "Connect failed: " << error.message() << endl;
lastError = error;
return 1;
}
return 0;
}
void TcpSocket::_close()
{
if(sslEnabled) {
secureSocket.shutdown();
} else {
tcpSocket.shutdown(ip::tcp::socket::shutdown_both);
}
}
const int TcpSocket::receive_ushort(unsigned short &header)
{
int numBytes = sizeof(unsigned short);
buffer.prepare(numBytes);
int result = _receive_bytes(numBytes);
if(result == -1 || result == 0) {
return result;
}
// How to know when full header is received in non-blocking mode?
header = *buffer_cast<const unsigned short*>(buffer.data());
buffer.consume(numBytes);
return result;
}
int TcpSocket::receive_bytes(int numBytes, char* byte_buffer)
{
buffer.prepare(numBytes);
int result = _receive_bytes(numBytes);
if(result == -1 || result == 0) {
buffer.consume(numBytes);
return result;
}
memcpy(byte_buffer, buffer_cast<const char*>(buffer.data()), result);
cout << "Received: " << result << " bytes>>>" << endl;
for(int i = 0; i < result; i++) {
cout << byte_buffer[i];
}
cout << endl;
buffer.consume(numBytes);
return result;
}
int TcpSocket::send_ushort(unsigned short ushort)
{
boost::system::error_code error;
ushort = htons(ushort);
if(sslEnabled) {
boost::asio::write(secureSocket, boost::asio::buffer(&ushort, 2), transfer_all(), error);
} else {
boost::asio::write(tcpSocket, boost::asio::buffer(&ushort, 2), transfer_all(), error);
}
cout << "sent ushort: " << error.message() << endl;
if(error && error != error::eof ) {
cout << "send failed: " << error.message() << endl;
lastError = error;
return -1;
}
return 0;
}
int TcpSocket::_send_bytes(const char* bytes, int numBytes)
{
boost::system::error_code error;
if(sslEnabled) {
boost::asio::write(secureSocket, boost::asio::buffer(bytes, numBytes), transfer_all(), error);
} else {
boost::asio::write(tcpSocket, boost::asio::buffer(bytes, numBytes), transfer_all(), error);
}
cout << "sent bytes: " << error.message() << endl;
if(error && error != error::eof ) {
cout << "send failed: " << error.message() << endl;
lastError = error;
return -1;
}
return 0;
}
int TcpSocket::_receive_bytes(int numBytes)
{
boost::system::error_code error;
int bytesReceived = 0;
if(sslEnabled) {
bytesReceived = read(secureSocket, buffer, transfer_exactly(numBytes), error);
} else {
bytesReceived = read(tcpSocket, buffer, transfer_exactly(numBytes), error);
}
if(error) {
if(blocking == false && error == error::would_block) {
return bytesReceived;
}
if(error != error::eof ) {
cout << "receive failed: " << error.message() << endl;
lastError = error;
return -1;
}
}
return bytesReceived;
}
And this is the header file:
/*************************************************************************/
/* TcpSocket.hpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT TcpSocket PLUGIN */
/* https://github.com/flodihn/GodotTcpSocket */
/*************************************************************************/
/* Copyright (c) 2021 Christian Flodihn. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#ifndef GODOT_TCP_SOCKET_H
#define GODOT_TCP_SOCKET_H
#include <stdio.h>
#include <godot_cpp/classes/node.hpp>
#include <godot_cpp/classes/stream_peer_buffer.hpp>
#include <godot_cpp/variant/packed_byte_array.hpp>
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
typedef enum {
RECEIVE_HEADER,
RECEIVE_PACKET
} NonBlockingState;
struct ReceiveData {
NonBlockingState state;
int numBytesReceived;
int numBytesToReceive;
// Internal buffer for receiving the header. The header is allocated
// to the size of the header set in set_header_size function.
char* header_buffer;
// Internal buffer for receiving bytes from the socket,
// will allocated same number of bytes as the godot stream peer buffer.
char* packet_buffer;
int packet_size;
};
namespace godot {
class TcpSocket : public Node {
GDCLASS(TcpSocket, Node)
private:
ReceiveData receiveData;
bool blocking = false;
bool debug = true;
bool sslEnabled = false;
unsigned int headerSize = 0;
boost::asio::streambuf buffer;
boost::system::error_code lastError;
// External buffer used to send back bytes to Godot when receiving full
// message with 2 or 4 byte header.
StreamPeerBuffer* messageBuffer;
int blocking_receive(int numBytes);
int non_blocking_receive(int numBytes);
void debug_print(const char * output);
void fill_message_buffer(char*, int messageSize);
int blocking_receive_message();
int non_blocking_receive_message();
int non_blocking_receive_header();
int non_blocking_receive_packet();
int _connect(const char* hostname, int port);
void _close();
const int receive_ushort(unsigned short &header);
int receive_bytes(int numBytes, char* byte_buffer);
int send_ushort(unsigned short ushort);
int _send_bytes(const char* bytes, int numBytes);
int _receive_bytes(int numBytes);
protected:
static void _bind_methods();
public:
TcpSocket();
~TcpSocket();
void _process(double delta);
int connect_to_host(Variant host, int port);
void close();
void set_message_header_size(int size);
void set_message_buffer(Ref<godot::StreamPeerBuffer> messageBufferRef);
void set_ssl(bool trueOrFalse);
void set_blocking(bool trueOrFalse);
void set_debug(bool trueOrFalse);
void set_receive_buffer(unsigned size);
int receive_message();
int send_message(PackedByteArray sendBuffer);
short ntohs(short var);
int htonl(int var);
};
}
#endif