feat: updated engine version to 4.4-rc1

This commit is contained in:
Sara 2025-02-23 14:38:14 +01:00
parent ee00efde1f
commit 21ba8e33af
5459 changed files with 1128836 additions and 198305 deletions

View file

@ -1,4 +1,5 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
Import("env")
Import("env_modules")

View file

@ -60,7 +60,7 @@
<member name="inbound_buffer_size" type="int" setter="set_inbound_buffer_size" getter="get_inbound_buffer_size" default="65535">
The inbound buffer size for connected peers. See [member WebSocketPeer.inbound_buffer_size] for more details.
</member>
<member name="max_queued_packets" type="int" setter="set_max_queued_packets" getter="get_max_queued_packets" default="2048">
<member name="max_queued_packets" type="int" setter="set_max_queued_packets" getter="get_max_queued_packets" default="4096">
The maximum number of queued packets for connected peers. See [member WebSocketPeer.max_queued_packets] for more details.
</member>
<member name="outbound_buffer_size" type="int" setter="set_outbound_buffer_size" getter="get_outbound_buffer_size" default="65535">

View file

@ -61,6 +61,7 @@
<param index="1" name="tls_client_options" type="TLSOptions" default="null" />
<description>
Connects to the given URL. TLS certificates will be verified against the hostname when connecting using the [code]wss://[/code] protocol. You can pass the optional [param tls_client_options] parameter to customize the trusted certification authorities, or disable the common name verification. See [method TLSOptions.client] and [method TLSOptions.client_unsafe].
[b]Note:[/b] This method is non-blocking, and will return [constant OK] before the connection is established as long as the provided parameters are valid and the peer is not in an invalid state (e.g. already connected). Regularly call [method poll] (e.g. during [Node] process) and check the result of [method get_ready_state] to know whether the connection succeeds or fails.
[b]Note:[/b] To avoid mixed content warnings or errors in Web, you may have to use a [param url] that starts with [code]wss://[/code] (secure) instead of [code]ws://[/code]. When doing so, make sure to use the fully qualified domain name that matches the one defined in the server's TLS certificate. Do not connect directly via the IP address for [code]wss://[/code] connections, as it won't match with the TLS certificate.
</description>
</method>
@ -155,10 +156,14 @@
The extra HTTP headers to be sent during the WebSocket handshake.
[b]Note:[/b] Not supported in Web exports due to browsers' restrictions.
</member>
<member name="heartbeat_interval" type="float" setter="set_heartbeat_interval" getter="get_heartbeat_interval" default="0.0">
The interval (in seconds) at which the peer will automatically send WebSocket "ping" control frames. When set to [code]0[/code], no "ping" control frames will be sent.
[b]Note:[/b] Has no effect in Web exports due to browser restrictions.
</member>
<member name="inbound_buffer_size" type="int" setter="set_inbound_buffer_size" getter="get_inbound_buffer_size" default="65535">
The size of the input buffer in bytes (roughly the maximum amount of memory that will be allocated for the inbound packets).
</member>
<member name="max_queued_packets" type="int" setter="set_max_queued_packets" getter="get_max_queued_packets" default="2048">
<member name="max_queued_packets" type="int" setter="set_max_queued_packets" getter="get_max_queued_packets" default="4096">
The maximum amount of packets that will be allowed in the queues (both inbound and outbound).
</member>
<member name="outbound_buffer_size" type="int" setter="set_outbound_buffer_size" getter="get_outbound_buffer_size" default="65535">

View file

