diff options
Diffstat (limited to 'modules')
55 files changed, 514 insertions, 378 deletions
diff --git a/modules/astcenc/image_compress_astcenc.cpp b/modules/astcenc/image_compress_astcenc.cpp index ce10201343..1c643d780d 100644 --- a/modules/astcenc/image_compress_astcenc.cpp +++ b/modules/astcenc/image_compress_astcenc.cpp @@ -35,7 +35,7 @@ #include <astcenc.h> -void _compress_astc(Image *r_img, float p_lossy_quality, Image::ASTCFormat p_format) { +void _compress_astc(Image *r_img, Image::ASTCFormat p_format) { uint64_t start_time = OS::get_singleton()->get_ticks_msec(); // TODO: See how to handle lossy quality. @@ -83,65 +83,91 @@ void _compress_astc(Image *r_img, float p_lossy_quality, Image::ASTCFormat p_for const bool mipmaps = r_img->has_mipmaps(); int width = r_img->get_width(); int height = r_img->get_height(); + int required_width = (width % block_x) != 0 ? width + (block_x - (width % block_x)) : width; + int required_height = (height % block_y) != 0 ? height + (block_y - (height % block_y)) : height; + + if (width != required_width || height != required_height) { + // Resize texture to fit block size. + r_img->resize(required_width, required_height); + width = required_width; + height = required_height; + } print_verbose(vformat("astcenc: Encoding image size %dx%d to format %s%s.", width, height, Image::get_format_name(target_format), mipmaps ? ", with mipmaps" : "")); // Initialize astcenc. + int dest_size = Image::get_image_data_size(width, height, target_format, mipmaps); + Vector<uint8_t> dest_data; + dest_data.resize(dest_size); + uint8_t *dest_write = dest_data.ptrw(); + astcenc_config config; config.block_x = block_x; config.block_y = block_y; config.profile = profile; - const float quality = ASTCENC_PRE_MEDIUM; - astcenc_error status = astcenc_config_init(profile, block_x, block_y, block_x, quality, 0, &config); + const float quality = ASTCENC_PRE_MEDIUM; + astcenc_error status = astcenc_config_init(profile, block_x, block_y, 1, quality, 0, &config); ERR_FAIL_COND_MSG(status != ASTCENC_SUCCESS, vformat("astcenc: Configuration initialization failed: %s.", astcenc_get_error_string(status))); // Context allocation. astcenc_context *context; - const unsigned int thread_count = OS::get_singleton()->get_processor_count(); - + const unsigned int thread_count = 1; // Godot compresses multiple images each on a thread, which is more efficient for large amount of images imported. status = astcenc_context_alloc(&config, thread_count, &context); ERR_FAIL_COND_MSG(status != ASTCENC_SUCCESS, vformat("astcenc: Context allocation failed: %s.", astcenc_get_error_string(status))); - // Compress image. - Vector<uint8_t> image_data = r_img->get_data(); - uint8_t *slices = image_data.ptrw(); - astcenc_image image; - image.dim_x = width; - image.dim_y = height; - image.dim_z = 1; - image.data_type = ASTCENC_TYPE_U8; - if (is_hdr) { - image.data_type = ASTCENC_TYPE_F32; - } - image.data = reinterpret_cast<void **>(&slices); + int mip_count = mipmaps ? Image::get_image_required_mipmaps(width, height, target_format) : 0; + for (int i = 0; i < mip_count + 1; i++) { + int src_mip_w, src_mip_h; + int src_ofs = Image::get_image_mipmap_offset_and_dimensions(width, height, r_img->get_format(), i, src_mip_w, src_mip_h); - // Compute the number of ASTC blocks in each dimension. - unsigned int block_count_x = (width + block_x - 1) / block_x; - unsigned int block_count_y = (height + block_y - 1) / block_y; - size_t comp_len = block_count_x * block_count_y * 16; + const uint8_t *slices = &image_data.ptr()[src_ofs]; - Vector<uint8_t> compressed_data; - compressed_data.resize(comp_len); - compressed_data.fill(0); + int dst_mip_w, dst_mip_h; + int dst_ofs = Image::get_image_mipmap_offset_and_dimensions(width, height, target_format, i, dst_mip_w, dst_mip_h); + // Ensure that mip offset is a multiple of 8 (etcpak expects uint64_t pointer). + ERR_FAIL_COND(dst_ofs % 8 != 0); + uint8_t *dest_mip_write = (uint8_t *)&dest_write[dst_ofs]; - const astcenc_swizzle swizzle = { - ASTCENC_SWZ_R, ASTCENC_SWZ_G, ASTCENC_SWZ_B, ASTCENC_SWZ_A - }; + // Compress image. - status = astcenc_compress_image(context, &image, &swizzle, compressed_data.ptrw(), comp_len, 0); - ERR_FAIL_COND_MSG(status != ASTCENC_SUCCESS, - vformat("astcenc: ASTC image compression failed: %s.", astcenc_get_error_string(status))); + astcenc_image image; + image.dim_x = src_mip_w; + image.dim_y = src_mip_h; + image.dim_z = 1; + image.data_type = ASTCENC_TYPE_U8; + if (is_hdr) { + image.data_type = ASTCENC_TYPE_F32; + } + image.data = (void **)(&slices); + + // Compute the number of ASTC blocks in each dimension. + unsigned int block_count_x = (src_mip_w + block_x - 1) / block_x; + unsigned int block_count_y = (src_mip_h + block_y - 1) / block_y; + size_t comp_len = block_count_x * block_count_y * 16; + + const astcenc_swizzle swizzle = { + ASTCENC_SWZ_R, ASTCENC_SWZ_G, ASTCENC_SWZ_B, ASTCENC_SWZ_A + }; + + status = astcenc_compress_image(context, &image, &swizzle, dest_mip_write, comp_len, 0); + + ERR_BREAK_MSG(status != ASTCENC_SUCCESS, + vformat("astcenc: ASTC image compression failed: %s.", astcenc_get_error_string(status))); + astcenc_compress_reset(context); + } + + astcenc_context_free(context); // Replace original image with compressed one. - r_img->set_data(width, height, mipmaps, target_format, compressed_data); + r_img->set_data(width, height, mipmaps, target_format, dest_data); print_verbose(vformat("astcenc: Encoding took %s ms.", rtos(OS::get_singleton()->get_ticks_msec() - start_time))); } @@ -184,68 +210,81 @@ void _decompress_astc(Image *r_img) { astcenc_config config; const float quality = ASTCENC_PRE_MEDIUM; - astcenc_error status = astcenc_config_init(profile, block_x, block_y, block_x, quality, 0, &config); + astcenc_error status = astcenc_config_init(profile, block_x, block_y, 1, quality, 0, &config); ERR_FAIL_COND_MSG(status != ASTCENC_SUCCESS, vformat("astcenc: Configuration initialization failed: %s.", astcenc_get_error_string(status))); // Context allocation. astcenc_context *context = nullptr; - const unsigned int thread_count = OS::get_singleton()->get_processor_count(); + const unsigned int thread_count = 1; status = astcenc_context_alloc(&config, thread_count, &context); ERR_FAIL_COND_MSG(status != ASTCENC_SUCCESS, vformat("astcenc: Context allocation failed: %s.", astcenc_get_error_string(status))); - // Decompress image. + Image::Format target_format = is_hdr ? Image::FORMAT_RGBAF : Image::FORMAT_RGBA8; const bool mipmaps = r_img->has_mipmaps(); int width = r_img->get_width(); int height = r_img->get_height(); + int dest_size = Image::get_image_data_size(width, height, target_format, mipmaps); + Vector<uint8_t> dest_data; + dest_data.resize(dest_size); + uint8_t *dest_write = dest_data.ptrw(); - astcenc_image image; - image.dim_x = width; - image.dim_y = height; - image.dim_z = 1; - image.data_type = ASTCENC_TYPE_U8; - Image::Format target_format = Image::FORMAT_RGBA8; - if (is_hdr) { - target_format = Image::FORMAT_RGBAF; - image.data_type = ASTCENC_TYPE_F32; - } + // Decompress image. Vector<uint8_t> image_data = r_img->get_data(); + int mip_count = mipmaps ? Image::get_image_required_mipmaps(width, height, target_format) : 0; - Vector<uint8_t> new_image_data; - new_image_data.resize(Image::get_image_data_size(width, height, target_format, false)); - new_image_data.fill(0); - uint8_t *slices = new_image_data.ptrw(); - image.data = reinterpret_cast<void **>(&slices); + for (int i = 0; i < mip_count + 1; i++) { + int src_mip_w, src_mip_h; + + int src_ofs = Image::get_image_mipmap_offset_and_dimensions(width, height, r_img->get_format(), i, src_mip_w, src_mip_h); + const uint8_t *src_data = &image_data.ptr()[src_ofs]; + int src_size; + if (i == mip_count) { + src_size = image_data.size() - src_ofs; + } else { + int auxw, auxh; + src_size = Image::get_image_mipmap_offset_and_dimensions(width, height, r_img->get_format(), i + 1, auxw, auxh) - src_ofs; + } - const astcenc_swizzle swizzle = { - ASTCENC_SWZ_R, ASTCENC_SWZ_G, ASTCENC_SWZ_B, ASTCENC_SWZ_A - }; + int dst_mip_w, dst_mip_h; + int dst_ofs = Image::get_image_mipmap_offset_and_dimensions(width, height, target_format, i, dst_mip_w, dst_mip_h); + // Ensure that mip offset is a multiple of 8 (etcpak expects uint64_t pointer). + ERR_FAIL_COND(dst_ofs % 8 != 0); + uint8_t *dest_mip_write = (uint8_t *)&dest_write[dst_ofs]; + + astcenc_image image; + image.dim_x = dst_mip_w; + image.dim_y = dst_mip_h; + image.dim_z = 1; + image.data_type = ASTCENC_TYPE_U8; + if (is_hdr) { + target_format = Image::FORMAT_RGBAF; + image.data_type = ASTCENC_TYPE_F32; + } - status = astcenc_decompress_image(context, image_data.ptr(), image_data.size(), &image, &swizzle, 0); - ERR_FAIL_COND_MSG(status != ASTCENC_SUCCESS, - vformat("astcenc: ASTC decompression failed: %s.", astcenc_get_error_string(status))); - ERR_FAIL_COND_MSG(image.dim_z > 1, - "astcenc: ASTC decompression failed because this is a 3D texture, which is not supported."); + image.data = (void **)(&dest_mip_write); - // Replace original image with compressed one. + const astcenc_swizzle swizzle = { + ASTCENC_SWZ_R, ASTCENC_SWZ_G, ASTCENC_SWZ_B, ASTCENC_SWZ_A + }; - Image::Format image_format = Image::FORMAT_RGBA8; - if (image.data_type == ASTCENC_TYPE_F32) { - image_format = Image::FORMAT_RGBAF; - } else if (image.data_type == ASTCENC_TYPE_U8) { - image_format = Image::FORMAT_RGBA8; - } else if (image.data_type == ASTCENC_TYPE_F16) { - image_format = Image::FORMAT_RGBAH; - } else { - ERR_FAIL_MSG("astcenc: ASTC decompression failed with an unknown format."); + status = astcenc_decompress_image(context, src_data, src_size, &image, &swizzle, 0); + ERR_BREAK_MSG(status != ASTCENC_SUCCESS, + vformat("astcenc: ASTC decompression failed: %s.", astcenc_get_error_string(status))); + ERR_BREAK_MSG(image.dim_z > 1, + "astcenc: ASTC decompression failed because this is a 3D texture, which is not supported."); + astcenc_compress_reset(context); } + astcenc_context_free(context); + + // Replace original image with compressed one. - r_img->set_data(image.dim_x, image.dim_y, mipmaps, image_format, new_image_data); + r_img->set_data(width, height, mipmaps, target_format, dest_data); print_verbose(vformat("astcenc: Decompression took %s ms.", rtos(OS::get_singleton()->get_ticks_msec() - start_time))); } diff --git a/modules/astcenc/image_compress_astcenc.h b/modules/astcenc/image_compress_astcenc.h index a197a91e0d..ad157d7c0a 100644 --- a/modules/astcenc/image_compress_astcenc.h +++ b/modules/astcenc/image_compress_astcenc.h @@ -33,7 +33,7 @@ #include "core/io/image.h" -void _compress_astc(Image *r_img, float p_lossy_quality, Image::ASTCFormat p_format); +void _compress_astc(Image *r_img, Image::ASTCFormat p_format); void _decompress_astc(Image *r_img); #endif // IMAGE_COMPRESS_ASTCENC_H diff --git a/modules/cvtt/image_compress_cvtt.cpp b/modules/cvtt/image_compress_cvtt.cpp index 4982b6b995..f19228cb18 100644 --- a/modules/cvtt/image_compress_cvtt.cpp +++ b/modules/cvtt/image_compress_cvtt.cpp @@ -129,7 +129,7 @@ static void _digest_row_task(const CVTTCompressionJobParams &p_job_params, const } } -void image_compress_cvtt(Image *p_image, float p_lossy_quality, Image::UsedChannels p_channels) { +void image_compress_cvtt(Image *p_image, Image::UsedChannels p_channels) { if (p_image->get_format() >= Image::FORMAT_BPTC_RGBA) { return; //do not compress, already compressed } diff --git a/modules/cvtt/image_compress_cvtt.h b/modules/cvtt/image_compress_cvtt.h index 5dc8b6f52c..ca88a9d4c9 100644 --- a/modules/cvtt/image_compress_cvtt.h +++ b/modules/cvtt/image_compress_cvtt.h @@ -33,7 +33,7 @@ #include "core/io/image.h" -void image_compress_cvtt(Image *p_image, float p_lossy_quality, Image::UsedChannels p_channels); +void image_compress_cvtt(Image *p_image, Image::UsedChannels p_channels); void image_decompress_cvtt(Image *p_image); #endif // IMAGE_COMPRESS_CVTT_H diff --git a/modules/enet/doc_classes/ENetConnection.xml b/modules/enet/doc_classes/ENetConnection.xml index 8c84fe87d7..dc832976d9 100644 --- a/modules/enet/doc_classes/ENetConnection.xml +++ b/modules/enet/doc_classes/ENetConnection.xml @@ -84,19 +84,17 @@ </method> <method name="dtls_client_setup"> <return type="int" enum="Error" /> - <param index="0" name="certificate" type="X509Certificate" /> - <param index="1" name="hostname" type="String" /> - <param index="2" name="verify" type="bool" default="true" /> + <param index="0" name="hostname" type="String" /> + <param index="1" name="client_options" type="TLSOptions" default="null" /> <description> - Configure this ENetHost to use the custom Godot extension allowing DTLS encryption for ENet clients. Call this before [method connect_to_host] to have ENet connect using DTLS with [code]certificate[/code] and [code]hostname[/code] verification. Verification can be optionally turned off via the [code]verify[/code] parameter. + Configure this ENetHost to use the custom Godot extension allowing DTLS encryption for ENet clients. Call this before [method connect_to_host] to have ENet connect using DTLS validating the server certificate against [code]hostname[/code]. You can pass the optional [param client_options] parameter to customize the trusted certification authorities, or disable the common name verification. See [method TLSOptions.client] and [method TLSOptions.client_unsafe]. </description> </method> <method name="dtls_server_setup"> <return type="int" enum="Error" /> - <param index="0" name="key" type="CryptoKey" /> - <param index="1" name="certificate" type="X509Certificate" /> + <param index="0" name="server_options" type="TLSOptions" /> <description> - Configure this ENetHost to use the custom Godot extension allowing DTLS encryption for ENet servers. Call this right after [method create_host_bound] to have ENet expect peers to connect using DTLS. + Configure this ENetHost to use the custom Godot extension allowing DTLS encryption for ENet servers. Call this right after [method create_host_bound] to have ENet expect peers to connect using DTLS. See [method TLSOptions.server]. </description> </method> <method name="flush"> diff --git a/modules/enet/enet_connection.cpp b/modules/enet/enet_connection.cpp index d16e7d7c4a..804263186f 100644 --- a/modules/enet/enet_connection.cpp +++ b/modules/enet/enet_connection.cpp @@ -273,10 +273,11 @@ TypedArray<ENetPacketPeer> ENetConnection::_get_peers() { return out; } -Error ENetConnection::dtls_server_setup(Ref<CryptoKey> p_key, Ref<X509Certificate> p_cert) { +Error ENetConnection::dtls_server_setup(const Ref<TLSOptions> &p_options) { #ifdef GODOT_ENET ERR_FAIL_COND_V_MSG(!host, ERR_UNCONFIGURED, "The ENetConnection instance isn't currently active."); - return enet_host_dtls_server_setup(host, p_key.ptr(), p_cert.ptr()) ? FAILED : OK; + ERR_FAIL_COND_V(p_options.is_null() || !p_options->is_server(), ERR_INVALID_PARAMETER); + return enet_host_dtls_server_setup(host, const_cast<TLSOptions *>(p_options.ptr())) ? FAILED : OK; #else ERR_FAIL_V_MSG(ERR_UNAVAILABLE, "ENet DTLS support not available in this build."); #endif @@ -291,10 +292,11 @@ void ENetConnection::refuse_new_connections(bool p_refuse) { #endif } -Error ENetConnection::dtls_client_setup(Ref<X509Certificate> p_cert, const String &p_hostname, bool p_verify) { +Error ENetConnection::dtls_client_setup(const String &p_hostname, const Ref<TLSOptions> &p_options) { #ifdef GODOT_ENET ERR_FAIL_COND_V_MSG(!host, ERR_UNCONFIGURED, "The ENetConnection instance isn't currently active."); - return enet_host_dtls_client_setup(host, p_cert.ptr(), p_verify, p_hostname.utf8().get_data()) ? FAILED : OK; + ERR_FAIL_COND_V(p_options.is_null() || p_options->is_server(), ERR_INVALID_PARAMETER); + return enet_host_dtls_client_setup(host, p_hostname.utf8().get_data(), const_cast<TLSOptions *>(p_options.ptr())) ? FAILED : OK; #else ERR_FAIL_V_MSG(ERR_UNAVAILABLE, "ENet DTLS support not available in this build."); #endif @@ -351,8 +353,8 @@ void ENetConnection::_bind_methods() { ClassDB::bind_method(D_METHOD("channel_limit", "limit"), &ENetConnection::channel_limit); ClassDB::bind_method(D_METHOD("broadcast", "channel", "packet", "flags"), &ENetConnection::_broadcast); ClassDB::bind_method(D_METHOD("compress", "mode"), &ENetConnection::compress); - ClassDB::bind_method(D_METHOD("dtls_server_setup", "key", "certificate"), &ENetConnection::dtls_server_setup); - ClassDB::bind_method(D_METHOD("dtls_client_setup", "certificate", "hostname", "verify"), &ENetConnection::dtls_client_setup, DEFVAL(true)); + ClassDB::bind_method(D_METHOD("dtls_server_setup", "server_options"), &ENetConnection::dtls_server_setup); + ClassDB::bind_method(D_METHOD("dtls_client_setup", "hostname", "client_options"), &ENetConnection::dtls_client_setup, DEFVAL(Ref<TLSOptions>())); ClassDB::bind_method(D_METHOD("refuse_new_connections", "refuse"), &ENetConnection::refuse_new_connections); ClassDB::bind_method(D_METHOD("pop_statistic", "statistic"), &ENetConnection::pop_statistic); ClassDB::bind_method(D_METHOD("get_max_channels"), &ENetConnection::get_max_channels); diff --git a/modules/enet/enet_connection.h b/modules/enet/enet_connection.h index 9e444911cc..481afc48bb 100644 --- a/modules/enet/enet_connection.h +++ b/modules/enet/enet_connection.h @@ -128,8 +128,8 @@ public: int get_local_port() const; // Godot additions - Error dtls_server_setup(Ref<CryptoKey> p_key, Ref<X509Certificate> p_cert); - Error dtls_client_setup(Ref<X509Certificate> p_cert, const String &p_hostname, bool p_verify = true); + Error dtls_server_setup(const Ref<TLSOptions> &p_options); + Error dtls_client_setup(const String &p_hostname, const Ref<TLSOptions> &p_options); void refuse_new_connections(bool p_refuse); ENetConnection() {} diff --git a/modules/enet/enet_multiplayer_peer.cpp b/modules/enet/enet_multiplayer_peer.cpp index 50ea0dd37a..93a20ab1f8 100644 --- a/modules/enet/enet_multiplayer_peer.cpp +++ b/modules/enet/enet_multiplayer_peer.cpp @@ -363,7 +363,7 @@ Error ENetMultiplayerPeer::put_packet(const uint8_t *p_buffer, int p_buffer_size #ifdef DEBUG_ENABLED if ((packet_flags & ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT) && p_buffer_size > ENET_HOST_DEFAULT_MTU) { - WARN_PRINT_ONCE(vformat("Sending %d bytes unrealiably which is above the MTU (%d), this will result in higher packet loss", p_buffer_size, ENET_HOST_DEFAULT_MTU)); + WARN_PRINT_ONCE(vformat("Sending %d bytes unreliably which is above the MTU (%d), this will result in higher packet loss", p_buffer_size, ENET_HOST_DEFAULT_MTU)); } #endif diff --git a/modules/etcpak/image_compress_etcpak.cpp b/modules/etcpak/image_compress_etcpak.cpp index a6aeec54cc..16a59d3880 100644 --- a/modules/etcpak/image_compress_etcpak.cpp +++ b/modules/etcpak/image_compress_etcpak.cpp @@ -74,25 +74,23 @@ EtcpakType _determine_dxt_type(Image::UsedChannels p_channels) { } } -void _compress_etc1(Image *r_img, float p_lossy_quality) { - _compress_etcpak(EtcpakType::ETCPAK_TYPE_ETC1, r_img, p_lossy_quality); +void _compress_etc1(Image *r_img) { + _compress_etcpak(EtcpakType::ETCPAK_TYPE_ETC1, r_img); } -void _compress_etc2(Image *r_img, float p_lossy_quality, Image::UsedChannels p_channels) { +void _compress_etc2(Image *r_img, Image::UsedChannels p_channels) { EtcpakType type = _determine_etc_type(p_channels); - _compress_etcpak(type, r_img, p_lossy_quality); + _compress_etcpak(type, r_img); } -void _compress_bc(Image *r_img, float p_lossy_quality, Image::UsedChannels p_channels) { +void _compress_bc(Image *r_img, Image::UsedChannels p_channels) { EtcpakType type = _determine_dxt_type(p_channels); - _compress_etcpak(type, r_img, p_lossy_quality); + _compress_etcpak(type, r_img); } -void _compress_etcpak(EtcpakType p_compresstype, Image *r_img, float p_lossy_quality) { +void _compress_etcpak(EtcpakType p_compresstype, Image *r_img) { uint64_t start_time = OS::get_singleton()->get_ticks_msec(); - // TODO: See how to handle lossy quality. - Image::Format img_format = r_img->get_format(); if (img_format >= Image::FORMAT_DXT1) { return; // Do not compress, already compressed. diff --git a/modules/etcpak/image_compress_etcpak.h b/modules/etcpak/image_compress_etcpak.h index 8cb17b1c8a..ff267631a6 100644 --- a/modules/etcpak/image_compress_etcpak.h +++ b/modules/etcpak/image_compress_etcpak.h @@ -43,10 +43,10 @@ enum class EtcpakType { ETCPAK_TYPE_DXT5_RA_AS_RG, }; -void _compress_etc1(Image *r_img, float p_lossy_quality); -void _compress_etc2(Image *r_img, float p_lossy_quality, Image::UsedChannels p_channels); -void _compress_bc(Image *r_img, float p_lossy_quality, Image::UsedChannels p_channels); +void _compress_etc1(Image *r_img); +void _compress_etc2(Image *r_img, Image::UsedChannels p_channels); +void _compress_bc(Image *r_img, Image::UsedChannels p_channels); -void _compress_etcpak(EtcpakType p_compresstype, Image *r_img, float p_lossy_quality); +void _compress_etcpak(EtcpakType p_compresstype, Image *r_img); #endif // IMAGE_COMPRESS_ETCPAK_H diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml index 5bed1b9da3..7f9d4ae253 100644 --- a/modules/gdscript/doc_classes/@GDScript.xml +++ b/modules/gdscript/doc_classes/@GDScript.xml @@ -316,12 +316,21 @@ <return type="void" /> <param index="0" name="names" type="String" /> <description> - Export a [String] or integer property as an enumerated list of options. If the property is an integer field, then the index of the value is stored, in the same order the values are provided. You can add specific identifiers for allowed values using a colon. + Export an [int] or [String] property as an enumerated list of options. If the property is an [int], then the index of the value is stored, in the same order the values are provided. You can add specific identifiers for allowed values using a colon. If the property is a [String], then the value is stored. See also [constant PROPERTY_HINT_ENUM]. [codeblock] - @export_enum("Rebecca", "Mary", "Leah") var character_name: String @export_enum("Warrior", "Magician", "Thief") var character_class: int @export_enum("Slow:30", "Average:60", "Very Fast:200") var character_speed: int + @export_enum("Rebecca", "Mary", "Leah") var character_name: String + [/codeblock] + If you want to set an initial value, you must specify it explicitly: + [codeblock] + @export_enum("Rebecca", "Mary", "Leah") var character_name: String = "Rebecca" + [/codeblock] + If you want to use named GDScript enums, then use [annotation @export] instead: + [codeblock] + enum CharacterName {REBECCA, MARY, LEAH} + @export var character_name: CharacterName [/codeblock] </description> </annotation> @@ -507,7 +516,7 @@ <param index="2" name="step" type="float" default="1.0" /> <param index="3" name="extra_hints" type="String" default="""" /> <description> - Export a numeric property as a range value. The range must be defined by [param min] and [param max], as well as an optional [param step] and a variety of extra hints. The [param step] defaults to [code]1[/code] for integer properties. For floating-point numbers this value depends on your [code]EditorSettings.interface/inspector/default_float_step[/code] setting. + Export an [int] or [float] property as a range value. The range must be defined by [param min] and [param max], as well as an optional [param step] and a variety of extra hints. The [param step] defaults to [code]1[/code] for integer properties. For floating-point numbers this value depends on your [code]EditorSettings.interface/inspector/default_float_step[/code] setting. If hints [code]"or_greater"[/code] and [code]"or_less"[/code] are provided, the editor widget will not cap the value at range boundaries. The [code]"exp"[/code] hint will make the edited values on range to change exponentially. The [code]"hide_slider"[/code] hint will hide the slider element of the editor widget. Hints also allow to indicate the units for the edited value. Using [code]"radians"[/code] you can specify that the actual value is in radians, but should be displayed in degrees in the Inspector dock. [code]"degrees"[/code] allows to add a degree sign as a unit suffix. Finally, a custom suffix can be provided using [code]"suffix:unit"[/code], where "unit" can be any string. See also [constant PROPERTY_HINT_RANGE]. diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 7ea0603d57..8dd65a700a 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -1049,7 +1049,7 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class, co resolve_class_body(base_class, p_class); } - // Do functions and properties now. + // Do functions, properties, and groups now. for (int i = 0; i < p_class->members.size(); i++) { GDScriptParser::ClassNode::Member member = p_class->members[i]; if (member.type == GDScriptParser::ClassNode::Member::FUNCTION) { @@ -1089,6 +1089,10 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class, co resolve_function_body(member.variable->setter); } } + } else if (member.type == GDScriptParser::ClassNode::Member::GROUP) { + // Apply annotation (`@export_{category,group,subgroup}`). + resolve_annotation(member.annotation); + member.annotation->apply(parser, nullptr); } } diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index c78dd1528f..6acdc9f212 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -196,7 +196,11 @@ static bool _is_exact_type(const PropertyInfo &p_par_type, const GDScriptDataTyp } } -static bool _have_exact_arguments(const MethodBind *p_method, const Vector<GDScriptCodeGenerator::Address> &p_arguments) { +static bool _can_use_ptrcall(const MethodBind *p_method, const Vector<GDScriptCodeGenerator::Address> &p_arguments) { + if (p_method->is_vararg()) { + // ptrcall won't work with vararg methods. + return false; + } if (p_method->get_argument_count() != p_arguments.size()) { // ptrcall won't work with default arguments. return false; @@ -563,7 +567,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code self.mode = GDScriptCodeGenerator::Address::SELF; MethodBind *method = ClassDB::get_method(codegen.script->native->get_name(), call->function_name); - if (_have_exact_arguments(method, arguments)) { + if (_can_use_ptrcall(method, arguments)) { // Exact arguments, use ptrcall. gen->write_call_ptrcall(result, self, method, arguments); } else { @@ -613,7 +617,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code } if (ClassDB::class_exists(class_name) && ClassDB::has_method(class_name, call->function_name)) { MethodBind *method = ClassDB::get_method(class_name, call->function_name); - if (_have_exact_arguments(method, arguments)) { + if (_can_use_ptrcall(method, arguments)) { // Exact arguments, use ptrcall. gen->write_call_ptrcall(result, base, method, arguments); } else { diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 74d7f94d7c..66374d0a6d 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -122,7 +122,7 @@ GDScriptParser::GDScriptParser() { register_annotation(MethodInfo("@onready"), AnnotationInfo::VARIABLE, &GDScriptParser::onready_annotation); // Export annotations. register_annotation(MethodInfo("@export"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_NONE, Variant::NIL>); - register_annotation(MethodInfo("@export_enum", PropertyInfo(Variant::STRING, "names")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_ENUM, Variant::INT>, varray(), true); + register_annotation(MethodInfo("@export_enum", PropertyInfo(Variant::STRING, "names")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_ENUM, Variant::NIL>, varray(), true); register_annotation(MethodInfo("@export_file", PropertyInfo(Variant::STRING, "filter")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_FILE, Variant::STRING>, varray(""), true); register_annotation(MethodInfo("@export_dir"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_DIR, Variant::STRING>); register_annotation(MethodInfo("@export_global_file", PropertyInfo(Variant::STRING, "filter")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_GLOBAL_FILE, Variant::STRING>, varray(""), true); @@ -840,14 +840,19 @@ void GDScriptParser::parse_class_body(bool p_is_multiline) { case GDScriptTokenizer::Token::ANNOTATION: { advance(); - // Check for class-level annotations. + // Check for standalone and class-level annotations. AnnotationNode *annotation = parse_annotation(AnnotationInfo::STANDALONE | AnnotationInfo::CLASS_LEVEL); if (annotation != nullptr) { if (annotation->applies_to(AnnotationInfo::STANDALONE)) { if (previous.type != GDScriptTokenizer::Token::NEWLINE) { push_error(R"(Expected newline after a standalone annotation.)"); } - head->annotations.push_back(annotation); + if (annotation->name == "@export_category" || annotation->name == "@export_group" || annotation->name == "@export_subgroup") { + current_class->add_member_group(annotation); + } else { + // For potential non-group standalone annotations. + push_error(R"(Unexpected standalone annotation in class body.)"); + } } else { annotation_stack.push_back(annotation); } @@ -1901,10 +1906,8 @@ GDScriptParser::MatchNode *GDScriptParser::parse_match() { return match; } -#ifdef DEBUG_ENABLED bool all_have_return = true; bool have_wildcard = false; -#endif while (!check(GDScriptTokenizer::Token::DEDENT) && !is_at_end()) { MatchBranchNode *branch = parse_match_branch(); @@ -1917,21 +1920,19 @@ GDScriptParser::MatchNode *GDScriptParser::parse_match() { if (have_wildcard && !branch->patterns.is_empty()) { push_warning(branch->patterns[0], GDScriptWarning::UNREACHABLE_PATTERN); } +#endif have_wildcard = have_wildcard || branch->has_wildcard; all_have_return = all_have_return && branch->block->has_return; -#endif match->branches.push_back(branch); } complete_extents(match); consume(GDScriptTokenizer::Token::DEDENT, R"(Expected an indented block after "match" statement.)"); -#ifdef DEBUG_ENABLED if (all_have_return && have_wildcard) { current_suite->has_return = true; } -#endif return match; } @@ -3657,6 +3658,10 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node String hint_string; for (int i = 0; i < p_annotation->resolved_arguments.size(); i++) { + if (p_annotation->name != SNAME("@export_placeholder") && String(p_annotation->resolved_arguments[i]).contains(",")) { + push_error(vformat(R"(Argument %d of annotation "%s" contains a comma. Use separate arguments instead.)", i + 1, p_annotation->name), p_annotation->arguments[i]); + return false; + } if (i > 0) { hint_string += ","; } @@ -3801,6 +3806,24 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node variable->export_info.hint_string = hint_prefix + ":" + variable->export_info.hint_string; variable->export_info.type = Variant::ARRAY; } + } else if (p_annotation->name == SNAME("@export_enum")) { + Variant::Type enum_type = Variant::INT; + + if (export_type.kind == DataType::BUILTIN && export_type.builtin_type == Variant::STRING) { + enum_type = Variant::STRING; + } else if (export_type.is_variant() && variable->initializer != nullptr) { + DataType initializer_type = variable->initializer->get_datatype(); + if (initializer_type.kind == DataType::BUILTIN && initializer_type.builtin_type == Variant::STRING) { + enum_type = Variant::STRING; + } + } + + variable->export_info.type = enum_type; + + if (!export_type.is_variant() && (export_type.kind != DataType::BUILTIN || export_type.builtin_type != enum_type)) { + push_error(vformat(R"("@export_enum" annotation requires a variable of type "int" or "String" but type "%s" was given instead.)", export_type.to_string()), variable); + return false; + } } else { // Validate variable type with export. if (!export_type.is_variant() && (export_type.kind != DataType::BUILTIN || export_type.builtin_type != t_type)) { @@ -3841,7 +3864,6 @@ bool GDScriptParser::export_group_annotations(const AnnotationNode *p_annotation } break; } - current_class->add_member_group(annotation); return true; } diff --git a/modules/gdscript/tests/gdscript_test_runner.cpp b/modules/gdscript/tests/gdscript_test_runner.cpp index d2c8b5c317..5b8af0ff34 100644 --- a/modules/gdscript/tests/gdscript_test_runner.cpp +++ b/modules/gdscript/tests/gdscript_test_runner.cpp @@ -132,9 +132,10 @@ void finish_language() { StringName GDScriptTestRunner::test_function_name; -GDScriptTestRunner::GDScriptTestRunner(const String &p_source_dir, bool p_init_language) { +GDScriptTestRunner::GDScriptTestRunner(const String &p_source_dir, bool p_init_language, bool p_print_filenames) { test_function_name = StaticCString::create("test"); do_init_languages = p_init_language; + print_filenames = p_print_filenames; source_dir = p_source_dir; if (!source_dir.ends_with("/")) { @@ -194,6 +195,9 @@ int GDScriptTestRunner::run_tests() { int failed = 0; for (int i = 0; i < tests.size(); i++) { GDScriptTest test = tests[i]; + if (print_filenames) { + print_line(test.get_source_relative_filepath()); + } GDScriptTest::TestResult result = test.run_test(); String expected = FileAccess::get_file_as_string(test.get_output_file()); @@ -225,8 +229,13 @@ bool GDScriptTestRunner::generate_outputs() { } for (int i = 0; i < tests.size(); i++) { - OS::get_singleton()->print("."); GDScriptTest test = tests[i]; + if (print_filenames) { + print_line(test.get_source_relative_filepath()); + } else { + OS::get_singleton()->print("."); + } + bool result = test.generate_output(); if (!result) { @@ -337,15 +346,10 @@ GDScriptTest::GDScriptTest(const String &p_source_path, const String &p_output_p void GDScriptTestRunner::handle_cmdline() { List<String> cmdline_args = OS::get_singleton()->get_cmdline_args(); - // TODO: this could likely be ported to use test commands: - // https://github.com/godotengine/godot/pull/41355 - // Currently requires to startup the whole engine, which is slow. - String test_cmd = "--gdscript-test"; - String gen_cmd = "--gdscript-generate-tests"; for (List<String>::Element *E = cmdline_args.front(); E; E = E->next()) { String &cmd = E->get(); - if (cmd == test_cmd || cmd == gen_cmd) { + if (cmd == "--gdscript-generate-tests") { if (E->next() == nullptr) { ERR_PRINT("Needed a path for the test files."); exit(-1); @@ -353,14 +357,10 @@ void GDScriptTestRunner::handle_cmdline() { const String &path = E->next()->get(); - GDScriptTestRunner runner(path, false); - int failed = 0; - if (cmd == test_cmd) { - failed = runner.run_tests(); - } else { - bool completed = runner.generate_outputs(); - failed = completed ? 0 : -1; - } + GDScriptTestRunner runner(path, false, cmdline_args.find("--print-filenames") != nullptr); + + bool completed = runner.generate_outputs(); + int failed = completed ? 0 : -1; exit(failed); } } diff --git a/modules/gdscript/tests/gdscript_test_runner.h b/modules/gdscript/tests/gdscript_test_runner.h index b097f1b485..60b48c6a57 100644 --- a/modules/gdscript/tests/gdscript_test_runner.h +++ b/modules/gdscript/tests/gdscript_test_runner.h @@ -92,6 +92,7 @@ public: bool generate_output(); const String &get_source_file() const { return source_file; } + const String get_source_relative_filepath() const { return source_file.trim_prefix(base_dir); } const String &get_output_file() const { return output_file; } GDScriptTest(const String &p_source_path, const String &p_output_path, const String &p_base_dir); @@ -105,6 +106,7 @@ class GDScriptTestRunner { bool is_generating = false; bool do_init_languages = false; + bool print_filenames; // Whether filenames should be printed when generated/running tests bool make_tests(); bool make_tests_for_dir(const String &p_dir); @@ -117,7 +119,7 @@ public: int run_tests(); bool generate_outputs(); - GDScriptTestRunner(const String &p_source_dir, bool p_init_language); + GDScriptTestRunner(const String &p_source_dir, bool p_init_language, bool p_print_filenames = false); ~GDScriptTestRunner(); }; diff --git a/modules/gdscript/tests/gdscript_test_runner_suite.h b/modules/gdscript/tests/gdscript_test_runner_suite.h index aed0ac2baf..e27b6218f1 100644 --- a/modules/gdscript/tests/gdscript_test_runner_suite.h +++ b/modules/gdscript/tests/gdscript_test_runner_suite.h @@ -41,7 +41,8 @@ TEST_SUITE("[Modules][GDScript]") { // Allow the tests to fail, but do not ignore errors during development. // Update the scripts and expected output as needed. TEST_CASE("Script compilation and runtime") { - GDScriptTestRunner runner("modules/gdscript/tests/scripts", true); + bool print_filenames = OS::get_singleton()->get_cmdline_args().find("--print-filenames") != nullptr; + GDScriptTestRunner runner("modules/gdscript/tests/scripts", true, print_filenames); int fail_count = runner.run_tests(); INFO("Make sure `*.out` files have expected results."); REQUIRE_MESSAGE(fail_count == 0, "All GDScript tests should pass."); diff --git a/modules/gdscript/tests/scripts/analyzer/features/vararg_call.gd b/modules/gdscript/tests/scripts/analyzer/features/vararg_call.gd new file mode 100644 index 0000000000..d444250f1e --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/vararg_call.gd @@ -0,0 +1,6 @@ +signal ok() + +@warning_ignore("return_value_discarded") +func test(): + ok.connect(func(): print('ok')) + emit_signal(&'ok') diff --git a/modules/gdscript/tests/scripts/analyzer/features/vararg_call.out b/modules/gdscript/tests/scripts/analyzer/features/vararg_call.out new file mode 100644 index 0000000000..1b47ed10dc --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/vararg_call.out @@ -0,0 +1,2 @@ +GDTEST_OK +ok diff --git a/modules/gdscript/tests/scripts/parser/features/export_enum.gd b/modules/gdscript/tests/scripts/parser/features/export_enum.gd new file mode 100644 index 0000000000..9b2c22dea1 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/export_enum.gd @@ -0,0 +1,15 @@ +@export_enum("Red", "Green", "Blue") var untyped + +@export_enum("Red", "Green", "Blue") var weak_int = 0 +@export_enum("Red", "Green", "Blue") var weak_string = "" + +@export_enum("Red", "Green", "Blue") var hard_int: int +@export_enum("Red", "Green", "Blue") var hard_string: String + +@export_enum("Red:10", "Green:20", "Blue:30") var with_values + +func test(): + for property in get_property_list(): + if property.name in ["untyped", "weak_int", "weak_string", "hard_int", + "hard_string", "with_values"]: + prints(property.name, property.type, property.hint_string) diff --git a/modules/gdscript/tests/scripts/parser/features/export_enum.out b/modules/gdscript/tests/scripts/parser/features/export_enum.out new file mode 100644 index 0000000000..330b7eaf01 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/export_enum.out @@ -0,0 +1,7 @@ +GDTEST_OK +untyped 2 Red,Green,Blue +weak_int 2 Red,Green,Blue +weak_string 4 Red,Green,Blue +hard_int 2 Red,Green,Blue +hard_string 4 Red,Green,Blue +with_values 2 Red:10,Green:20,Blue:30 diff --git a/modules/gltf/doc_classes/GLTFDocument.xml b/modules/gltf/doc_classes/GLTFDocument.xml index d7e8141eb1..d6657cad5d 100644 --- a/modules/gltf/doc_classes/GLTFDocument.xml +++ b/modules/gltf/doc_classes/GLTFDocument.xml @@ -51,6 +51,7 @@ <param index="0" name="state" type="GLTFState" /> <param index="1" name="bake_fps" type="float" default="30" /> <param index="2" name="trimming" type="bool" default="false" /> + <param index="3" name="remove_immutable_tracks" type="bool" default="true" /> <description> Takes a [GLTFState] object through the [param state] parameter and returns a Godot Engine scene node. </description> diff --git a/modules/gltf/editor/editor_scene_importer_blend.cpp b/modules/gltf/editor/editor_scene_importer_blend.cpp index 0075558dfc..5415c5818f 100644 --- a/modules/gltf/editor/editor_scene_importer_blend.cpp +++ b/modules/gltf/editor/editor_scene_importer_blend.cpp @@ -233,7 +233,7 @@ Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_ } return nullptr; } - return gltf->generate_scene(state, (float)p_options["animation/fps"], (bool)p_options["animation/trimming"]); + return gltf->generate_scene(state, (float)p_options["animation/fps"], (bool)p_options["animation/trimming"], (bool)p_options["animation/remove_immutable_tracks"]); } Variant EditorSceneFormatImporterBlend::get_option_visibility(const String &p_path, bool p_for_animation, const String &p_option, diff --git a/modules/gltf/editor/editor_scene_importer_fbx.cpp b/modules/gltf/editor/editor_scene_importer_fbx.cpp index 6c6ab7cd03..d829630032 100644 --- a/modules/gltf/editor/editor_scene_importer_fbx.cpp +++ b/modules/gltf/editor/editor_scene_importer_fbx.cpp @@ -100,7 +100,7 @@ Node *EditorSceneFormatImporterFBX::import_scene(const String &p_path, uint32_t } return nullptr; } - return gltf->generate_scene(state, (float)p_options["animation/fps"], (bool)p_options["animation/trimming"]); + return gltf->generate_scene(state, (float)p_options["animation/fps"], (bool)p_options["animation/trimming"], (bool)p_options["animation/remove_immutable_tracks"]); } Variant EditorSceneFormatImporterFBX::get_option_visibility(const String &p_path, bool p_for_animation, diff --git a/modules/gltf/editor/editor_scene_importer_gltf.cpp b/modules/gltf/editor/editor_scene_importer_gltf.cpp index 7c40afc8e7..67bbf8dd15 100644 --- a/modules/gltf/editor/editor_scene_importer_gltf.cpp +++ b/modules/gltf/editor/editor_scene_importer_gltf.cpp @@ -66,11 +66,23 @@ Node *EditorSceneFormatImporterGLTF::import_scene(const String &p_path, uint32_t state->set_create_animations(bool(p_options["animation/import"])); } +#ifndef DISABLE_DEPRECATED if (p_options.has("animation/trimming")) { - return doc->generate_scene(state, (float)p_options["animation/fps"], (bool)p_options["animation/trimming"]); + if (p_options.has("animation/remove_immutable_tracks")) { + return doc->generate_scene(state, (float)p_options["animation/fps"], (bool)p_options["animation/trimming"], (bool)p_options["animation/remove_immutable_tracks"]); + } else { + return doc->generate_scene(state, (float)p_options["animation/fps"], (bool)p_options["animation/trimming"], true); + } } else { - return doc->generate_scene(state, (float)p_options["animation/fps"], false); + if (p_options.has("animation/remove_immutable_tracks")) { + return doc->generate_scene(state, (float)p_options["animation/fps"], false, (bool)p_options["animation/remove_immutable_tracks"]); + } else { + return doc->generate_scene(state, (float)p_options["animation/fps"], false, true); + } } +#else + return doc->generate_scene(state, (float)p_options["animation/fps"], (bool)p_options["animation/trimming"], (bool)p_options["animation/remove_immutable_tracks"]); +#endif } void EditorSceneFormatImporterGLTF::get_import_options(const String &p_path, diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp index bc50e74cd3..0b519bd6a3 100644 --- a/modules/gltf/gltf_document.cpp +++ b/modules/gltf/gltf_document.cpp @@ -5961,7 +5961,7 @@ T GLTFDocument::_interpolate_track(const Vector<real_t> &p_times, const Vector<T ERR_FAIL_V(p_values[0]); } -void GLTFDocument::_import_animation(Ref<GLTFState> p_state, AnimationPlayer *p_animation_player, const GLTFAnimationIndex p_index, const float p_bake_fps, const bool p_trimming) { +void GLTFDocument::_import_animation(Ref<GLTFState> p_state, AnimationPlayer *p_animation_player, const GLTFAnimationIndex p_index, const float p_bake_fps, const bool p_trimming, const bool p_remove_immutable_tracks) { Ref<GLTFAnimation> anim = p_state->animations[p_index]; String anim_name = anim->get_name(); @@ -6064,35 +6064,38 @@ void GLTFDocument::_import_animation(Ref<GLTFState> p_state, AnimationPlayer *p_ int scale_idx = -1; if (track.position_track.values.size()) { - Vector3 base_pos = p_state->nodes[track_i.key]->position; - bool not_default = false; //discard the track if all it contains is default values - for (int i = 0; i < track.position_track.times.size(); i++) { - Vector3 value = track.position_track.values[track.position_track.interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE ? (1 + i * 3) : i]; - if (!value.is_equal_approx(base_pos)) { - not_default = true; - break; + bool is_default = true; //discard the track if all it contains is default values + if (p_remove_immutable_tracks) { + Vector3 base_pos = p_state->nodes[track_i.key]->position; + for (int i = 0; i < track.position_track.times.size(); i++) { + Vector3 value = track.position_track.values[track.position_track.interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE ? (1 + i * 3) : i]; + if (!value.is_equal_approx(base_pos)) { + is_default = false; + break; + } } } - if (not_default) { + if (!p_remove_immutable_tracks || !is_default) { position_idx = base_idx; animation->add_track(Animation::TYPE_POSITION_3D); animation->track_set_path(position_idx, transform_node_path); animation->track_set_imported(position_idx, true); //helps merging later - base_idx++; } } if (track.rotation_track.values.size()) { - Quaternion base_rot = p_state->nodes[track_i.key]->rotation.normalized(); - bool not_default = false; //discard the track if all it contains is default values - for (int i = 0; i < track.rotation_track.times.size(); i++) { - Quaternion value = track.rotation_track.values[track.rotation_track.interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE ? (1 + i * 3) : i].normalized(); - if (!value.is_equal_approx(base_rot)) { - not_default = true; - break; + bool is_default = true; //discard the track if all it contains is default values + if (p_remove_immutable_tracks) { + Quaternion base_rot = p_state->nodes[track_i.key]->rotation.normalized(); + for (int i = 0; i < track.rotation_track.times.size(); i++) { + Quaternion value = track.rotation_track.values[track.rotation_track.interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE ? (1 + i * 3) : i].normalized(); + if (!value.is_equal_approx(base_rot)) { + is_default = false; + break; + } } } - if (not_default) { + if (!p_remove_immutable_tracks || !is_default) { rotation_idx = base_idx; animation->add_track(Animation::TYPE_ROTATION_3D); animation->track_set_path(rotation_idx, transform_node_path); @@ -6101,16 +6104,18 @@ void GLTFDocument::_import_animation(Ref<GLTFState> p_state, AnimationPlayer *p_ } } if (track.scale_track.values.size()) { - Vector3 base_scale = p_state->nodes[track_i.key]->scale; - bool not_default = false; //discard the track if all it contains is default values - for (int i = 0; i < track.scale_track.times.size(); i++) { - Vector3 value = track.scale_track.values[track.scale_track.interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE ? (1 + i * 3) : i]; - if (!value.is_equal_approx(base_scale)) { - not_default = true; - break; + bool is_default = true; //discard the track if all it contains is default values + if (p_remove_immutable_tracks) { + Vector3 base_scale = p_state->nodes[track_i.key]->scale; + for (int i = 0; i < track.scale_track.times.size(); i++) { + Vector3 value = track.scale_track.values[track.scale_track.interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE ? (1 + i * 3) : i]; + if (!value.is_equal_approx(base_scale)) { + is_default = false; + break; + } } } - if (not_default) { + if (!p_remove_immutable_tracks || !is_default) { scale_idx = base_idx; animation->add_track(Animation::TYPE_SCALE_3D); animation->track_set_path(scale_idx, transform_node_path); @@ -6895,8 +6900,8 @@ void GLTFDocument::_bind_methods() { &GLTFDocument::append_from_buffer, DEFVAL(0)); ClassDB::bind_method(D_METHOD("append_from_scene", "node", "state", "flags"), &GLTFDocument::append_from_scene, DEFVAL(0)); - ClassDB::bind_method(D_METHOD("generate_scene", "state", "bake_fps", "trimming"), - &GLTFDocument::generate_scene, DEFVAL(30), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("generate_scene", "state", "bake_fps", "trimming", "remove_immutable_tracks"), + &GLTFDocument::generate_scene, DEFVAL(30), DEFVAL(false), DEFVAL(true)); ClassDB::bind_method(D_METHOD("generate_buffer", "state"), &GLTFDocument::generate_buffer); ClassDB::bind_method(D_METHOD("write_to_filesystem", "state", "path"), @@ -7005,7 +7010,7 @@ Error GLTFDocument::write_to_filesystem(Ref<GLTFState> p_state, const String &p_ return OK; } -Node *GLTFDocument::generate_scene(Ref<GLTFState> p_state, float p_bake_fps, bool p_trimming) { +Node *GLTFDocument::generate_scene(Ref<GLTFState> p_state, float p_bake_fps, bool p_trimming, bool p_remove_immutable_tracks) { ERR_FAIL_NULL_V(p_state, nullptr); ERR_FAIL_INDEX_V(0, p_state->root_nodes.size(), nullptr); Error err = OK; @@ -7019,7 +7024,7 @@ Node *GLTFDocument::generate_scene(Ref<GLTFState> p_state, float p_bake_fps, boo root->add_child(ap, true); ap->set_owner(root); for (int i = 0; i < p_state->animations.size(); i++) { - _import_animation(p_state, ap, i, p_bake_fps, p_trimming); + _import_animation(p_state, ap, i, p_bake_fps, p_trimming, p_remove_immutable_tracks); } } for (KeyValue<GLTFNodeIndex, Node *> E : p_state->scene_nodes) { diff --git a/modules/gltf/gltf_document.h b/modules/gltf/gltf_document.h index 73128c13aa..b8b989bf89 100644 --- a/modules/gltf/gltf_document.h +++ b/modules/gltf/gltf_document.h @@ -296,7 +296,7 @@ public: Error append_from_scene(Node *p_node, Ref<GLTFState> r_state, uint32_t p_flags = 0); public: - Node *generate_scene(Ref<GLTFState> p_state, float p_bake_fps = 30.0f, bool p_trimming = false); + Node *generate_scene(Ref<GLTFState> p_state, float p_bake_fps = 30.0f, bool p_trimming = false, bool p_remove_immutable_tracks = true); PackedByteArray generate_buffer(Ref<GLTFState> p_state); Error write_to_filesystem(Ref<GLTFState> p_state, const String &p_path); @@ -309,7 +309,7 @@ public: const GLTFNodeIndex p_node_index); void _generate_skeleton_bone_node(Ref<GLTFState> p_state, Node *p_scene_parent, Node3D *p_scene_root, const GLTFNodeIndex p_node_index); void _import_animation(Ref<GLTFState> p_state, AnimationPlayer *p_animation_player, - const GLTFAnimationIndex p_index, const float p_bake_fps, const bool p_trimming); + const GLTFAnimationIndex p_index, const float p_bake_fps, const bool p_trimming, const bool p_remove_immutable_tracks); void _convert_mesh_instances(Ref<GLTFState> p_state); GLTFCameraIndex _convert_camera(Ref<GLTFState> p_state, Camera3D *p_camera); void _convert_light_to_gltf(Light3D *p_light, Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node); diff --git a/modules/mbedtls/dtls_server_mbedtls.cpp b/modules/mbedtls/dtls_server_mbedtls.cpp index c54ab8ef6e..62513929ea 100644 --- a/modules/mbedtls/dtls_server_mbedtls.cpp +++ b/modules/mbedtls/dtls_server_mbedtls.cpp @@ -31,25 +31,25 @@ #include "dtls_server_mbedtls.h" #include "packet_peer_mbed_dtls.h" -Error DTLSServerMbedTLS::setup(Ref<CryptoKey> p_key, Ref<X509Certificate> p_cert, Ref<X509Certificate> p_ca_chain) { - ERR_FAIL_COND_V(_cookies->setup() != OK, ERR_ALREADY_IN_USE); - _key = p_key; - _cert = p_cert; - _ca_chain = p_ca_chain; +Error DTLSServerMbedTLS::setup(Ref<TLSOptions> p_options) { + ERR_FAIL_COND_V(p_options.is_null() || !p_options->is_server(), ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(cookies->setup() != OK, ERR_ALREADY_IN_USE); + tls_options = p_options; return OK; } void DTLSServerMbedTLS::stop() { - _cookies->clear(); + cookies->clear(); } Ref<PacketPeerDTLS> DTLSServerMbedTLS::take_connection(Ref<PacketPeerUDP> p_udp_peer) { Ref<PacketPeerMbedDTLS> out; - out.instantiate(); - ERR_FAIL_COND_V(!out.is_valid(), out); + ERR_FAIL_COND_V(tls_options.is_null(), out); ERR_FAIL_COND_V(!p_udp_peer.is_valid(), out); - out->accept_peer(p_udp_peer, _key, _cert, _ca_chain, _cookies); + + out.instantiate(); + out->accept_peer(p_udp_peer, tls_options, cookies); return out; } @@ -68,7 +68,7 @@ void DTLSServerMbedTLS::finalize() { } DTLSServerMbedTLS::DTLSServerMbedTLS() { - _cookies.instantiate(); + cookies.instantiate(); } DTLSServerMbedTLS::~DTLSServerMbedTLS() { diff --git a/modules/mbedtls/dtls_server_mbedtls.h b/modules/mbedtls/dtls_server_mbedtls.h index e4612d01ef..d5841a45fa 100644 --- a/modules/mbedtls/dtls_server_mbedtls.h +++ b/modules/mbedtls/dtls_server_mbedtls.h @@ -37,16 +37,14 @@ class DTLSServerMbedTLS : public DTLSServer { private: static DTLSServer *_create_func(); - Ref<CryptoKey> _key; - Ref<X509Certificate> _cert; - Ref<X509Certificate> _ca_chain; - Ref<CookieContextMbedTLS> _cookies; + Ref<TLSOptions> tls_options; + Ref<CookieContextMbedTLS> cookies; public: static void initialize(); static void finalize(); - virtual Error setup(Ref<CryptoKey> p_key, Ref<X509Certificate> p_cert, Ref<X509Certificate> p_ca_chain = Ref<X509Certificate>()); + virtual Error setup(Ref<TLSOptions> p_options); virtual void stop(); virtual Ref<PacketPeerDTLS> take_connection(Ref<PacketPeerUDP> p_peer); diff --git a/modules/mbedtls/packet_peer_mbed_dtls.cpp b/modules/mbedtls/packet_peer_mbed_dtls.cpp index 16450e151e..e8eb32f88d 100644 --- a/modules/mbedtls/packet_peer_mbed_dtls.cpp +++ b/modules/mbedtls/packet_peer_mbed_dtls.cpp @@ -114,16 +114,14 @@ Error PacketPeerMbedDTLS::_do_handshake() { return OK; } -Error PacketPeerMbedDTLS::connect_to_peer(Ref<PacketPeerUDP> p_base, bool p_validate_certs, const String &p_for_hostname, Ref<X509Certificate> p_ca_certs) { +Error PacketPeerMbedDTLS::connect_to_peer(Ref<PacketPeerUDP> p_base, const String &p_hostname, Ref<TLSOptions> p_options) { ERR_FAIL_COND_V(!p_base.is_valid() || !p_base->is_socket_connected(), ERR_INVALID_PARAMETER); - base = p_base; - int authmode = p_validate_certs ? MBEDTLS_SSL_VERIFY_REQUIRED : MBEDTLS_SSL_VERIFY_NONE; - - Error err = tls_ctx->init_client(MBEDTLS_SSL_TRANSPORT_DATAGRAM, authmode, p_ca_certs); + Error err = tls_ctx->init_client(MBEDTLS_SSL_TRANSPORT_DATAGRAM, p_hostname, p_options.is_valid() ? p_options : TLSOptions::client()); ERR_FAIL_COND_V(err != OK, err); - mbedtls_ssl_set_hostname(tls_ctx->get_context(), p_for_hostname.utf8().get_data()); + base = p_base; + mbedtls_ssl_set_bio(tls_ctx->get_context(), this, bio_send, bio_recv, nullptr); mbedtls_ssl_set_timer_cb(tls_ctx->get_context(), &timer, mbedtls_timing_set_delay, mbedtls_timing_get_delay); @@ -137,8 +135,10 @@ Error PacketPeerMbedDTLS::connect_to_peer(Ref<PacketPeerUDP> p_base, bool p_vali return OK; } -Error PacketPeerMbedDTLS::accept_peer(Ref<PacketPeerUDP> p_base, Ref<CryptoKey> p_key, Ref<X509Certificate> p_cert, Ref<X509Certificate> p_ca_chain, Ref<CookieContextMbedTLS> p_cookies) { - Error err = tls_ctx->init_server(MBEDTLS_SSL_TRANSPORT_DATAGRAM, MBEDTLS_SSL_VERIFY_NONE, p_key, p_cert, p_cookies); +Error PacketPeerMbedDTLS::accept_peer(Ref<PacketPeerUDP> p_base, Ref<TLSOptions> p_options, Ref<CookieContextMbedTLS> p_cookies) { + ERR_FAIL_COND_V(!p_base.is_valid() || !p_base->is_socket_connected(), ERR_INVALID_PARAMETER); + + Error err = tls_ctx->init_server(MBEDTLS_SSL_TRANSPORT_DATAGRAM, p_options, p_cookies); ERR_FAIL_COND_V(err != OK, err); base = p_base; diff --git a/modules/mbedtls/packet_peer_mbed_dtls.h b/modules/mbedtls/packet_peer_mbed_dtls.h index 744ef81524..05decec783 100644 --- a/modules/mbedtls/packet_peer_mbed_dtls.h +++ b/modules/mbedtls/packet_peer_mbed_dtls.h @@ -64,8 +64,8 @@ protected: public: virtual void poll(); - virtual Error accept_peer(Ref<PacketPeerUDP> p_base, Ref<CryptoKey> p_key, Ref<X509Certificate> p_cert = Ref<X509Certificate>(), Ref<X509Certificate> p_ca_chain = Ref<X509Certificate>(), Ref<CookieContextMbedTLS> p_cookies = Ref<CookieContextMbedTLS>()); - virtual Error connect_to_peer(Ref<PacketPeerUDP> p_base, bool p_validate_certs = true, const String &p_for_hostname = String(), Ref<X509Certificate> p_ca_certs = Ref<X509Certificate>()); + virtual Error accept_peer(Ref<PacketPeerUDP> p_base, Ref<TLSOptions> p_options, Ref<CookieContextMbedTLS> p_cookies = Ref<CookieContextMbedTLS>()); + virtual Error connect_to_peer(Ref<PacketPeerUDP> p_base, const String &p_hostname, Ref<TLSOptions> p_options = Ref<TLSOptions>()); virtual Status get_status() const; virtual void disconnect_from_peer(); diff --git a/modules/mbedtls/stream_peer_mbedtls.cpp b/modules/mbedtls/stream_peer_mbedtls.cpp index 1d17fb9441..a9d187bd64 100644 --- a/modules/mbedtls/stream_peer_mbedtls.cpp +++ b/modules/mbedtls/stream_peer_mbedtls.cpp @@ -80,38 +80,30 @@ void StreamPeerMbedTLS::_cleanup() { } Error StreamPeerMbedTLS::_do_handshake() { - int ret = 0; - while ((ret = mbedtls_ssl_handshake(tls_ctx->get_context())) != 0) { - if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) { - // An error occurred. - ERR_PRINT("TLS handshake error: " + itos(ret)); - TLSContextMbedTLS::print_mbedtls_error(ret); - disconnect_from_stream(); - status = STATUS_ERROR; - return FAILED; - } - - // Handshake is still in progress. - if (!blocking_handshake) { - // Will retry via poll later - return OK; - } + int ret = mbedtls_ssl_handshake(tls_ctx->get_context()); + if (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE) { + // Handshake is still in progress, will retry via poll later. + return OK; + } else if (ret != 0) { + // An error occurred. + ERR_PRINT("TLS handshake error: " + itos(ret)); + TLSContextMbedTLS::print_mbedtls_error(ret); + disconnect_from_stream(); + status = STATUS_ERROR; + return FAILED; } status = STATUS_CONNECTED; return OK; } -Error StreamPeerMbedTLS::connect_to_stream(Ref<StreamPeer> p_base, bool p_validate_certs, const String &p_for_hostname, Ref<X509Certificate> p_ca_certs) { +Error StreamPeerMbedTLS::connect_to_stream(Ref<StreamPeer> p_base, const String &p_common_name, Ref<TLSOptions> p_options) { ERR_FAIL_COND_V(p_base.is_null(), ERR_INVALID_PARAMETER); - base = p_base; - int authmode = p_validate_certs ? MBEDTLS_SSL_VERIFY_REQUIRED : MBEDTLS_SSL_VERIFY_NONE; - - Error err = tls_ctx->init_client(MBEDTLS_SSL_TRANSPORT_STREAM, authmode, p_ca_certs); + Error err = tls_ctx->init_client(MBEDTLS_SSL_TRANSPORT_STREAM, p_common_name, p_options.is_valid() ? p_options : TLSOptions::client()); ERR_FAIL_COND_V(err != OK, err); - mbedtls_ssl_set_hostname(tls_ctx->get_context(), p_for_hostname.utf8().get_data()); + base = p_base; mbedtls_ssl_set_bio(tls_ctx->get_context(), this, bio_send, bio_recv, nullptr); status = STATUS_HANDSHAKING; @@ -124,10 +116,11 @@ Error StreamPeerMbedTLS::connect_to_stream(Ref<StreamPeer> p_base, bool p_valida return OK; } -Error StreamPeerMbedTLS::accept_stream(Ref<StreamPeer> p_base, Ref<CryptoKey> p_key, Ref<X509Certificate> p_cert, Ref<X509Certificate> p_ca_chain) { +Error StreamPeerMbedTLS::accept_stream(Ref<StreamPeer> p_base, Ref<TLSOptions> p_options) { ERR_FAIL_COND_V(p_base.is_null(), ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_options.is_null() || !p_options->is_server(), ERR_INVALID_PARAMETER); - Error err = tls_ctx->init_server(MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_VERIFY_NONE, p_key, p_cert); + Error err = tls_ctx->init_server(MBEDTLS_SSL_TRANSPORT_STREAM, p_options); ERR_FAIL_COND_V(err != OK, err); base = p_base; @@ -308,10 +301,8 @@ StreamPeerTLS *StreamPeerMbedTLS::_create_func() { void StreamPeerMbedTLS::initialize_tls() { _create = _create_func; - available = true; } void StreamPeerMbedTLS::finalize_tls() { - available = false; _create = nullptr; } diff --git a/modules/mbedtls/stream_peer_mbedtls.h b/modules/mbedtls/stream_peer_mbedtls.h index 8a36a7ea9a..ec0446c380 100644 --- a/modules/mbedtls/stream_peer_mbedtls.h +++ b/modules/mbedtls/stream_peer_mbedtls.h @@ -54,8 +54,8 @@ protected: public: virtual void poll(); - virtual Error accept_stream(Ref<StreamPeer> p_base, Ref<CryptoKey> p_key, Ref<X509Certificate> p_cert, Ref<X509Certificate> p_ca_chain = Ref<X509Certificate>()); - virtual Error connect_to_stream(Ref<StreamPeer> p_base, bool p_validate_certs = false, const String &p_for_hostname = String(), Ref<X509Certificate> p_valid_cert = Ref<X509Certificate>()); + virtual Error accept_stream(Ref<StreamPeer> p_base, Ref<TLSOptions> p_options); + virtual Error connect_to_stream(Ref<StreamPeer> p_base, const String &p_common_name, Ref<TLSOptions> p_options); virtual Status get_status() const; virtual Ref<StreamPeer> get_stream() const; diff --git a/modules/mbedtls/tls_context_mbedtls.cpp b/modules/mbedtls/tls_context_mbedtls.cpp index a01137f262..aab082f488 100644 --- a/modules/mbedtls/tls_context_mbedtls.cpp +++ b/modules/mbedtls/tls_context_mbedtls.cpp @@ -110,22 +110,20 @@ Error TLSContextMbedTLS::_setup(int p_endpoint, int p_transport, int p_authmode) return OK; } -Error TLSContextMbedTLS::init_server(int p_transport, int p_authmode, Ref<CryptoKeyMbedTLS> p_pkey, Ref<X509CertificateMbedTLS> p_cert, Ref<CookieContextMbedTLS> p_cookies) { - ERR_FAIL_COND_V(!p_pkey.is_valid(), ERR_INVALID_PARAMETER); - ERR_FAIL_COND_V(!p_cert.is_valid(), ERR_INVALID_PARAMETER); +Error TLSContextMbedTLS::init_server(int p_transport, Ref<TLSOptions> p_options, Ref<CookieContextMbedTLS> p_cookies) { + ERR_FAIL_COND_V(p_options.is_null() || !p_options->is_server(), ERR_INVALID_PARAMETER); - Error err = _setup(MBEDTLS_SSL_IS_SERVER, p_transport, p_authmode); + // Check key and certificate(s) + pkey = p_options->get_private_key(); + certs = p_options->get_own_certificate(); + ERR_FAIL_COND_V(pkey.is_null() || certs.is_null(), ERR_INVALID_PARAMETER); + + Error err = _setup(MBEDTLS_SSL_IS_SERVER, p_transport, MBEDTLS_SSL_VERIFY_NONE); // TODO client auth. ERR_FAIL_COND_V(err != OK, err); // Locking key and certificate(s) - pkey = p_pkey; - certs = p_cert; - if (pkey.is_valid()) { - pkey->lock(); - } - if (certs.is_valid()) { - certs->lock(); - } + pkey->lock(); + certs->lock(); // Adding key and certificate int ret = mbedtls_ssl_conf_own_cert(&conf, &(certs->cert), &(pkey->pkey)); @@ -150,15 +148,32 @@ Error TLSContextMbedTLS::init_server(int p_transport, int p_authmode, Ref<Crypto return OK; } -Error TLSContextMbedTLS::init_client(int p_transport, int p_authmode, Ref<X509CertificateMbedTLS> p_valid_cas) { - Error err = _setup(MBEDTLS_SSL_IS_CLIENT, p_transport, p_authmode); +Error TLSContextMbedTLS::init_client(int p_transport, const String &p_hostname, Ref<TLSOptions> p_options) { + ERR_FAIL_COND_V(p_options.is_null() || p_options->is_server(), ERR_INVALID_PARAMETER); + + int authmode = MBEDTLS_SSL_VERIFY_REQUIRED; + if (p_options->get_verify_mode() == TLSOptions::TLS_VERIFY_NONE) { + authmode = MBEDTLS_SSL_VERIFY_NONE; + } + + Error err = _setup(MBEDTLS_SSL_IS_CLIENT, p_transport, authmode); ERR_FAIL_COND_V(err != OK, err); + if (p_options->get_verify_mode() == TLSOptions::TLS_VERIFY_FULL) { + String cn = p_options->get_common_name(); + if (cn.is_empty()) { + cn = p_hostname; + } + mbedtls_ssl_set_hostname(&tls, cn.utf8().get_data()); + } else { + mbedtls_ssl_set_hostname(&tls, nullptr); + } + X509CertificateMbedTLS *cas = nullptr; - if (p_valid_cas.is_valid()) { + if (p_options->get_trusted_ca_chain().is_valid()) { // Locking CA certificates - certs = p_valid_cas; + certs = p_options->get_trusted_ca_chain(); certs->lock(); cas = certs.ptr(); } else { diff --git a/modules/mbedtls/tls_context_mbedtls.h b/modules/mbedtls/tls_context_mbedtls.h index 574e80e199..f1bad6a40c 100644 --- a/modules/mbedtls/tls_context_mbedtls.h +++ b/modules/mbedtls/tls_context_mbedtls.h @@ -71,17 +71,17 @@ public: static void print_mbedtls_error(int p_ret); Ref<X509CertificateMbedTLS> certs; + Ref<CryptoKeyMbedTLS> pkey; + Ref<CookieContextMbedTLS> cookies; + mbedtls_entropy_context entropy; mbedtls_ctr_drbg_context ctr_drbg; mbedtls_ssl_context tls; mbedtls_ssl_config conf; - Ref<CookieContextMbedTLS> cookies; - Ref<CryptoKeyMbedTLS> pkey; - Error _setup(int p_endpoint, int p_transport, int p_authmode); - Error init_server(int p_transport, int p_authmode, Ref<CryptoKeyMbedTLS> p_pkey, Ref<X509CertificateMbedTLS> p_cert, Ref<CookieContextMbedTLS> p_cookies = Ref<CookieContextMbedTLS>()); - Error init_client(int p_transport, int p_authmode, Ref<X509CertificateMbedTLS> p_valid_cas); + Error init_server(int p_transport, Ref<TLSOptions> p_options, Ref<CookieContextMbedTLS> p_cookies = Ref<CookieContextMbedTLS>()); + Error init_client(int p_transport, const String &p_hostname, Ref<TLSOptions> p_options); void clear(); mbedtls_ssl_context *get_context(); diff --git a/modules/minimp3/doc_classes/AudioStreamMP3.xml b/modules/minimp3/doc_classes/AudioStreamMP3.xml index 8f03681c06..a88ff23b6b 100644 --- a/modules/minimp3/doc_classes/AudioStreamMP3.xml +++ b/modules/minimp3/doc_classes/AudioStreamMP3.xml @@ -21,21 +21,17 @@ [codeblocks] [gdscript] func load_mp3(path): - var file = File.new() - file.open(path, File.READ) + var file = FileAccess.open(path, FileAccess.READ) var sound = AudioStreamMP3.new() sound.data = file.get_buffer(file.get_length()) - file.close() return sound [/gdscript] [csharp] public AudioStreamMP3 LoadMP3(string path) { - var file = new File(); - file.Open(path, File.READ); + using var file = FileAccess.Open(path, FileAccess.ModeFlags.Read); var sound = new AudioStreamMP3(); sound.Data = file.GetBuffer(file.GetLength()); - file.Close(); return sound; } [/csharp] diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs index e939396926..5283dc7ec6 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs @@ -50,6 +50,18 @@ namespace Godot } /// <summary> + /// Returns the transform's skew (in radians). + /// </summary> + public readonly real_t Skew + { + get + { + real_t detSign = Mathf.Sign(BasisDeterminant()); + return Mathf.Acos(X.Normalized().Dot(detSign * Y.Normalized())) - Mathf.Pi * 0.5f; + } + } + + /// <summary> /// Access whole columns in the form of <see cref="Vector2"/>. /// The third column is the <see cref="Origin"/> vector. /// </summary> @@ -190,48 +202,13 @@ namespace Godot /// <returns>The interpolated transform.</returns> public readonly Transform2D InterpolateWith(Transform2D transform, real_t weight) { - real_t r1 = Rotation; - real_t r2 = transform.Rotation; - - Vector2 s1 = Scale; - Vector2 s2 = transform.Scale; - - // Slerp rotation - (real_t sin1, real_t cos1) = Mathf.SinCos(r1); - (real_t sin2, real_t cos2) = Mathf.SinCos(r2); - var v1 = new Vector2(cos1, sin1); - var v2 = new Vector2(cos2, sin2); - - real_t dot = v1.Dot(v2); - - dot = Mathf.Clamp(dot, -1.0f, 1.0f); - - Vector2 v; - - if (dot > 0.9995f) - { - // Linearly interpolate to avoid numerical precision issues - v = v1.Lerp(v2, weight).Normalized(); - } - else - { - real_t angle = weight * Mathf.Acos(dot); - Vector2 v3 = (v2 - (v1 * dot)).Normalized(); - (real_t sine, real_t cos) = Mathf.SinCos(angle); - v = (v1 * sine) + (v3 * cos); - } - - // Extract parameters - Vector2 p1 = Origin; - Vector2 p2 = transform.Origin; - - // Construct matrix - var res = new Transform2D(Mathf.Atan2(v.Y, v.X), p1.Lerp(p2, weight)); - Vector2 scale = s1.Lerp(s2, weight); - res.X *= scale; - res.Y *= scale; - - return res; + return new Transform2D + ( + Mathf.LerpAngle(Rotation, transform.Rotation, weight), + Scale.Lerp(transform.Scale, weight), + Mathf.LerpAngle(Skew, transform.Skew, weight), + Origin.Lerp(transform.Origin, weight) + ); } /// <summary> @@ -294,7 +271,7 @@ namespace Godot /// <returns>The rotated transformation matrix.</returns> public readonly Transform2D Rotated(real_t angle) { - return this * new Transform2D(angle, new Vector2()); + return new Transform2D(angle, new Vector2()) * this; } /// <summary> @@ -306,7 +283,7 @@ namespace Godot /// <returns>The rotated transformation matrix.</returns> public readonly Transform2D RotatedLocal(real_t angle) { - return new Transform2D(angle, new Vector2()) * this; + return this * new Transform2D(angle, new Vector2()); } /// <summary> diff --git a/modules/mono/utils/naming_utils.cpp b/modules/mono/utils/naming_utils.cpp index dc9bc3485a..62fbf815f8 100644 --- a/modules/mono/utils/naming_utils.cpp +++ b/modules/mono/utils/naming_utils.cpp @@ -109,7 +109,7 @@ Vector<String> _split_pascal_case(const String &p_identifier) { if (!is_digit(p_identifier[i])) { // These conditions only apply when the separator is not a digit. if (i - current_part_start == 1) { - // Upper character was only the beggining of a word. + // Upper character was only the beginning of a word. prev_was_upper = false; continue; } diff --git a/modules/noise/doc_classes/Noise.xml b/modules/noise/doc_classes/Noise.xml index 735ca388de..78d7d6a15b 100644 --- a/modules/noise/doc_classes/Noise.xml +++ b/modules/noise/doc_classes/Noise.xml @@ -17,8 +17,10 @@ <param index="1" name="height" type="int" /> <param index="2" name="invert" type="bool" default="false" /> <param index="3" name="in_3d_space" type="bool" default="false" /> + <param index="4" name="normalize" type="bool" default="true" /> <description> Returns a 2D [Image] noise image. + Note: With [param normalize] set to [code]false[/code] the default implementation expects the noise generator to return values in the range [code]-1.0[/code] to [code]1.0[/code]. </description> </method> <method name="get_noise_1d" qualifiers="const"> @@ -66,8 +68,10 @@ <param index="2" name="invert" type="bool" default="false" /> <param index="3" name="in_3d_space" type="bool" default="false" /> <param index="4" name="skirt" type="float" default="0.1" /> + <param index="5" name="normalize" type="bool" default="true" /> <description> Returns a seamless 2D [Image] noise image. + Note: With [param normalize] set to [code]false[/code] the default implementation expects the noise generator to return values in the range [code]-1.0[/code] to [code]1.0[/code]. </description> </method> </methods> diff --git a/modules/noise/doc_classes/NoiseTexture2D.xml b/modules/noise/doc_classes/NoiseTexture2D.xml index 0a800a143b..0f10a3f32f 100644 --- a/modules/noise/doc_classes/NoiseTexture2D.xml +++ b/modules/noise/doc_classes/NoiseTexture2D.xml @@ -44,6 +44,10 @@ <member name="noise" type="Noise" setter="set_noise" getter="get_noise"> The instance of the [Noise] object. </member> + <member name="normalize" type="bool" setter="set_normalize" getter="is_normalized" default="true"> + If [code]true[/code], the noise image coming from the noise generator is normalized to the range [code]0.0[/code] to [code]1.0[/code]. + Turning normalization off can affect the contrast and allows you to generate non repeating tileable noise textures. + </member> <member name="resource_local_to_scene" type="bool" setter="set_local_to_scene" getter="is_local_to_scene" overrides="Resource" default="false" /> <member name="seamless" type="bool" setter="set_seamless" getter="get_seamless" default="false"> If [code]true[/code], a seamless texture is requested from the [Noise] resource. diff --git a/modules/noise/noise.cpp b/modules/noise/noise.cpp index 5a901cb6e1..e95788b863 100644 --- a/modules/noise/noise.cpp +++ b/modules/noise/noise.cpp @@ -32,7 +32,7 @@ #include <float.h> -Ref<Image> Noise::get_seamless_image(int p_width, int p_height, bool p_invert, bool p_in_3d_space, real_t p_blend_skirt) const { +Ref<Image> Noise::get_seamless_image(int p_width, int p_height, bool p_invert, bool p_in_3d_space, real_t p_blend_skirt, bool p_normalize) const { ERR_FAIL_COND_V(p_width <= 0 || p_height <= 0, Ref<Image>()); int skirt_width = MAX(1, p_width * p_blend_skirt); @@ -40,7 +40,7 @@ Ref<Image> Noise::get_seamless_image(int p_width, int p_height, bool p_invert, b int src_width = p_width + skirt_width; int src_height = p_height + skirt_height; - Ref<Image> src = get_image(src_width, src_height, p_invert, p_in_3d_space); + Ref<Image> src = get_image(src_width, src_height, p_invert, p_in_3d_space, p_normalize); bool grayscale = (src->get_format() == Image::FORMAT_L8); if (grayscale) { return _generate_seamless_image<uint8_t>(src, p_width, p_height, p_invert, p_blend_skirt); @@ -58,7 +58,7 @@ uint8_t Noise::_alpha_blend<uint8_t>(uint8_t p_bg, uint8_t p_fg, int p_alpha) co return (uint8_t)((alpha * p_fg + inv_alpha * p_bg) >> 8); } -Ref<Image> Noise::get_image(int p_width, int p_height, bool p_invert, bool p_in_3d_space) const { +Ref<Image> Noise::get_image(int p_width, int p_height, bool p_invert, bool p_in_3d_space, bool p_normalize) const { ERR_FAIL_COND_V(p_width <= 0 || p_height <= 0, Ref<Image>()); Vector<uint8_t> data; @@ -66,38 +66,49 @@ Ref<Image> Noise::get_image(int p_width, int p_height, bool p_invert, bool p_in_ uint8_t *wd8 = data.ptrw(); - // Get all values and identify min/max values. - Vector<real_t> values; - values.resize(p_width * p_height); - real_t min_val = FLT_MAX; - real_t max_val = -FLT_MAX; - - for (int y = 0, i = 0; y < p_height; y++) { - for (int x = 0; x < p_width; x++, i++) { - values.set(i, p_in_3d_space ? get_noise_3d(x, y, 0.0) : get_noise_2d(x, y)); - if (values[i] > max_val) { - max_val = values[i]; - } - if (values[i] < min_val) { - min_val = values[i]; + if (p_normalize) { + // Get all values and identify min/max values. + Vector<real_t> values; + values.resize(p_width * p_height); + real_t min_val = FLT_MAX; + real_t max_val = -FLT_MAX; + for (int y = 0, i = 0; y < p_height; y++) { + for (int x = 0; x < p_width; x++, i++) { + values.set(i, p_in_3d_space ? get_noise_3d(x, y, 0.0) : get_noise_2d(x, y)); + if (values[i] > max_val) { + max_val = values[i]; + } + if (values[i] < min_val) { + min_val = values[i]; + } } } - } - - // Normalize values and write to texture. - uint8_t value; - for (int i = 0, x = 0; i < p_height; i++) { - for (int j = 0; j < p_width; j++, x++) { - if (max_val == min_val) { - value = 0; - } else { - value = uint8_t(CLAMP((values[x] - min_val) / (max_val - min_val) * 255.f, 0, 255)); + // Normalize values and write to texture. + uint8_t ivalue; + for (int i = 0, x = 0; i < p_height; i++) { + for (int j = 0; j < p_width; j++, x++) { + if (max_val == min_val) { + ivalue = 0; + } else { + ivalue = static_cast<uint8_t>(CLAMP((values[x] - min_val) / (max_val - min_val) * 255.f, 0, 255)); + } + + if (p_invert) { + ivalue = 255 - ivalue; + } + + wd8[x] = ivalue; } - if (p_invert) { - value = 255 - value; + } + } else { + // Without normalization, the expected range of the noise function is [-1, 1]. + uint8_t ivalue; + for (int y = 0, i = 0; y < p_height; y++) { + for (int x = 0; x < p_width; x++, i++) { + float value = (p_in_3d_space ? get_noise_3d(x, y, 0.0) : get_noise_2d(x, y)); + ivalue = static_cast<uint8_t>(CLAMP(value * 127.5f + 127.5f, 0.0f, 255.0f)); + wd8[i] = p_invert ? (255 - ivalue) : ivalue; } - - wd8[x] = value; } } @@ -113,6 +124,6 @@ void Noise::_bind_methods() { ClassDB::bind_method(D_METHOD("get_noise_3dv", "v"), &Noise::get_noise_3dv); // Textures. - ClassDB::bind_method(D_METHOD("get_image", "width", "height", "invert", "in_3d_space"), &Noise::get_image, DEFVAL(false), DEFVAL(false)); - ClassDB::bind_method(D_METHOD("get_seamless_image", "width", "height", "invert", "in_3d_space", "skirt"), &Noise::get_seamless_image, DEFVAL(false), DEFVAL(false), DEFVAL(0.1)); + ClassDB::bind_method(D_METHOD("get_image", "width", "height", "invert", "in_3d_space", "normalize"), &Noise::get_image, DEFVAL(false), DEFVAL(false), DEFVAL(true)); + ClassDB::bind_method(D_METHOD("get_seamless_image", "width", "height", "invert", "in_3d_space", "skirt", "normalize"), &Noise::get_seamless_image, DEFVAL(false), DEFVAL(false), DEFVAL(0.1), DEFVAL(true)); } diff --git a/modules/noise/noise.h b/modules/noise/noise.h index 8f8ecf29a5..f7e615c2aa 100644 --- a/modules/noise/noise.h +++ b/modules/noise/noise.h @@ -233,8 +233,8 @@ public: virtual real_t get_noise_3dv(Vector3 p_v) const = 0; virtual real_t get_noise_3d(real_t p_x, real_t p_y, real_t p_z) const = 0; - virtual Ref<Image> get_image(int p_width, int p_height, bool p_invert = false, bool p_in_3d_space = false) const; - virtual Ref<Image> get_seamless_image(int p_width, int p_height, bool p_invert = false, bool p_in_3d_space = false, real_t p_blend_skirt = 0.1) const; + virtual Ref<Image> get_image(int p_width, int p_height, bool p_invert = false, bool p_in_3d_space = false, bool p_normalize = true) const; + virtual Ref<Image> get_seamless_image(int p_width, int p_height, bool p_invert = false, bool p_in_3d_space = false, real_t p_blend_skirt = 0.1, bool p_normalize = true) const; }; #endif // NOISE_H diff --git a/modules/noise/noise_texture_2d.cpp b/modules/noise/noise_texture_2d.cpp index 0eedb286bd..0d5e778875 100644 --- a/modules/noise/noise_texture_2d.cpp +++ b/modules/noise/noise_texture_2d.cpp @@ -76,6 +76,9 @@ void NoiseTexture2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_bump_strength", "bump_strength"), &NoiseTexture2D::set_bump_strength); ClassDB::bind_method(D_METHOD("get_bump_strength"), &NoiseTexture2D::get_bump_strength); + ClassDB::bind_method(D_METHOD("set_normalize", "normalize"), &NoiseTexture2D::set_normalize); + ClassDB::bind_method(D_METHOD("is_normalized"), &NoiseTexture2D::is_normalized); + ClassDB::bind_method(D_METHOD("set_color_ramp", "gradient"), &NoiseTexture2D::set_color_ramp); ClassDB::bind_method(D_METHOD("get_color_ramp"), &NoiseTexture2D::get_color_ramp); @@ -91,6 +94,7 @@ void NoiseTexture2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "seamless_blend_skirt", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_seamless_blend_skirt", "get_seamless_blend_skirt"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "as_normal_map"), "set_as_normal_map", "is_normal_map"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "bump_strength", PROPERTY_HINT_RANGE, "0,32,0.1,or_greater"), "set_bump_strength", "get_bump_strength"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "normalize"), "set_normalize", "is_normalized"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "color_ramp", PROPERTY_HINT_RESOURCE_TYPE, "Gradient"), "set_color_ramp", "get_color_ramp"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "noise", PROPERTY_HINT_RESOURCE_TYPE, "Noise"), "set_noise", "get_noise"); } @@ -156,9 +160,9 @@ Ref<Image> NoiseTexture2D::_generate_texture() { Ref<Image> new_image; if (seamless) { - new_image = ref_noise->get_seamless_image(size.x, size.y, invert, in_3d_space, seamless_blend_skirt); + new_image = ref_noise->get_seamless_image(size.x, size.y, invert, in_3d_space, seamless_blend_skirt, normalize); } else { - new_image = ref_noise->get_image(size.x, size.y, invert, in_3d_space); + new_image = ref_noise->get_image(size.x, size.y, invert, in_3d_space, normalize); } if (color_ramp.is_valid()) { new_image = _modulate_with_gradient(new_image, color_ramp); @@ -349,6 +353,18 @@ void NoiseTexture2D::set_color_ramp(const Ref<Gradient> &p_gradient) { _queue_update(); } +void NoiseTexture2D::set_normalize(bool p_normalize) { + if (normalize == p_normalize) { + return; + } + normalize = p_normalize; + _queue_update(); +} + +bool NoiseTexture2D::is_normalized() const { + return normalize; +} + Ref<Gradient> NoiseTexture2D::get_color_ramp() const { return color_ramp; } diff --git a/modules/noise/noise_texture_2d.h b/modules/noise/noise_texture_2d.h index cda14df6c2..f53670b690 100644 --- a/modules/noise/noise_texture_2d.h +++ b/modules/noise/noise_texture_2d.h @@ -59,6 +59,7 @@ private: real_t seamless_blend_skirt = 0.1; bool as_normal_map = false; float bump_strength = 8.0; + bool normalize = true; Ref<Gradient> color_ramp; Ref<Noise> noise; @@ -105,6 +106,9 @@ public: void set_bump_strength(float p_bump_strength); float get_bump_strength(); + void set_normalize(bool p_normalize); + bool is_normalized() const; + void set_color_ramp(const Ref<Gradient> &p_gradient); Ref<Gradient> get_color_ramp() const; diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp index e53aef965a..9b474bf2ce 100644 --- a/modules/text_server_adv/text_server_adv.cpp +++ b/modules/text_server_adv/text_server_adv.cpp @@ -4068,7 +4068,6 @@ void TextServerAdvanced::_realign(ShapedTextDataAdvanced *p_sd) const { RID TextServerAdvanced::_shaped_text_substr(const RID &p_shaped, int64_t p_start, int64_t p_length) const { _THREAD_SAFE_METHOD_ - const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, RID()); @@ -5513,6 +5512,7 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int64_t p_star } bool TextServerAdvanced::_shaped_text_shape(const RID &p_shaped) { + _THREAD_SAFE_METHOD_ ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, false); @@ -6569,6 +6569,7 @@ TextServerAdvanced::TextServerAdvanced() { } void TextServerAdvanced::_cleanup() { + _THREAD_SAFE_METHOD_ for (const KeyValue<SystemFontKey, SystemFontCache> &E : system_fonts) { const Vector<SystemFontCacheRec> &sysf_cache = E.value.var; for (const SystemFontCacheRec &F : sysf_cache) { diff --git a/modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml b/modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml index 7e896a0ca3..aaeb2025ee 100644 --- a/modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml +++ b/modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml @@ -13,10 +13,9 @@ <method name="create_client"> <return type="int" enum="Error" /> <param index="0" name="url" type="String" /> - <param index="1" name="verify_tls" type="bool" default="true" /> - <param index="2" name="tls_certificate" type="X509Certificate" default="null" /> + <param index="1" name="tls_client_options" type="TLSOptions" default="null" /> <description> - Starts a new multiplayer client connecting to the given [param url]. If [param verify_tls] is [code]false[/code] certificate validation will be disabled. If specified, the [param tls_certificate] will be used to verify the TLS host. + Starts a new multiplayer client connecting to the given [param 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]: It is recommended to specify the scheme part of the URL, i.e. the [param url] should start with either [code]ws://[/code] or [code]wss://[/code]. </description> </method> @@ -24,10 +23,9 @@ <return type="int" enum="Error" /> <param index="0" name="port" type="int" /> <param index="1" name="bind_address" type="String" default=""*"" /> - <param index="2" name="tls_key" type="CryptoKey" default="null" /> - <param index="3" name="tls_certificate" type="X509Certificate" default="null" /> + <param index="2" name="tls_server_options" type="TLSOptions" default="null" /> <description> - Starts a new multiplayer server listening on the given [param port]. You can optionally specify a [param bind_address], and provide a [param tls_key] and [param tls_certificate] to use TLS. + Starts a new multiplayer server listening on the given [param port]. You can optionally specify a [param bind_address], and provide valiid [param tls_server_options] to use TLS. See [method TLSOptions.server]. </description> </method> <method name="get_peer" qualifiers="const"> diff --git a/modules/websocket/doc_classes/WebSocketPeer.xml b/modules/websocket/doc_classes/WebSocketPeer.xml index 41d166a0f5..0f8c27c4cc 100644 --- a/modules/websocket/doc_classes/WebSocketPeer.xml +++ b/modules/websocket/doc_classes/WebSocketPeer.xml @@ -58,10 +58,9 @@ <method name="connect_to_url"> <return type="int" enum="Error" /> <param index="0" name="url" type="String" /> - <param index="1" name="verify_tls" type="bool" default="true" /> - <param index="2" name="trusted_tls_certificate" type="X509Certificate" default="null" /> + <param index="1" name="tls_client_options" type="TLSOptions" default="null" /> <description> - Connects to the given URL. If [param verify_tls] is [code]false[/code] certificate validation will be disabled. If specified, the [param trusted_tls_certificate] will be the only one accepted when connecting to a TLS host. + 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] To avoid mixed content warnings or errors in Web, you may have to use a [code]url[/code] 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> diff --git a/modules/websocket/emws_peer.cpp b/modules/websocket/emws_peer.cpp index 1ec557427f..7b14a3a61d 100644 --- a/modules/websocket/emws_peer.cpp +++ b/modules/websocket/emws_peer.cpp @@ -58,7 +58,8 @@ void EMWSPeer::_esws_on_close(void *p_obj, int p_code, const char *p_reason, int peer->ready_state = STATE_CLOSED; } -Error EMWSPeer::connect_to_url(const String &p_url, bool p_verify_tls, Ref<X509Certificate> p_tls_certificate) { +Error EMWSPeer::connect_to_url(const String &p_url, Ref<TLSOptions> p_tls_options) { + ERR_FAIL_COND_V(p_tls_options.is_valid() && p_tls_options->is_server(), ERR_INVALID_PARAMETER); ERR_FAIL_COND_V(ready_state != STATE_CLOSED, ERR_ALREADY_IN_USE); _clear(); @@ -85,9 +86,6 @@ Error EMWSPeer::connect_to_url(const String &p_url, bool p_verify_tls, Ref<X509C if (handshake_headers.size()) { WARN_PRINT_ONCE("Custom headers are not supported in Web platform."); } - if (p_tls_certificate.is_valid()) { - WARN_PRINT_ONCE("Custom SSL certificates are not supported in Web platform."); - } requested_url = scheme + host; diff --git a/modules/websocket/emws_peer.h b/modules/websocket/emws_peer.h index 66c5283d5c..08b2dad977 100644 --- a/modules/websocket/emws_peer.h +++ b/modules/websocket/emws_peer.h @@ -86,7 +86,7 @@ public: // WebSocketPeer virtual Error send(const uint8_t *p_buffer, int p_buffer_size, WriteMode p_mode) override; - virtual Error connect_to_url(const String &p_url, bool p_verify_tls = true, Ref<X509Certificate> p_cert = Ref<X509Certificate>()) override; + virtual Error connect_to_url(const String &p_url, Ref<TLSOptions> p_tls_client_options) override; virtual Error accept_stream(Ref<StreamPeer> p_stream) override; virtual void close(int p_code = 1000, String p_reason = "") override; virtual void poll() override; diff --git a/modules/websocket/websocket_multiplayer_peer.cpp b/modules/websocket/websocket_multiplayer_peer.cpp index 36b4215f8c..389d8c56ad 100644 --- a/modules/websocket/websocket_multiplayer_peer.cpp +++ b/modules/websocket/websocket_multiplayer_peer.cpp @@ -54,11 +54,9 @@ void WebSocketMultiplayerPeer::_clear() { connection_status = CONNECTION_DISCONNECTED; unique_id = 0; peers_map.clear(); - use_tls = false; tcp_server.unref(); pending_peers.clear(); - tls_certificate.unref(); - tls_key.unref(); + tls_server_options.unref(); if (current_packet.data != nullptr) { memfree(current_packet.data); current_packet.data = nullptr; @@ -73,8 +71,8 @@ void WebSocketMultiplayerPeer::_clear() { } void WebSocketMultiplayerPeer::_bind_methods() { - ClassDB::bind_method(D_METHOD("create_client", "url", "verify_tls", "tls_certificate"), &WebSocketMultiplayerPeer::create_client, DEFVAL(true), DEFVAL(Ref<X509Certificate>())); - ClassDB::bind_method(D_METHOD("create_server", "port", "bind_address", "tls_key", "tls_certificate"), &WebSocketMultiplayerPeer::create_server, DEFVAL("*"), DEFVAL(Ref<CryptoKey>()), DEFVAL(Ref<X509Certificate>())); + ClassDB::bind_method(D_METHOD("create_client", "url", "tls_client_options"), &WebSocketMultiplayerPeer::create_client, DEFVAL(Ref<TLSOptions>())); + ClassDB::bind_method(D_METHOD("create_server", "port", "bind_address", "tls_server_options"), &WebSocketMultiplayerPeer::create_server, DEFVAL("*"), DEFVAL(Ref<TLSOptions>())); ClassDB::bind_method(D_METHOD("get_peer", "peer_id"), &WebSocketMultiplayerPeer::get_peer); ClassDB::bind_method(D_METHOD("get_peer_address", "id"), &WebSocketMultiplayerPeer::get_peer_address); @@ -179,8 +177,9 @@ int WebSocketMultiplayerPeer::get_max_packet_size() const { return get_outbound_buffer_size() - PROTO_SIZE; } -Error WebSocketMultiplayerPeer::create_server(int p_port, IPAddress p_bind_ip, Ref<CryptoKey> p_tls_key, Ref<X509Certificate> p_tls_certificate) { +Error WebSocketMultiplayerPeer::create_server(int p_port, IPAddress p_bind_ip, Ref<TLSOptions> p_options) { ERR_FAIL_COND_V(get_connection_status() != CONNECTION_DISCONNECTED, ERR_ALREADY_IN_USE); + ERR_FAIL_COND_V(p_options.is_valid() && !p_options->is_server(), ERR_INVALID_PARAMETER); _clear(); tcp_server.instantiate(); Error err = tcp_server->listen(p_port, p_bind_ip); @@ -190,20 +189,16 @@ Error WebSocketMultiplayerPeer::create_server(int p_port, IPAddress p_bind_ip, R } unique_id = 1; connection_status = CONNECTION_CONNECTED; - // TLS config - tls_key = p_tls_key; - tls_certificate = p_tls_certificate; - if (tls_key.is_valid() && tls_certificate.is_valid()) { - use_tls = true; - } + tls_server_options = p_options; return OK; } -Error WebSocketMultiplayerPeer::create_client(const String &p_url, bool p_verify_tls, Ref<X509Certificate> p_tls_certificate) { +Error WebSocketMultiplayerPeer::create_client(const String &p_url, Ref<TLSOptions> p_options) { ERR_FAIL_COND_V(get_connection_status() != CONNECTION_DISCONNECTED, ERR_ALREADY_IN_USE); + ERR_FAIL_COND_V(p_options.is_valid() && p_options->is_server(), ERR_INVALID_PARAMETER); _clear(); Ref<WebSocketPeer> peer = _create_peer(); - Error err = peer->connect_to_url(p_url, p_verify_tls, p_tls_certificate); + Error err = peer->connect_to_url(p_url, p_options); if (err != OK) { return err; } @@ -334,14 +329,14 @@ void WebSocketMultiplayerPeer::_poll_server() { to_remove.insert(id); // Error. continue; } - if (!use_tls) { + if (tls_server_options.is_null()) { peer.ws = _create_peer(); peer.ws->accept_stream(peer.tcp); continue; } else { if (peer.connection == peer.tcp) { Ref<StreamPeerTLS> tls = Ref<StreamPeerTLS>(StreamPeerTLS::create()); - Error err = tls->accept_stream(peer.tcp, tls_key, tls_certificate); + Error err = tls->accept_stream(peer.tcp, tls_server_options); if (err != OK) { to_remove.insert(id); continue; diff --git a/modules/websocket/websocket_multiplayer_peer.h b/modules/websocket/websocket_multiplayer_peer.h index ea10e8799f..22f1bc939b 100644 --- a/modules/websocket/websocket_multiplayer_peer.h +++ b/modules/websocket/websocket_multiplayer_peer.h @@ -71,9 +71,7 @@ protected: Ref<WebSocketPeer> peer_config; HashMap<int, PendingPeer> pending_peers; Ref<TCPServer> tcp_server; - bool use_tls = false; - Ref<X509Certificate> tls_certificate; - Ref<CryptoKey> tls_key; + Ref<TLSOptions> tls_server_options; ConnectionStatus connection_status = CONNECTION_DISCONNECTED; @@ -115,8 +113,8 @@ public: /* WebSocketPeer */ virtual Ref<WebSocketPeer> get_peer(int p_peer_id) const; - Error create_client(const String &p_url, bool p_verify_tls, Ref<X509Certificate> p_tls_certificate); - Error create_server(int p_port, IPAddress p_bind_ip, Ref<CryptoKey> p_tls_key, Ref<X509Certificate> p_tls_certificate); + Error create_client(const String &p_url, Ref<TLSOptions> p_options); + Error create_server(int p_port, IPAddress p_bind_ip, Ref<TLSOptions> p_options); void set_supported_protocols(const Vector<String> &p_protocols); Vector<String> get_supported_protocols() const; diff --git a/modules/websocket/websocket_peer.cpp b/modules/websocket/websocket_peer.cpp index d10315f64c..3c0d316bc9 100644 --- a/modules/websocket/websocket_peer.cpp +++ b/modules/websocket/websocket_peer.cpp @@ -39,7 +39,7 @@ WebSocketPeer::~WebSocketPeer() { } void WebSocketPeer::_bind_methods() { - ClassDB::bind_method(D_METHOD("connect_to_url", "url", "verify_tls", "trusted_tls_certificate"), &WebSocketPeer::connect_to_url, DEFVAL(true), DEFVAL(Ref<X509Certificate>())); + ClassDB::bind_method(D_METHOD("connect_to_url", "url", "tls_client_options"), &WebSocketPeer::connect_to_url, DEFVAL(Ref<TLSOptions>())); ClassDB::bind_method(D_METHOD("accept_stream", "stream"), &WebSocketPeer::accept_stream); ClassDB::bind_method(D_METHOD("send", "message", "write_mode"), &WebSocketPeer::_send_bind, DEFVAL(WRITE_MODE_BINARY)); ClassDB::bind_method(D_METHOD("send_text", "message"), &WebSocketPeer::send_text); diff --git a/modules/websocket/websocket_peer.h b/modules/websocket/websocket_peer.h index 3a1527b769..3110e87071 100644 --- a/modules/websocket/websocket_peer.h +++ b/modules/websocket/websocket_peer.h @@ -81,7 +81,7 @@ public: return _create(); } - virtual Error connect_to_url(const String &p_url, bool p_verify_tls = true, Ref<X509Certificate> p_cert = Ref<X509Certificate>()) { return ERR_UNAVAILABLE; }; + virtual Error connect_to_url(const String &p_url, Ref<TLSOptions> p_options = Ref<TLSOptions>()) = 0; virtual Error accept_stream(Ref<StreamPeer> p_stream) = 0; virtual Error send(const uint8_t *p_buffer, int p_buffer_size, WriteMode p_mode) = 0; diff --git a/modules/websocket/wsl_peer.cpp b/modules/websocket/wsl_peer.cpp index 9ba286d5ee..8a150c8561 100644 --- a/modules/websocket/wsl_peer.cpp +++ b/modules/websocket/wsl_peer.cpp @@ -333,8 +333,7 @@ void WSLPeer::_do_client_handshake() { // Start SSL handshake tls = Ref<StreamPeerTLS>(StreamPeerTLS::create()); ERR_FAIL_COND_MSG(tls.is_null(), "SSL is not available in this build."); - tls->set_blocking_handshake_enabled(false); - if (tls->connect_to_stream(tcp, verify_tls, requested_host, tls_cert) != OK) { + if (tls->connect_to_stream(tcp, requested_host, tls_options) != OK) { close(-1); return; // Error. } @@ -476,9 +475,10 @@ bool WSLPeer::_verify_server_response() { return true; } -Error WSLPeer::connect_to_url(const String &p_url, bool p_verify_tls, Ref<X509Certificate> p_cert) { +Error WSLPeer::connect_to_url(const String &p_url, Ref<TLSOptions> p_options) { ERR_FAIL_COND_V(wsl_ctx || tcp.is_valid(), ERR_ALREADY_IN_USE); ERR_FAIL_COND_V(p_url.is_empty(), ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_options.is_valid() && p_options->is_server(), ERR_INVALID_PARAMETER); _clear(); @@ -506,8 +506,13 @@ Error WSLPeer::connect_to_url(const String &p_url, bool p_verify_tls, Ref<X509Ce requested_url = p_url; requested_host = host; - verify_tls = p_verify_tls; - tls_cert = p_cert; + + if (p_options.is_valid()) { + tls_options = p_options; + } else { + tls_options = TLSOptions::client(); + } + tcp.instantiate(); resolver.start(host, port); diff --git a/modules/websocket/wsl_peer.h b/modules/websocket/wsl_peer.h index fc81d39a37..c06e768b6d 100644 --- a/modules/websocket/wsl_peer.h +++ b/modules/websocket/wsl_peer.h @@ -102,8 +102,7 @@ private: // WebSocket configuration. bool use_tls = true; - bool verify_tls = true; - Ref<X509Certificate> tls_cert; + Ref<TLSOptions> tls_options; // Packet buffers. Vector<uint8_t> packet_buffer; @@ -132,7 +131,7 @@ public: // WebSocketPeer virtual Error send(const uint8_t *p_buffer, int p_buffer_size, WriteMode p_mode) override; - virtual Error connect_to_url(const String &p_url, bool p_verify_tls = true, Ref<X509Certificate> p_cert = Ref<X509Certificate>()) override; + virtual Error connect_to_url(const String &p_url, Ref<TLSOptions> p_options = Ref<TLSOptions>()) override; virtual Error accept_stream(Ref<StreamPeer> p_stream) override; virtual void close(int p_code = 1000, String p_reason = "") override; virtual void poll() override; |