@ -34,7 +34,6 @@
#include "../remote_debugger_peer_websocket.h"
#include "core/config/project_settings.h"
#include "editor/editor_log.h"
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
@ -77,8 +76,8 @@ Error EditorDebuggerServerWebSocket::start(const String &p_uri) {
// Optionally override
if (!p_uri.is_empty() && p_uri != "ws://") {
String scheme, path;
Error err = p_uri.parse_url(scheme, bind_host, bind_port, path);
String scheme, path, fragment;
Error err = p_uri.parse_url(scheme, bind_host, bind_port, path, fragment);
ERR_FAIL_COND_V(err != OK, ERR_INVALID_PARAMETER);
ERR_FAIL_COND_V(!bind_host.is_valid_ip_address() && bind_host != "*", ERR_INVALID_PARAMETER);
}

View file

@ -68,8 +68,9 @@ Error EMWSPeer::connect_to_url(const String &p_url, Ref<TLSOptions> p_tls_option
String host;
String path;
String scheme;
String fragment;
int port = 0;
Error err = p_url.parse_url(scheme, host, port, path);
Error err = p_url.parse_url(scheme, host, port, path, fragment);
ERR_FAIL_COND_V_MSG(err != OK, err, "Invalid URL: " + p_url);
if (scheme.is_empty()) {
@ -92,7 +93,7 @@ Error EMWSPeer::connect_to_url(const String &p_url, Ref<TLSOptions> p_tls_option
requested_url = scheme + host;
if (port && ((scheme == "ws://" && port != 80) || (scheme == "wss://" && port != 443))) {
requested_url += ":" + String::num(port);
requested_url += ":" + String::num_int64(port);
}
if (!path.is_empty()) {

View file

@ -68,7 +68,7 @@ private:
String selected_protocol;
String requested_url;
static WebSocketPeer *_create() { return memnew(EMWSPeer); }
static WebSocketPeer *_create(bool p_notify_postinitialize) { return static_cast<WebSocketPeer *>(ClassDB::creator<EMWSPeer>(p_notify_postinitialize)); }
static void _esws_on_connect(void *obj, char *proto);
static void _esws_on_message(void *obj, const uint8_t *p_data, int p_data_size, int p_is_string);
static void _esws_on_error(void *obj);
@ -84,7 +84,7 @@ public:
virtual int get_available_packet_count() const override;
virtual Error get_packet(const uint8_t **r_buffer, int &r_buffer_size) override;
virtual Error put_packet(const uint8_t *p_buffer, int p_buffer_size) override;
virtual int get_max_packet_size() const override { return packet_buffer.size(); };
virtual int get_max_packet_size() const override { return packet_buffer.size(); }
// WebSocketPeer
virtual Error send(const uint8_t *p_buffer, int p_buffer_size, WriteMode p_mode) override;

View file

@ -104,6 +104,14 @@ public:
return _queued;
}
int payload_space_left() const {
return _payload.space_left();
}
int packets_space_left() const {
return _packets.size() - _queued;
}
void clear() {
_payload.resize(0);
_packets.resize(0);

View file

@ -44,7 +44,6 @@
#include "editor/editor_debugger_server_websocket.h"
#endif
#include "core/config/project_settings.h"
#include "core/debugger/engine_debugger.h"
#include "core/error/error_macros.h"

View file

@ -30,6 +30,7 @@
#include "websocket_multiplayer_peer.h"
#include "core/io/stream_peer_tls.h"
#include "core/os/os.h"
WebSocketMultiplayerPeer::WebSocketMultiplayerPeer() {

View file

@ -34,7 +34,6 @@
#include "websocket_peer.h"
#include "core/error/error_list.h"
#include "core/io/stream_peer_tls.h"
#include "core/io/tcp_server.h"
#include "core/templates/list.h"
#include "scene/main/multiplayer_peer.h"

View file

@ -30,7 +30,7 @@
#include "websocket_peer.h"
WebSocketPeer *(*WebSocketPeer::_create)() = nullptr;
WebSocketPeer *(*WebSocketPeer::_create)(bool p_notify_postinitialize) = nullptr;
WebSocketPeer::WebSocketPeer() {
}
@ -70,6 +70,9 @@ void WebSocketPeer::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_max_queued_packets", "buffer_size"), &WebSocketPeer::set_max_queued_packets);
ClassDB::bind_method(D_METHOD("get_max_queued_packets"), &WebSocketPeer::get_max_queued_packets);
ClassDB::bind_method(D_METHOD("set_heartbeat_interval", "interval"), &WebSocketPeer::set_heartbeat_interval);
ClassDB::bind_method(D_METHOD("get_heartbeat_interval"), &WebSocketPeer::get_heartbeat_interval);
ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "supported_protocols"), "set_supported_protocols", "get_supported_protocols");
ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "handshake_headers"), "set_handshake_headers", "get_handshake_headers");
@ -78,6 +81,8 @@ void WebSocketPeer::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "max_queued_packets"), "set_max_queued_packets", "get_max_queued_packets");
ADD_PROPERTY(PropertyInfo(Variant::INT, "heartbeat_interval"), "set_heartbeat_interval", "get_heartbeat_interval");
BIND_ENUM_CONSTANT(WRITE_MODE_TEXT);
BIND_ENUM_CONSTANT(WRITE_MODE_BINARY);
@ -151,3 +156,12 @@ void WebSocketPeer::set_max_queued_packets(int p_max_queued_packets) {
int WebSocketPeer::get_max_queued_packets() const {
return max_queued_packets;
}
double WebSocketPeer::get_heartbeat_interval() const {
return heartbeat_interval_msec / 1000.0;
}
void WebSocketPeer::set_heartbeat_interval(double p_interval) {
ERR_FAIL_COND(p_interval < 0);
heartbeat_interval_msec = p_interval * 1000.0;
}

View file

@ -59,7 +59,7 @@ private:
virtual Error _send_bind(const PackedByteArray &p_data, WriteMode p_mode = WRITE_MODE_BINARY);
protected:
static WebSocketPeer *(*_create)();
static WebSocketPeer *(*_create)(bool p_notify_postinitialize);
static void _bind_methods();
@ -71,14 +71,15 @@ protected:
int outbound_buffer_size = DEFAULT_BUFFER_SIZE;
int inbound_buffer_size = DEFAULT_BUFFER_SIZE;
int max_queued_packets = 2048;
int max_queued_packets = 4096;
uint64_t heartbeat_interval_msec = 0;
public:
static WebSocketPeer *create() {
static WebSocketPeer *create(bool p_notify_postinitialize = true) {
if (!_create) {
return nullptr;
}
return _create();
return _create(p_notify_postinitialize);
}
virtual Error connect_to_url(const String &p_url, Ref<TLSOptions> p_options = Ref<TLSOptions>()) = 0;
@ -117,6 +118,9 @@ public:
void set_max_queued_packets(int p_max_queued_packets);
int get_max_queued_packets() const;
double get_heartbeat_interval() const;
void set_heartbeat_interval(double p_interval);
WebSocketPeer();
~WebSocketPeer();
};

View file

@ -295,6 +295,7 @@ Error WSLPeer::_do_server_handshake() {
resolver.stop();
// Response sent, initialize wslay context.
wslay_event_context_server_init(&wsl_ctx, &_wsl_callbacks, this);
wslay_event_config_set_no_buffering(wsl_ctx, 1);
wslay_event_config_set_max_recv_msg_length(wsl_ctx, inbound_buffer_size);
in_buffer.resize(nearest_shift(inbound_buffer_size), max_queued_packets);
packet_buffer.resize(inbound_buffer_size);
@ -403,6 +404,7 @@ void WSLPeer::_do_client_handshake() {
ERR_FAIL_MSG("Invalid response headers.");
}
wslay_event_context_client_init(&wsl_ctx, &_wsl_callbacks, this);
wslay_event_config_set_no_buffering(wsl_ctx, 1);
wslay_event_config_set_max_recv_msg_length(wsl_ctx, inbound_buffer_size);
in_buffer.resize(nearest_shift(inbound_buffer_size), max_queued_packets);
packet_buffer.resize(inbound_buffer_size);
@ -482,8 +484,9 @@ Error WSLPeer::connect_to_url(const String &p_url, Ref<TLSOptions> p_options) {
String host;
String path;
String scheme;
String fragment;
int port = 0;
Error err = p_url.parse_url(scheme, host, port, path);
Error err = p_url.parse_url(scheme, host, port, path, fragment);
ERR_FAIL_COND_V_MSG(err != OK, err, "Invalid URL: " + p_url);
if (scheme.is_empty()) {
scheme = "ws://";
@ -567,8 +570,15 @@ ssize_t WSLPeer::_wsl_recv_callback(wslay_event_context_ptr ctx, uint8_t *data,
wslay_event_set_error(ctx, WSLAY_ERR_CALLBACK_FAILURE);
return -1;
}
// Make sure we don't read more than what our buffer can hold.
size_t buffer_limit = MIN(peer->in_buffer.payload_space_left(), peer->in_buffer.packets_space_left() * 2); // The minimum size of a websocket message is 2 bytes.
size_t to_read = MIN(len, buffer_limit);
if (to_read == 0) {
wslay_event_set_error(ctx, WSLAY_ERR_WOULDBLOCK);
return -1;
}
int read = 0;
Error err = conn->get_partial_data(data, len, read);
Error err = conn->get_partial_data(data, to_read, read);
if (err != OK) {
print_verbose("Websocket get data error: " + itos(err) + ", read (should be 0!): " + itos(read));
wslay_event_set_error(ctx, WSLAY_ERR_CALLBACK_FAILURE);
@ -581,6 +591,26 @@ ssize_t WSLPeer::_wsl_recv_callback(wslay_event_context_ptr ctx, uint8_t *data,
return read;
}
void WSLPeer::_wsl_recv_start_callback(wslay_event_context_ptr ctx, const struct wslay_event_on_frame_recv_start_arg *arg, void *user_data) {
WSLPeer *peer = (WSLPeer *)user_data;
uint8_t op = arg->opcode;
if (op == WSLAY_TEXT_FRAME || op == WSLAY_BINARY_FRAME) {
// Get ready to process a data package.
PendingMessage &pm = peer->pending_message;
pm.opcode = op;
}
}
void WSLPeer::_wsl_frame_recv_chunk_callback(wslay_event_context_ptr ctx, const struct wslay_event_on_frame_recv_chunk_arg *arg, void *user_data) {
WSLPeer *peer = (WSLPeer *)user_data;
PendingMessage &pm = peer->pending_message;
if (pm.opcode != 0) {
// Only write the payload.
peer->in_buffer.write_packet(arg->data, arg->data_length, nullptr);
pm.payload_size += arg->data_length;
}
}
ssize_t WSLPeer::_wsl_send_callback(wslay_event_context_ptr ctx, const uint8_t *data, size_t len, int flags, void *user_data) {
WSLPeer *peer = (WSLPeer *)user_data;
Ref<StreamPeer> conn = peer->connection;
@ -626,25 +656,26 @@ void WSLPeer::_wsl_msg_recv_callback(wslay_event_context_ptr ctx, const struct w
return;
}
if (peer->ready_state == STATE_CLOSING) {
return;
if (op == WSLAY_PONG) {
peer->heartbeat_waiting = false;
} else if (op == WSLAY_TEXT_FRAME || op == WSLAY_BINARY_FRAME) {
PendingMessage &pm = peer->pending_message;
ERR_FAIL_COND(pm.opcode != op);
// Only write the packet (since it's now completed).
uint8_t is_string = pm.opcode == WSLAY_TEXT_FRAME ? 1 : 0;
peer->in_buffer.write_packet(nullptr, pm.payload_size, &is_string);
pm.clear();
}
if (op == WSLAY_TEXT_FRAME || op == WSLAY_BINARY_FRAME) {
// Message.
uint8_t is_string = arg->opcode == WSLAY_TEXT_FRAME ? 1 : 0;
peer->in_buffer.write_packet(arg->msg, arg->msg_length, &is_string);
}
// Ping or pong.
// Ping.
}
wslay_event_callbacks WSLPeer::_wsl_callbacks = {
_wsl_recv_callback,
_wsl_send_callback,
_wsl_genmask_callback,
nullptr, /* on_frame_recv_start_callback */
nullptr, /* on_frame_recv_callback */
nullptr, /* on_frame_recv_end_callback */
_wsl_recv_start_callback,
_wsl_frame_recv_chunk_callback,
nullptr,
_wsl_msg_recv_callback
};
@ -679,7 +710,31 @@ void WSLPeer::poll() {
if (ready_state == STATE_OPEN || ready_state == STATE_CLOSING) {
ERR_FAIL_NULL(wsl_ctx);
uint64_t ticks = OS::get_singleton()->get_ticks_msec();
int err = 0;
if (heartbeat_interval_msec != 0 && ticks - last_heartbeat > heartbeat_interval_msec && ready_state == STATE_OPEN) {
if (heartbeat_waiting) {
wslay_event_context_free(wsl_ctx);
wsl_ctx = nullptr;
close(-1);
return;
}
heartbeat_waiting = true;
struct wslay_event_msg msg;
msg.opcode = WSLAY_PING;
msg.msg = nullptr;
msg.msg_length = 0;
err = wslay_event_queue_msg(wsl_ctx, &msg);
if (err == 0) {
last_heartbeat = ticks;
} else {
print_verbose("Websocket (wslay) failed to send ping: " + itos(err));
wslay_event_context_free(wsl_ctx);
wsl_ctx = nullptr;
close(-1);
return;
}
}
if ((err = wslay_event_recv(wsl_ctx)) != 0 || (err = wslay_event_send(wsl_ctx)) != 0) {
// Error close.
print_verbose("Websocket (wslay) poll error: " + itos(err));
@ -688,12 +743,37 @@ void WSLPeer::poll() {
close(-1);
return;
}
if (wslay_event_get_close_sent(wsl_ctx) && wslay_event_get_close_received(wsl_ctx)) {
// Clean close.
wslay_event_context_free(wsl_ctx);
wsl_ctx = nullptr;
close(-1);
return;
if (wslay_event_get_close_sent(wsl_ctx)) {
if (wslay_event_get_close_received(wsl_ctx)) {
// Clean close.
wslay_event_context_free(wsl_ctx);
wsl_ctx = nullptr;
close(-1);
return;
} else if (!wslay_event_get_read_enabled(wsl_ctx)) {
// Some protocol error caused wslay to stop processing incoming events, we'll never receive a close from the other peer.
close_code = wslay_event_get_status_code_sent(wsl_ctx);
switch (close_code) {
case WSLAY_CODE_MESSAGE_TOO_BIG:
close_reason = "Message too big";
break;
case WSLAY_CODE_PROTOCOL_ERROR:
close_reason = "Protocol error";
break;
case WSLAY_CODE_ABNORMAL_CLOSURE:
close_reason = "Abnormal closure";
break;
case WSLAY_CODE_INVALID_FRAME_PAYLOAD_DATA:
close_reason = "Invalid frame payload data";
break;
default:
close_reason = "Unknown";
}
wslay_event_context_free(wsl_ctx);
wsl_ctx = nullptr;
close(-1);
return;
}
}
}
}
@ -780,8 +860,12 @@ void WSLPeer::close(int p_code, String p_reason) {
}
}
in_buffer.clear();
packet_buffer.resize(0);
if (ready_state == STATE_CLOSED) {
heartbeat_waiting = false;
in_buffer.clear();
packet_buffer.resize(0);
pending_message.clear();
}
}
IPAddress WSLPeer::get_connected_host() const {

View file

@ -38,9 +38,7 @@
#include "core/crypto/crypto_core.h"
#include "core/error/error_list.h"
#include "core/io/packet_peer.h"
#include "core/io/stream_peer_tcp.h"
#include "core/templates/ring_buffer.h"
#include <wslay/wslay.h>
@ -49,10 +47,13 @@
class WSLPeer : public WebSocketPeer {
private:
static CryptoCore::RandomGenerator *_static_rng;
static WebSocketPeer *_create() { return memnew(WSLPeer); }
static WebSocketPeer *_create(bool p_notify_postinitialize) { return static_cast<WebSocketPeer *>(ClassDB::creator<WSLPeer>(p_notify_postinitialize)); }
// Callbacks.
static ssize_t _wsl_recv_callback(wslay_event_context_ptr ctx, uint8_t *data, size_t len, int flags, void *user_data);
static void _wsl_recv_start_callback(wslay_event_context_ptr ctx, const struct wslay_event_on_frame_recv_start_arg *arg, void *user_data);
static void _wsl_frame_recv_chunk_callback(wslay_event_context_ptr ctx, const struct wslay_event_on_frame_recv_chunk_arg *arg, void *user_data);
static ssize_t _wsl_send_callback(wslay_event_context_ptr ctx, const uint8_t *data, size_t len, int flags, void *user_data);
static int _wsl_genmask_callback(wslay_event_context_ptr ctx, uint8_t *buf, size_t len, void *user_data);
static void _wsl_msg_recv_callback(wslay_event_context_ptr ctx, const struct wslay_event_on_msg_recv_arg *arg, void *user_data);
@ -80,6 +81,16 @@ private:
Resolver() {}
};
struct PendingMessage {
size_t payload_size = 0;
uint8_t opcode = 0;
void clear() {
payload_size = 0;
opcode = 0;
}
};
Resolver resolver;
// WebSocket connection state.
@ -99,6 +110,9 @@ private:
int close_code = -1;
String close_reason;
uint8_t was_string = 0;
uint64_t last_heartbeat = 0;
bool heartbeat_waiting = false;
PendingMessage pending_message;
// WebSocket configuration.
bool use_tls = true;
@ -127,7 +141,7 @@ public:
virtual int get_available_packet_count() const override;
virtual Error get_packet(const uint8_t **r_buffer, int &r_buffer_size) override;
virtual Error put_packet(const uint8_t *p_buffer, int p_buffer_size) override;
virtual int get_max_packet_size() const override { return packet_buffer.size(); };
virtual int get_max_packet_size() const override { return packet_buffer.size(); }
// WebSocketPeer
virtual Error send(const uint8_t *p_buffer, int p_buffer_size, WriteMode p_mode) override;