diff options
Diffstat (limited to 'modules')
39 files changed, 737 insertions, 207 deletions
diff --git a/modules/bcdec/SCsub b/modules/bcdec/SCsub new file mode 100644 index 0000000000..32198eff96 --- /dev/null +++ b/modules/bcdec/SCsub @@ -0,0 +1,10 @@ +#!/usr/bin/env python +from misc.utility.scons_hints import * + +Import("env") +Import("env_modules") + +env_bcdec = env_modules.Clone() + +# Godot source files +env_bcdec.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/squish/config.py b/modules/bcdec/config.py index d22f9454ed..d22f9454ed 100644 --- a/modules/squish/config.py +++ b/modules/bcdec/config.py diff --git a/modules/bcdec/image_decompress_bcdec.cpp b/modules/bcdec/image_decompress_bcdec.cpp new file mode 100644 index 0000000000..30ca1fccb3 --- /dev/null +++ b/modules/bcdec/image_decompress_bcdec.cpp @@ -0,0 +1,181 @@ +/**************************************************************************/ +/* image_decompress_bcdec.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "image_decompress_bcdec.h" + +#include "core/os/os.h" +#include "core/string/print_string.h" + +#define BCDEC_IMPLEMENTATION +#include "thirdparty/misc/bcdec.h" + +inline void bcdec_bc6h_half_s(const void *compressedBlock, void *decompressedBlock, int destinationPitch) { + bcdec_bc6h_half(compressedBlock, decompressedBlock, destinationPitch, true); +} + +inline void bcdec_bc6h_half_u(const void *compressedBlock, void *decompressedBlock, int destinationPitch) { + bcdec_bc6h_half(compressedBlock, decompressedBlock, destinationPitch, false); +} + +static void decompress_image(BCdecFormat format, const void *src, void *dst, const uint64_t width, const uint64_t height) { + const uint8_t *src_blocks = reinterpret_cast<const uint8_t *>(src); + uint8_t *dec_blocks = reinterpret_cast<uint8_t *>(dst); + uint64_t src_pos = 0, dst_pos = 0; + +#define DECOMPRESS_LOOP(func, block_size, color_bytesize, color_components) \ + for (uint64_t y = 0; y < height; y += 4) { \ + for (uint64_t x = 0; x < width; x += 4) { \ + func(&src_blocks[src_pos], &dec_blocks[dst_pos], width *color_components); \ + src_pos += block_size; \ + dst_pos += 4 * color_bytesize; \ + } \ + dst_pos += 3 * width * color_bytesize; \ + } + + switch (format) { + case BCdec_BC1: { + DECOMPRESS_LOOP(bcdec_bc1, BCDEC_BC1_BLOCK_SIZE, 4, 4) + } break; + case BCdec_BC2: { + DECOMPRESS_LOOP(bcdec_bc2, BCDEC_BC2_BLOCK_SIZE, 4, 4) + } break; + case BCdec_BC3: { + DECOMPRESS_LOOP(bcdec_bc3, BCDEC_BC3_BLOCK_SIZE, 4, 4) + } break; + case BCdec_BC4: { + DECOMPRESS_LOOP(bcdec_bc4, BCDEC_BC4_BLOCK_SIZE, 1, 1) + } break; + case BCdec_BC5: { + DECOMPRESS_LOOP(bcdec_bc5, BCDEC_BC5_BLOCK_SIZE, 2, 2) + } break; + case BCdec_BC6U: { + DECOMPRESS_LOOP(bcdec_bc6h_half_u, BCDEC_BC6H_BLOCK_SIZE, 6, 3) + } break; + case BCdec_BC6S: { + DECOMPRESS_LOOP(bcdec_bc6h_half_s, BCDEC_BC6H_BLOCK_SIZE, 6, 3) + } break; + case BCdec_BC7: { + DECOMPRESS_LOOP(bcdec_bc7, BCDEC_BC7_BLOCK_SIZE, 4, 4) + } break; + } + +#undef DECOMPRESS_LOOP +} + +void image_decompress_bcdec(Image *p_image) { + uint64_t start_time = OS::get_singleton()->get_ticks_msec(); + + int w = p_image->get_width(); + int h = p_image->get_height(); + + Image::Format source_format = p_image->get_format(); + Image::Format target_format = Image::FORMAT_MAX; + + BCdecFormat bcdec_format = BCdec_BC1; + + switch (source_format) { + case Image::FORMAT_DXT1: + bcdec_format = BCdec_BC1; + target_format = Image::FORMAT_RGBA8; + break; + + case Image::FORMAT_DXT3: + bcdec_format = BCdec_BC2; + target_format = Image::FORMAT_RGBA8; + break; + + case Image::FORMAT_DXT5: + case Image::FORMAT_DXT5_RA_AS_RG: + bcdec_format = BCdec_BC3; + target_format = Image::FORMAT_RGBA8; + break; + + case Image::FORMAT_RGTC_R: + bcdec_format = BCdec_BC4; + target_format = Image::FORMAT_R8; + break; + + case Image::FORMAT_RGTC_RG: + bcdec_format = BCdec_BC5; + target_format = Image::FORMAT_RG8; + break; + + case Image::FORMAT_BPTC_RGBFU: + bcdec_format = BCdec_BC6U; + target_format = Image::FORMAT_RGBH; + break; + + case Image::FORMAT_BPTC_RGBF: + bcdec_format = BCdec_BC6S; + target_format = Image::FORMAT_RGBH; + break; + + case Image::FORMAT_BPTC_RGBA: + bcdec_format = BCdec_BC7; + target_format = Image::FORMAT_RGBA8; + break; + + default: + ERR_FAIL_MSG("bcdec: Can't decompress unknown format: " + Image::get_format_name(source_format) + "."); + break; + } + + int mm_count = p_image->get_mipmap_count(); + int64_t target_size = Image::get_image_data_size(w, h, target_format, p_image->has_mipmaps()); + + Vector<uint8_t> data; + data.resize(target_size); + + const uint8_t *rb = p_image->get_data().ptr(); + uint8_t *wb = data.ptrw(); + + // Decompress mipmaps. + for (int i = 0; i <= mm_count; i++) { + int64_t src_ofs = 0, mipmap_size = 0; + int mipmap_w = 0, mipmap_h = 0; + p_image->get_mipmap_offset_size_and_dimensions(i, src_ofs, mipmap_size, mipmap_w, mipmap_h); + + int64_t dst_ofs = Image::get_image_mipmap_offset(p_image->get_width(), p_image->get_height(), target_format, i); + decompress_image(bcdec_format, rb + src_ofs, wb + dst_ofs, mipmap_w, mipmap_h); + + w >>= 1; + h >>= 1; + } + + p_image->set_data(p_image->get_width(), p_image->get_height(), p_image->has_mipmaps(), target_format, data); + + // Swap channels if necessary. + if (source_format == Image::FORMAT_DXT5_RA_AS_RG) { + p_image->convert_ra_rgba8_to_rg(); + } + + print_verbose(vformat("bcdec: Decompression of a %dx%d %s image with %d mipmaps took %d ms.", + p_image->get_width(), p_image->get_height(), Image::get_format_name(source_format), p_image->get_mipmap_count(), OS::get_singleton()->get_ticks_msec() - start_time)); +} diff --git a/modules/squish/image_decompress_squish.h b/modules/bcdec/image_decompress_bcdec.h index 53c5d344a5..b82ceed9a4 100644 --- a/modules/squish/image_decompress_squish.h +++ b/modules/bcdec/image_decompress_bcdec.h @@ -1,5 +1,5 @@ /**************************************************************************/ -/* image_decompress_squish.h */ +/* image_decompress_bcdec.h */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,11 +28,22 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ -#ifndef IMAGE_DECOMPRESS_SQUISH_H -#define IMAGE_DECOMPRESS_SQUISH_H +#ifndef IMAGE_DECOMPRESS_BCDEC_H +#define IMAGE_DECOMPRESS_BCDEC_H #include "core/io/image.h" -void image_decompress_squish(Image *p_image); +enum BCdecFormat { + BCdec_BC1, + BCdec_BC2, + BCdec_BC3, + BCdec_BC4, + BCdec_BC5, + BCdec_BC6S, + BCdec_BC6U, + BCdec_BC7, +}; -#endif // IMAGE_DECOMPRESS_SQUISH_H +void image_decompress_bcdec(Image *p_image); + +#endif // IMAGE_DECOMPRESS_BCDEC_H diff --git a/modules/squish/register_types.cpp b/modules/bcdec/register_types.cpp index af7cf8f4f1..cbf9c0d383 100644 --- a/modules/squish/register_types.cpp +++ b/modules/bcdec/register_types.cpp @@ -30,17 +30,18 @@ #include "register_types.h" -#include "image_decompress_squish.h" +#include "image_decompress_bcdec.h" -void initialize_squish_module(ModuleInitializationLevel p_level) { +void initialize_bcdec_module(ModuleInitializationLevel p_level) { if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) { return; } - Image::_image_decompress_bc = image_decompress_squish; + Image::_image_decompress_bc = image_decompress_bcdec; + Image::_image_decompress_bptc = image_decompress_bcdec; } -void uninitialize_squish_module(ModuleInitializationLevel p_level) { +void uninitialize_bcdec_module(ModuleInitializationLevel p_level) { if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) { return; } diff --git a/modules/squish/register_types.h b/modules/bcdec/register_types.h index 1786b28ed3..eb721e3f2a 100644 --- a/modules/squish/register_types.h +++ b/modules/bcdec/register_types.h @@ -28,12 +28,12 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ -#ifndef SQUISH_REGISTER_TYPES_H -#define SQUISH_REGISTER_TYPES_H +#ifndef BCDEC_REGISTER_TYPES_H +#define BCDEC_REGISTER_TYPES_H #include "modules/register_module_types.h" -void initialize_squish_module(ModuleInitializationLevel p_level); -void uninitialize_squish_module(ModuleInitializationLevel p_level); +void initialize_bcdec_module(ModuleInitializationLevel p_level); +void uninitialize_bcdec_module(ModuleInitializationLevel p_level); -#endif // SQUISH_REGISTER_TYPES_H +#endif // BCDEC_REGISTER_TYPES_H diff --git a/modules/camera/buffer_decoder.cpp b/modules/camera/buffer_decoder.cpp index 024a68f080..85cfea242c 100644 --- a/modules/camera/buffer_decoder.cpp +++ b/modules/camera/buffer_decoder.cpp @@ -105,7 +105,7 @@ void SeparateYuyvBufferDecoder::decode(StreamingBuffer p_buffer) { cbcr_image.instantiate(width, height, false, Image::FORMAT_RGB8, cbcr_image_data); } - camera_feed->set_YCbCr_imgs(y_image, cbcr_image); + camera_feed->set_ycbcr_images(y_image, cbcr_image); } YuyvToGrayscaleBufferDecoder::YuyvToGrayscaleBufferDecoder(CameraFeed *p_camera_feed) : @@ -133,7 +133,7 @@ void YuyvToGrayscaleBufferDecoder::decode(StreamingBuffer p_buffer) { image.instantiate(width, height, false, Image::FORMAT_RGB8, image_data); } - camera_feed->set_RGB_img(image); + camera_feed->set_rgb_image(image); } YuyvToRgbBufferDecoder::YuyvToRgbBufferDecoder(CameraFeed *p_camera_feed) : @@ -176,7 +176,7 @@ void YuyvToRgbBufferDecoder::decode(StreamingBuffer p_buffer) { image.instantiate(width, height, false, Image::FORMAT_RGB8, image_data); } - camera_feed->set_RGB_img(image); + camera_feed->set_rgb_image(image); } CopyBufferDecoder::CopyBufferDecoder(CameraFeed *p_camera_feed, bool p_rgba) : @@ -195,7 +195,7 @@ void CopyBufferDecoder::decode(StreamingBuffer p_buffer) { image.instantiate(width, height, false, rgba ? Image::FORMAT_RGBA8 : Image::FORMAT_LA8, image_data); } - camera_feed->set_RGB_img(image); + camera_feed->set_rgb_image(image); } JpegBufferDecoder::JpegBufferDecoder(CameraFeed *p_camera_feed) : @@ -207,6 +207,6 @@ void JpegBufferDecoder::decode(StreamingBuffer p_buffer) { uint8_t *dst = (uint8_t *)image_data.ptrw(); memcpy(dst, p_buffer.start, p_buffer.length); if (image->load_jpg_from_buffer(image_data) == OK) { - camera_feed->set_RGB_img(image); + camera_feed->set_rgb_image(image); } } diff --git a/modules/camera/camera_feed_linux.cpp b/modules/camera/camera_feed_linux.cpp index 9ed8eb0d0a..94bb2b6ad3 100644 --- a/modules/camera/camera_feed_linux.cpp +++ b/modules/camera/camera_feed_linux.cpp @@ -232,6 +232,7 @@ String CameraFeedLinux::get_device_name() const { } bool CameraFeedLinux::activate_feed() { + ERR_FAIL_COND_V_MSG(selected_format == -1, false, "CameraFeed format needs to be set before activating."); file_descriptor = open(device_name.ascii(), O_RDWR | O_NONBLOCK, 0); if (_request_buffers() && _start_capturing()) { buffer_decoder = _create_buffer_decoder(); @@ -302,16 +303,14 @@ Array CameraFeedLinux::get_formats() const { } CameraFeed::FeedFormat CameraFeedLinux::get_format() const { - return formats[selected_format]; + FeedFormat feed_format = {}; + return selected_format == -1 ? feed_format : formats[selected_format]; } bool CameraFeedLinux::set_format(int p_index, const Dictionary &p_parameters) { ERR_FAIL_COND_V_MSG(active, false, "Feed is active."); ERR_FAIL_INDEX_V_MSG(p_index, formats.size(), false, "Invalid format index."); - parameters = p_parameters.duplicate(); - selected_format = p_index; - FeedFormat feed_format = formats[p_index]; file_descriptor = open(device_name.ascii(), O_RDWR | O_NONBLOCK, 0); @@ -344,6 +343,8 @@ bool CameraFeedLinux::set_format(int p_index, const Dictionary &p_parameters) { } close(file_descriptor); + parameters = p_parameters.duplicate(); + selected_format = p_index; emit_signal(SNAME("format_changed")); return true; @@ -353,7 +354,6 @@ CameraFeedLinux::CameraFeedLinux(const String &p_device_name) : CameraFeed() { device_name = p_device_name; _query_device(device_name); - set_format(0, Dictionary()); } CameraFeedLinux::~CameraFeedLinux() { diff --git a/modules/camera/camera_macos.mm b/modules/camera/camera_macos.mm index 578a1d6325..de4f814846 100644 --- a/modules/camera/camera_macos.mm +++ b/modules/camera/camera_macos.mm @@ -182,7 +182,7 @@ } // set our texture... - feed->set_YCbCr_imgs(img[0], img[1]); + feed->set_ycbcr_images(img[0], img[1]); } // and unlock diff --git a/modules/cvtt/register_types.cpp b/modules/cvtt/register_types.cpp index 211d419349..80e3062d04 100644 --- a/modules/cvtt/register_types.cpp +++ b/modules/cvtt/register_types.cpp @@ -40,7 +40,6 @@ void initialize_cvtt_module(ModuleInitializationLevel p_level) { } Image::set_compress_bptc_func(image_compress_cvtt); - Image::_image_decompress_bptc = image_decompress_cvtt; } void uninitialize_cvtt_module(ModuleInitializationLevel p_level) { diff --git a/modules/enet/doc_classes/ENetPacketPeer.xml b/modules/enet/doc_classes/ENetPacketPeer.xml index 3171da1f6d..659cea974c 100644 --- a/modules/enet/doc_classes/ENetPacketPeer.xml +++ b/modules/enet/doc_classes/ENetPacketPeer.xml @@ -18,6 +18,12 @@ Returns the number of channels allocated for communication with peer. </description> </method> + <method name="get_packet_flags" qualifiers="const"> + <return type="int" /> + <description> + Returns the ENet flags of the next packet in the received queue. See [code]FLAG_*[/code] constants for available packet flags. Note that not all flags are replicated from the sending peer to the receiving peer. + </description> + </method> <method name="get_remote_address" qualifiers="const"> <return type="String" /> <description> diff --git a/modules/enet/enet_packet_peer.cpp b/modules/enet/enet_packet_peer.cpp index edb33fc96b..9ec68465a5 100644 --- a/modules/enet/enet_packet_peer.cpp +++ b/modules/enet/enet_packet_peer.cpp @@ -175,6 +175,11 @@ int ENetPacketPeer::get_channels() const { return peer->channelCount; } +int ENetPacketPeer::get_packet_flags() const { + ERR_FAIL_COND_V(packet_queue.is_empty(), 0); + return packet_queue.front()->get()->flags; +} + void ENetPacketPeer::_on_disconnect() { if (peer) { peer->data = nullptr; @@ -206,6 +211,7 @@ void ENetPacketPeer::_bind_methods() { ClassDB::bind_method(D_METHOD("send", "channel", "packet", "flags"), &ENetPacketPeer::_send); ClassDB::bind_method(D_METHOD("throttle_configure", "interval", "acceleration", "deceleration"), &ENetPacketPeer::throttle_configure); ClassDB::bind_method(D_METHOD("set_timeout", "timeout", "timeout_min", "timeout_max"), &ENetPacketPeer::set_timeout); + ClassDB::bind_method(D_METHOD("get_packet_flags"), &ENetPacketPeer::get_packet_flags); ClassDB::bind_method(D_METHOD("get_remote_address"), &ENetPacketPeer::get_remote_address); ClassDB::bind_method(D_METHOD("get_remote_port"), &ENetPacketPeer::get_remote_port); ClassDB::bind_method(D_METHOD("get_statistic", "statistic"), &ENetPacketPeer::get_statistic); diff --git a/modules/enet/enet_packet_peer.h b/modules/enet/enet_packet_peer.h index fe40d06188..b41d67e86b 100644 --- a/modules/enet/enet_packet_peer.h +++ b/modules/enet/enet_packet_peer.h @@ -113,6 +113,7 @@ public: double get_statistic(PeerStatistic p_stat); PeerState get_state() const; int get_channels() const; + int get_packet_flags() const; // Extras IPAddress get_remote_address() const; diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml index f539f27848..5fe47d69df 100644 --- a/modules/gdscript/doc_classes/@GDScript.xml +++ b/modules/gdscript/doc_classes/@GDScript.xml @@ -669,6 +669,41 @@ [b]Note:[/b] Subgroups cannot be nested, they only provide one extra level of depth. Just like the next group ends the previous group, so do the subsequent subgroups. </description> </annotation> + <annotation name="@export_tool_button"> + <return type="void" /> + <param index="0" name="text" type="String" /> + <param index="1" name="icon" type="String" default="""" /> + <description> + Export a [Callable] property as a clickable button with the label [param text]. When the button is pressed, the callable is called. + If [param icon] is specified, it is used to fetch an icon for the button via [method Control.get_theme_icon], from the [code]"EditorIcons"[/code] theme type. If [param icon] is omitted, the default [code]"Callable"[/code] icon is used instead. + Consider using the [EditorUndoRedoManager] to allow the action to be reverted safely. + See also [constant PROPERTY_HINT_TOOL_BUTTON]. + [codeblock] + @tool + extends Sprite2D + + @export_tool_button("Hello") var hello_action = hello + @export_tool_button("Randomize the color!", "ColorRect") + var randomize_color_action = randomize_color + + func hello(): + print("Hello world!") + + func randomize_color(): + var undo_redo = EditorInterface.get_editor_undo_redo() + undo_redo.create_action("Randomized Sprite2D Color") + undo_redo.add_do_property(self, &"self_modulate", Color(randf(), randf(), randf())) + undo_redo.add_undo_property(self, &"self_modulate", self_modulate) + undo_redo.commit_action() + [/codeblock] + [b]Note:[/b] The property is exported without the [constant PROPERTY_USAGE_STORAGE] flag because a [Callable] cannot be properly serialized and stored in a file. + [b]Note:[/b] In an exported project neither [EditorInterface] nor [EditorUndoRedoManager] exist, which may cause some scripts to break. To prevent this, you can use [method Engine.get_singleton] and omit the static type from the variable declaration: + [codeblock] + var undo_redo = Engine.get_singleton(&"EditorInterface").get_editor_undo_redo() + [/codeblock] + [b]Note:[/b] Avoid storing lambda callables in member variables of [RefCounted]-based classes (e.g. resources), as this can lead to memory leaks. Use only method callables and optionally [method Callable.bind] or [method Callable.unbind]. + </description> + </annotation> <annotation name="@icon"> <return type="void" /> <param index="0" name="icon_path" type="String" /> diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index 73f2b1d618..0fd891aa80 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -864,7 +864,8 @@ static void _get_directory_contents(EditorFileSystemDirectory *p_dir, HashMap<St } } -static void _find_annotation_arguments(const GDScriptParser::AnnotationNode *p_annotation, int p_argument, const String p_quote_style, HashMap<String, ScriptLanguage::CodeCompletionOption> &r_result) { +static void _find_annotation_arguments(const GDScriptParser::AnnotationNode *p_annotation, int p_argument, const String p_quote_style, HashMap<String, ScriptLanguage::CodeCompletionOption> &r_result, String &r_arghint) { + r_arghint = _make_arguments_hint(p_annotation->info->info, p_argument, true); if (p_annotation->name == SNAME("@export_range")) { if (p_argument == 3 || p_argument == 4 || p_argument == 5) { // Slider hint. @@ -2975,11 +2976,6 @@ static bool _get_subscript_type(GDScriptParser::CompletionContext &p_context, co } break; case GDScriptParser::Node::IDENTIFIER: { - if (p_subscript->base->datatype.type_source == GDScriptParser::DataType::ANNOTATED_EXPLICIT) { - // Annotated type takes precedence. - return false; - } - const GDScriptParser::IdentifierNode *identifier_node = static_cast<GDScriptParser::IdentifierNode *>(p_subscript->base); switch (identifier_node->source) { @@ -3017,6 +3013,14 @@ static bool _get_subscript_type(GDScriptParser::CompletionContext &p_context, co if (get_node != nullptr) { const Object *node = p_context.base->call("get_node_or_null", NodePath(get_node->full_path)); if (node != nullptr) { + GDScriptParser::DataType assigned_type = _type_from_variant(node, p_context).type; + GDScriptParser::DataType base_type = p_subscript->base->datatype; + + if (p_subscript->base->type == GDScriptParser::Node::IDENTIFIER && base_type.type_source == GDScriptParser::DataType::ANNOTATED_EXPLICIT && (assigned_type.kind != base_type.kind || assigned_type.script_path != base_type.script_path || assigned_type.native_type != base_type.native_type)) { + // Annotated type takes precedence. + return false; + } + if (r_base != nullptr) { *r_base = node; } @@ -3183,7 +3187,7 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c break; } const GDScriptParser::AnnotationNode *annotation = static_cast<const GDScriptParser::AnnotationNode *>(completion_context.node); - _find_annotation_arguments(annotation, completion_context.current_argument, quote_style, options); + _find_annotation_arguments(annotation, completion_context.current_argument, quote_style, options, r_call_hint); r_forced = true; } break; case GDScriptParser::COMPLETION_BUILT_IN_TYPE_CONSTANT_OR_STATIC_METHOD: { diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 65aa150be3..111a39d730 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -122,6 +122,7 @@ GDScriptParser::GDScriptParser() { register_annotation(MethodInfo("@export_flags_avoidance"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_AVOIDANCE, Variant::INT>); register_annotation(MethodInfo("@export_storage"), AnnotationInfo::VARIABLE, &GDScriptParser::export_storage_annotation); register_annotation(MethodInfo("@export_custom", PropertyInfo(Variant::INT, "hint", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_CLASS_IS_ENUM, "PropertyHint"), PropertyInfo(Variant::STRING, "hint_string"), PropertyInfo(Variant::INT, "usage", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_CLASS_IS_BITFIELD, "PropertyUsageFlags")), AnnotationInfo::VARIABLE, &GDScriptParser::export_custom_annotation, varray(PROPERTY_USAGE_DEFAULT)); + register_annotation(MethodInfo("@export_tool_button", PropertyInfo(Variant::STRING, "text"), PropertyInfo(Variant::STRING, "icon")), AnnotationInfo::VARIABLE, &GDScriptParser::export_tool_button_annotation, varray("")); // Export grouping annotations. register_annotation(MethodInfo("@export_category", PropertyInfo(Variant::STRING, "name")), AnnotationInfo::STANDALONE, &GDScriptParser::export_group_annotations<PROPERTY_USAGE_CATEGORY>); register_annotation(MethodInfo("@export_group", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::STRING, "prefix")), AnnotationInfo::STANDALONE, &GDScriptParser::export_group_annotations<PROPERTY_USAGE_GROUP>, varray("")); @@ -1639,23 +1640,29 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali advance(); // Arguments. push_completion_call(annotation); - make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, 0); int argument_index = 0; do { + make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, argument_index); + set_last_completion_call_arg(argument_index); if (check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) { // Allow for trailing comma. break; } - make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, argument_index); - set_last_completion_call_arg(argument_index++); ExpressionNode *argument = parse_expression(false); + if (argument == nullptr) { push_error("Expected expression as the annotation argument."); valid = false; - continue; + } else { + annotation->arguments.push_back(argument); + + if (argument->type == Node::LITERAL) { + override_completion_context(argument, COMPLETION_ANNOTATION_ARGUMENTS, annotation, argument_index); + } } - annotation->arguments.push_back(argument); + + argument_index++; } while (match(GDScriptTokenizer::Token::COMMA) && !is_at_end()); pop_multiline(); @@ -4618,10 +4625,10 @@ bool GDScriptParser::export_annotations(AnnotationNode *p_annotation, Node *p_ta // For `@export_storage` and `@export_custom`, there is no need to check the variable type, argument values, // or handle array exports in a special way, so they are implemented as separate methods. -bool GDScriptParser::export_storage_annotation(AnnotationNode *p_annotation, Node *p_node, ClassNode *p_class) { - ERR_FAIL_COND_V_MSG(p_node->type != Node::VARIABLE, false, vformat(R"("%s" annotation can only be applied to variables.)", p_annotation->name)); +bool GDScriptParser::export_storage_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { + ERR_FAIL_COND_V_MSG(p_target->type != Node::VARIABLE, false, vformat(R"("%s" annotation can only be applied to variables.)", p_annotation->name)); - VariableNode *variable = static_cast<VariableNode *>(p_node); + VariableNode *variable = static_cast<VariableNode *>(p_target); if (variable->is_static) { push_error(vformat(R"(Annotation "%s" cannot be applied to a static variable.)", p_annotation->name), p_annotation); return false; @@ -4640,11 +4647,11 @@ bool GDScriptParser::export_storage_annotation(AnnotationNode *p_annotation, Nod return true; } -bool GDScriptParser::export_custom_annotation(AnnotationNode *p_annotation, Node *p_node, ClassNode *p_class) { - ERR_FAIL_COND_V_MSG(p_node->type != Node::VARIABLE, false, vformat(R"("%s" annotation can only be applied to variables.)", p_annotation->name)); +bool GDScriptParser::export_custom_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { + ERR_FAIL_COND_V_MSG(p_target->type != Node::VARIABLE, false, vformat(R"("%s" annotation can only be applied to variables.)", p_annotation->name)); ERR_FAIL_COND_V_MSG(p_annotation->resolved_arguments.size() < 2, false, R"(Annotation "@export_custom" requires 2 arguments.)"); - VariableNode *variable = static_cast<VariableNode *>(p_node); + VariableNode *variable = static_cast<VariableNode *>(p_target); if (variable->is_static) { push_error(vformat(R"(Annotation "%s" cannot be applied to a static variable.)", p_annotation->name), p_annotation); return false; @@ -4668,12 +4675,56 @@ bool GDScriptParser::export_custom_annotation(AnnotationNode *p_annotation, Node return true; } -template <PropertyUsageFlags t_usage> -bool GDScriptParser::export_group_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { - if (p_annotation->resolved_arguments.is_empty()) { +bool GDScriptParser::export_tool_button_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { +#ifdef TOOLS_ENABLED + ERR_FAIL_COND_V_MSG(p_target->type != Node::VARIABLE, false, vformat(R"("%s" annotation can only be applied to variables.)", p_annotation->name)); + ERR_FAIL_COND_V(p_annotation->resolved_arguments.is_empty(), false); + + if (!is_tool()) { + push_error(R"(Tool buttons can only be used in tool scripts (add "@tool" to the top of the script).)", p_annotation); + return false; + } + + VariableNode *variable = static_cast<VariableNode *>(p_target); + + if (variable->is_static) { + push_error(vformat(R"(Annotation "%s" cannot be applied to a static variable.)", p_annotation->name), p_annotation); + return false; + } + if (variable->exported) { + push_error(vformat(R"(Annotation "%s" cannot be used with another "@export" annotation.)", p_annotation->name), p_annotation); return false; } + const DataType variable_type = variable->get_datatype(); + if (!variable_type.is_variant() && variable_type.is_hard_type()) { + if (variable_type.kind != DataType::BUILTIN || variable_type.builtin_type != Variant::CALLABLE) { + push_error(vformat(R"("@export_tool_button" annotation requires a variable of type "Callable", but type "%s" was given instead.)", variable_type.to_string()), p_annotation); + return false; + } + } + + variable->exported = true; + + // Build the hint string (format: `<text>[,<icon>]`). + String hint_string = p_annotation->resolved_arguments[0].operator String(); // Button text. + if (p_annotation->resolved_arguments.size() > 1) { + hint_string += "," + p_annotation->resolved_arguments[1].operator String(); // Button icon. + } + + variable->export_info.type = Variant::CALLABLE; + variable->export_info.hint = PROPERTY_HINT_TOOL_BUTTON; + variable->export_info.hint_string = hint_string; + variable->export_info.usage = PROPERTY_USAGE_EDITOR; +#endif // TOOLS_ENABLED + + return true; // Only available in editor. +} + +template <PropertyUsageFlags t_usage> +bool GDScriptParser::export_group_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { + ERR_FAIL_COND_V(p_annotation->resolved_arguments.is_empty(), false); + p_annotation->export_info.name = p_annotation->resolved_arguments[0]; switch (t_usage) { diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index 7840474a89..7f64ae902b 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -1507,6 +1507,7 @@ private: bool export_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); bool export_storage_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); bool export_custom_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); + bool export_tool_button_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); template <PropertyUsageFlags t_usage> bool export_group_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); bool warning_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); diff --git a/modules/gdscript/tests/scripts/parser/errors/export_tool_button_requires_tool_mode.gd b/modules/gdscript/tests/scripts/parser/errors/export_tool_button_requires_tool_mode.gd new file mode 100644 index 0000000000..48be5b2541 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/export_tool_button_requires_tool_mode.gd @@ -0,0 +1 @@ +@export_tool_button("Click me!") var action diff --git a/modules/gdscript/tests/scripts/parser/errors/export_tool_button_requires_tool_mode.out b/modules/gdscript/tests/scripts/parser/errors/export_tool_button_requires_tool_mode.out new file mode 100644 index 0000000000..fb148308e4 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/export_tool_button_requires_tool_mode.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Tool buttons can only be used in tool scripts (add "@tool" to the top of the script). diff --git a/modules/gdscript/tests/scripts/parser/features/export_variable.gd b/modules/gdscript/tests/scripts/parser/features/export_variable.gd index 1e134d0e0e..8aa449f602 100644 --- a/modules/gdscript/tests/scripts/parser/features/export_variable.gd +++ b/modules/gdscript/tests/scripts/parser/features/export_variable.gd @@ -1,3 +1,4 @@ +@tool class_name ExportVariableTest extends Node @@ -47,6 +48,10 @@ const PreloadedUnnamedClass = preload("./export_variable_unnamed.notest.gd") @export_custom(PROPERTY_HINT_ENUM, "A,B,C") var test_export_custom_weak_int = 5 @export_custom(PROPERTY_HINT_ENUM, "A,B,C") var test_export_custom_hard_int: int = 6 +# `@export_tool_button`. +@export_tool_button("Click me!") var test_tool_button_1: Callable +@export_tool_button("Click me!", "ColorRect") var test_tool_button_2: Callable + func test(): for property in get_property_list(): if str(property.name).begins_with("test_"): diff --git a/modules/gdscript/tests/scripts/parser/features/export_variable.out b/modules/gdscript/tests/scripts/parser/features/export_variable.out index d10462bb8d..0d915e00e6 100644 --- a/modules/gdscript/tests/scripts/parser/features/export_variable.out +++ b/modules/gdscript/tests/scripts/parser/features/export_variable.out @@ -55,3 +55,7 @@ var test_export_custom_weak_int: int = 5 hint=ENUM hint_string="A,B,C" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_export_custom_hard_int: int = 6 hint=ENUM hint_string="A,B,C" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" +var test_tool_button_1: Callable = Callable() + hint=TOOL_BUTTON hint_string="Click me!" usage=EDITOR|SCRIPT_VARIABLE class_name=&"" +var test_tool_button_2: Callable = Callable() + hint=TOOL_BUTTON hint_string="Click me!,ColorRect" usage=EDITOR|SCRIPT_VARIABLE class_name=&"" diff --git a/modules/gdscript/tests/scripts/project.godot b/modules/gdscript/tests/scripts/project.godot index c9035ecab9..0757bec5c4 100644 --- a/modules/gdscript/tests/scripts/project.godot +++ b/modules/gdscript/tests/scripts/project.godot @@ -12,6 +12,6 @@ config/name="GDScript Integration Test Suite" [input] test_input_action={ -"deadzone": 0.5, +"deadzone": 0.2, "events": [] } diff --git a/modules/gdscript/tests/scripts/utils.notest.gd b/modules/gdscript/tests/scripts/utils.notest.gd index 1e2788f765..fa289e442f 100644 --- a/modules/gdscript/tests/scripts/utils.notest.gd +++ b/modules/gdscript/tests/scripts/utils.notest.gd @@ -205,6 +205,9 @@ static func get_property_hint_name(hint: PropertyHint) -> String: return "PROPERTY_HINT_HIDE_QUATERNION_EDIT" PROPERTY_HINT_PASSWORD: return "PROPERTY_HINT_PASSWORD" + PROPERTY_HINT_TOOL_BUTTON: + return "PROPERTY_HINT_TOOL_BUTTON" + printerr("Argument `hint` is invalid. Use `PROPERTY_HINT_*` constants.") return "<invalid hint>" diff --git a/modules/multiplayer/SCsub b/modules/multiplayer/SCsub index 97f91c5674..f9f4e579e8 100644 --- a/modules/multiplayer/SCsub +++ b/modules/multiplayer/SCsub @@ -13,3 +13,10 @@ if env.editor_build: env_mp.add_source_files(module_obj, "editor/*.cpp") env.modules_sources += module_obj + +if env["tests"]: + env_mp.Append(CPPDEFINES=["TESTS_ENABLED"]) + env_mp.add_source_files(env.modules_sources, "./tests/*.cpp") + + if env["disable_exceptions"]: + env_mp.Append(CPPDEFINES=["DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS"]) diff --git a/modules/multiplayer/doc_classes/SceneMultiplayer.xml b/modules/multiplayer/doc_classes/SceneMultiplayer.xml index 42f32d4848..3277f1ff3e 100644 --- a/modules/multiplayer/doc_classes/SceneMultiplayer.xml +++ b/modules/multiplayer/doc_classes/SceneMultiplayer.xml @@ -65,7 +65,7 @@ [b]Warning:[/b] Deserialized objects can contain code which gets executed. Do not use this option if the serialized object comes from untrusted sources to avoid potential security threat such as remote code execution. </member> <member name="auth_callback" type="Callable" setter="set_auth_callback" getter="get_auth_callback" default="Callable()"> - The callback to execute when when receiving authentication data sent via [method send_auth]. If the [Callable] is empty (default), peers will be automatically accepted as soon as they connect. + The callback to execute when receiving authentication data sent via [method send_auth]. If the [Callable] is empty (default), peers will be automatically accepted as soon as they connect. </member> <member name="auth_timeout" type="float" setter="set_auth_timeout" getter="get_auth_timeout" default="3.0"> If set to a value greater than [code]0.0[/code], the maximum duration in seconds peers can stay in the authenticating state, after which the authentication will automatically fail. See the [signal peer_authenticating] and [signal peer_authentication_failed] signals. diff --git a/modules/multiplayer/tests/test_scene_multiplayer.h b/modules/multiplayer/tests/test_scene_multiplayer.h new file mode 100644 index 0000000000..5e526c9be6 --- /dev/null +++ b/modules/multiplayer/tests/test_scene_multiplayer.h @@ -0,0 +1,284 @@ +/**************************************************************************/ +/* test_scene_multiplayer.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef TEST_SCENE_MULTIPLAYER_H +#define TEST_SCENE_MULTIPLAYER_H + +#include "tests/test_macros.h" +#include "tests/test_utils.h" + +#include "../scene_multiplayer.h" + +namespace TestSceneMultiplayer { + +static inline Array build_array() { + return Array(); +} +template <typename... Targs> +static inline Array build_array(Variant item, Targs... Fargs) { + Array a = build_array(Fargs...); + a.push_front(item); + return a; +} + +TEST_CASE("[Multiplayer][SceneMultiplayer] Defaults") { + Ref<SceneMultiplayer> scene_multiplayer; + scene_multiplayer.instantiate(); + + REQUIRE(scene_multiplayer->has_multiplayer_peer()); + Ref<MultiplayerPeer> multiplayer_peer = scene_multiplayer->get_multiplayer_peer(); + REQUIRE_MESSAGE(Object::cast_to<OfflineMultiplayerPeer>(multiplayer_peer.ptr()) != nullptr, "By default it must be an OfflineMultiplayerPeer instance."); + CHECK_EQ(scene_multiplayer->poll(), Error::OK); + CHECK_EQ(scene_multiplayer->get_unique_id(), MultiplayerPeer::TARGET_PEER_SERVER); + CHECK_EQ(scene_multiplayer->get_peer_ids(), Vector<int>()); + CHECK_EQ(scene_multiplayer->get_remote_sender_id(), 0); + CHECK_EQ(scene_multiplayer->get_root_path(), NodePath()); + CHECK(scene_multiplayer->get_connected_peers().is_empty()); + CHECK_FALSE(scene_multiplayer->is_refusing_new_connections()); + CHECK_FALSE(scene_multiplayer->is_object_decoding_allowed()); + CHECK(scene_multiplayer->is_server_relay_enabled()); + CHECK_EQ(scene_multiplayer->get_max_sync_packet_size(), 1350); + CHECK_EQ(scene_multiplayer->get_max_delta_packet_size(), 65535); + CHECK(scene_multiplayer->is_server()); +} + +TEST_CASE("[Multiplayer][SceneMultiplayer][SceneTree] SceneTree has a OfflineMultiplayerPeer by default") { + Ref<SceneMultiplayer> scene_multiplayer = SceneTree::get_singleton()->get_multiplayer(); + REQUIRE(scene_multiplayer->has_multiplayer_peer()); + + Ref<MultiplayerPeer> multiplayer_peer = scene_multiplayer->get_multiplayer_peer(); + REQUIRE_MESSAGE(Object::cast_to<OfflineMultiplayerPeer>(multiplayer_peer.ptr()) != nullptr, "By default it must be an OfflineMultiplayerPeer instance."); +} + +TEST_CASE("[Multiplayer][SceneMultiplayer][SceneTree] Object configuration add/remove") { + Ref<SceneMultiplayer> scene_multiplayer; + scene_multiplayer.instantiate(); + + SUBCASE("Returns invalid parameter") { + CHECK_EQ(scene_multiplayer->object_configuration_add(nullptr, "ImInvalid"), Error::ERR_INVALID_PARAMETER); + CHECK_EQ(scene_multiplayer->object_configuration_remove(nullptr, "ImInvalid"), Error::ERR_INVALID_PARAMETER); + + NodePath foo_path("/Foo"); + NodePath bar_path("/Bar"); + CHECK_EQ(scene_multiplayer->object_configuration_add(nullptr, foo_path), Error::OK); + ERR_PRINT_OFF; + CHECK_EQ(scene_multiplayer->object_configuration_remove(nullptr, bar_path), Error::ERR_INVALID_PARAMETER); + ERR_PRINT_ON; + } + + SUBCASE("Sets root path") { + NodePath foo_path("/Foo"); + CHECK_EQ(scene_multiplayer->object_configuration_add(nullptr, foo_path), Error::OK); + + CHECK_EQ(scene_multiplayer->get_root_path(), foo_path); + } + + SUBCASE("Unsets root path") { + NodePath foo_path("/Foo"); + CHECK_EQ(scene_multiplayer->object_configuration_add(nullptr, foo_path), Error::OK); + + CHECK_EQ(scene_multiplayer->object_configuration_remove(nullptr, foo_path), Error::OK); + CHECK_EQ(scene_multiplayer->get_root_path(), NodePath()); + } + + SUBCASE("Add/Remove a MultiplayerSpawner") { + Node2D *node = memnew(Node2D); + MultiplayerSpawner *spawner = memnew(MultiplayerSpawner); + + CHECK_EQ(scene_multiplayer->object_configuration_add(node, spawner), Error::OK); + CHECK_EQ(scene_multiplayer->object_configuration_remove(node, spawner), Error::OK); + + memdelete(spawner); + memdelete(node); + } + + SUBCASE("Add/Remove a MultiplayerSynchronizer") { + Node2D *node = memnew(Node2D); + MultiplayerSynchronizer *synchronizer = memnew(MultiplayerSynchronizer); + + CHECK_EQ(scene_multiplayer->object_configuration_add(node, synchronizer), Error::OK); + CHECK_EQ(scene_multiplayer->object_configuration_remove(node, synchronizer), Error::OK); + + memdelete(synchronizer); + memdelete(node); + } +} + +TEST_CASE("[Multiplayer][SceneMultiplayer] Root Path") { + Ref<SceneMultiplayer> scene_multiplayer; + scene_multiplayer.instantiate(); + + SUBCASE("Is set") { + NodePath foo_path("/Foo"); + scene_multiplayer->set_root_path(foo_path); + + CHECK_EQ(scene_multiplayer->get_root_path(), foo_path); + } + + SUBCASE("Fails when path is empty") { + ERR_PRINT_OFF; + scene_multiplayer->set_root_path(NodePath()); + ERR_PRINT_ON; + } + + SUBCASE("Fails when path is relative") { + NodePath foo_path("Foo"); + ERR_PRINT_OFF; + scene_multiplayer->set_root_path(foo_path); + ERR_PRINT_ON; + + CHECK_EQ(scene_multiplayer->get_root_path(), NodePath()); + } +} + +// This one could be a dummy callback because the current set of test is not actually testing the full auth flow. +static Variant auth_callback(Variant sv, Variant pvav) { + return Variant(); +} + +TEST_CASE("[Multiplayer][SceneMultiplayer][SceneTree] Send Authentication") { + Ref<SceneMultiplayer> scene_multiplayer; + scene_multiplayer.instantiate(); + SceneTree::get_singleton()->set_multiplayer(scene_multiplayer); + scene_multiplayer->set_auth_callback(callable_mp_static(auth_callback)); + + SUBCASE("Is properly sent") { + SIGNAL_WATCH(scene_multiplayer.ptr(), "peer_authenticating"); + + // Adding a peer to MultiplayerPeer. + Ref<MultiplayerPeer> multiplayer_peer = scene_multiplayer->get_multiplayer_peer(); + int peer_id = 42; + multiplayer_peer->emit_signal(SNAME("peer_connected"), peer_id); + SIGNAL_CHECK("peer_authenticating", build_array(build_array(peer_id))); + + CHECK_EQ(scene_multiplayer->send_auth(peer_id, String("It's me").to_ascii_buffer()), Error::OK); + + Vector<int> expected_peer_ids = { peer_id }; + CHECK_EQ(scene_multiplayer->get_authenticating_peer_ids(), expected_peer_ids); + + SIGNAL_UNWATCH(scene_multiplayer.ptr(), "peer_authenticating"); + } + + SUBCASE("peer_authentication_failed is emitted when a peer is deleted before authentication is completed") { + SIGNAL_WATCH(scene_multiplayer.ptr(), "peer_authentication_failed"); + + // Adding a peer to MultiplayerPeer. + Ref<MultiplayerPeer> multiplayer_peer = scene_multiplayer->get_multiplayer_peer(); + int peer_id = 42; + multiplayer_peer->emit_signal(SNAME("peer_connected"), peer_id); + multiplayer_peer->emit_signal(SNAME("peer_disconnected"), peer_id); + SIGNAL_CHECK("peer_authentication_failed", build_array(build_array(peer_id))); + + SIGNAL_UNWATCH(scene_multiplayer.ptr(), "peer_authentication_failed"); + } + + SUBCASE("peer_authentication_failed is emitted when authentication timeout") { + SIGNAL_WATCH(scene_multiplayer.ptr(), "peer_authentication_failed"); + scene_multiplayer->set_auth_timeout(0.01); + CHECK_EQ(scene_multiplayer->get_auth_timeout(), 0.01); + + // Adding two peesr to MultiplayerPeer. + Ref<MultiplayerPeer> multiplayer_peer = scene_multiplayer->get_multiplayer_peer(); + int first_peer_id = 42; + int second_peer_id = 84; + multiplayer_peer->emit_signal(SNAME("peer_connected"), first_peer_id); + multiplayer_peer->emit_signal(SNAME("peer_connected"), second_peer_id); + + // Let timeout happens. + OS::get_singleton()->delay_usec(500000); + + CHECK_EQ(scene_multiplayer->poll(), Error::OK); + + SIGNAL_CHECK("peer_authentication_failed", build_array(build_array(first_peer_id), build_array(second_peer_id))); + + SIGNAL_UNWATCH(scene_multiplayer.ptr(), "peer_authentication_failed"); + } + + SUBCASE("Fails when there is no MultiplayerPeer configured") { + scene_multiplayer->set_multiplayer_peer(nullptr); + + ERR_PRINT_OFF; + CHECK_EQ(scene_multiplayer->send_auth(42, Vector<uint8_t>()), Error::ERR_UNCONFIGURED); + ERR_PRINT_ON; + } + + SUBCASE("Fails when the peer to send the auth is not pending") { + ERR_PRINT_OFF; + CHECK_EQ(scene_multiplayer->send_auth(42, String("It's me").to_ascii_buffer()), Error::ERR_INVALID_PARAMETER); + ERR_PRINT_ON; + } +} + +TEST_CASE("[Multiplayer][SceneMultiplayer][SceneTree] Complete Authentication") { + Ref<SceneMultiplayer> scene_multiplayer; + scene_multiplayer.instantiate(); + SceneTree::get_singleton()->set_multiplayer(scene_multiplayer); + scene_multiplayer->set_auth_callback(callable_mp_static(auth_callback)); + + SUBCASE("Is properly completed") { + Ref<MultiplayerPeer> multiplayer_peer = scene_multiplayer->get_multiplayer_peer(); + int peer_id = 42; + multiplayer_peer->emit_signal(SNAME("peer_connected"), peer_id); + CHECK_EQ(scene_multiplayer->send_auth(peer_id, String("It's me").to_ascii_buffer()), Error::OK); + + CHECK_EQ(scene_multiplayer->complete_auth(peer_id), Error::OK); + } + + SUBCASE("Fails when there is no MultiplayerPeer configured") { + scene_multiplayer->set_multiplayer_peer(nullptr); + + ERR_PRINT_OFF; + CHECK_EQ(scene_multiplayer->complete_auth(42), Error::ERR_UNCONFIGURED); + ERR_PRINT_ON; + } + + SUBCASE("Fails when the peer to complete the auth is not pending") { + ERR_PRINT_OFF; + CHECK_EQ(scene_multiplayer->complete_auth(42), Error::ERR_INVALID_PARAMETER); + ERR_PRINT_ON; + } + + SUBCASE("Fails to send auth or completed for a second time") { + Ref<MultiplayerPeer> multiplayer_peer = scene_multiplayer->get_multiplayer_peer(); + int peer_id = 42; + multiplayer_peer->emit_signal(SNAME("peer_connected"), peer_id); + CHECK_EQ(scene_multiplayer->send_auth(peer_id, String("It's me").to_ascii_buffer()), Error::OK); + CHECK_EQ(scene_multiplayer->complete_auth(peer_id), Error::OK); + + ERR_PRINT_OFF; + CHECK_EQ(scene_multiplayer->send_auth(peer_id, String("It's me").to_ascii_buffer()), Error::ERR_FILE_CANT_WRITE); + CHECK_EQ(scene_multiplayer->complete_auth(peer_id), Error::ERR_FILE_CANT_WRITE); + ERR_PRINT_ON; + } +} + +} // namespace TestSceneMultiplayer + +#endif // TEST_SCENE_MULTIPLAYER_H diff --git a/modules/squish/SCsub b/modules/squish/SCsub deleted file mode 100644 index d8e7fbc142..0000000000 --- a/modules/squish/SCsub +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env python -from misc.utility.scons_hints import * - -Import("env") -Import("env_modules") - -env_squish = env_modules.Clone() - -# Thirdparty source files - -thirdparty_obj = [] - -if env["builtin_squish"]: - thirdparty_dir = "#thirdparty/squish/" - thirdparty_sources = [ - "alpha.cpp", - "clusterfit.cpp", - "colourblock.cpp", - "colourfit.cpp", - "colourset.cpp", - "maths.cpp", - "rangefit.cpp", - "singlecolourfit.cpp", - "squish.cpp", - ] - - thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] - - env_squish.Prepend(CPPPATH=[thirdparty_dir]) - - env_thirdparty = env_squish.Clone() - env_thirdparty.disable_warnings() - env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources) - env.modules_sources += thirdparty_obj - - -# Godot source files - -module_obj = [] - -env_squish.add_source_files(module_obj, "*.cpp") -env.modules_sources += module_obj - -# Needed to force rebuilding the module files when the thirdparty library is updated. -env.Depends(module_obj, thirdparty_obj) diff --git a/modules/squish/image_decompress_squish.cpp b/modules/squish/image_decompress_squish.cpp deleted file mode 100644 index 3841ba8db1..0000000000 --- a/modules/squish/image_decompress_squish.cpp +++ /dev/null @@ -1,96 +0,0 @@ -/**************************************************************************/ -/* image_decompress_squish.cpp */ -/**************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/**************************************************************************/ -/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ -/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/**************************************************************************/ - -#include "image_decompress_squish.h" - -#include <squish.h> - -void image_decompress_squish(Image *p_image) { - int w = p_image->get_width(); - int h = p_image->get_height(); - - Image::Format source_format = p_image->get_format(); - Image::Format target_format = Image::FORMAT_RGBA8; - - Vector<uint8_t> data; - int64_t target_size = Image::get_image_data_size(w, h, target_format, p_image->has_mipmaps()); - int mm_count = p_image->get_mipmap_count(); - data.resize(target_size); - - const uint8_t *rb = p_image->get_data().ptr(); - uint8_t *wb = data.ptrw(); - - int squish_flags = 0; - - switch (source_format) { - case Image::FORMAT_DXT1: - squish_flags = squish::kDxt1; - break; - - case Image::FORMAT_DXT3: - squish_flags = squish::kDxt3; - break; - - case Image::FORMAT_DXT5: - case Image::FORMAT_DXT5_RA_AS_RG: - squish_flags = squish::kDxt5; - break; - - case Image::FORMAT_RGTC_R: - squish_flags = squish::kBc4; - break; - - case Image::FORMAT_RGTC_RG: - squish_flags = squish::kBc5; - break; - - default: - ERR_FAIL_MSG("Squish: Can't decompress unknown format: " + itos(p_image->get_format()) + "."); - break; - } - - for (int i = 0; i <= mm_count; i++) { - int64_t src_ofs = 0, mipmap_size = 0; - int mipmap_w = 0, mipmap_h = 0; - p_image->get_mipmap_offset_size_and_dimensions(i, src_ofs, mipmap_size, mipmap_w, mipmap_h); - - int64_t dst_ofs = Image::get_image_mipmap_offset(p_image->get_width(), p_image->get_height(), target_format, i); - squish::DecompressImage(&wb[dst_ofs], w, h, &rb[src_ofs], squish_flags); - - w >>= 1; - h >>= 1; - } - - p_image->set_data(p_image->get_width(), p_image->get_height(), p_image->has_mipmaps(), target_format, data); - - if (source_format == Image::FORMAT_DXT5_RA_AS_RG) { - p_image->convert_ra_rgba8_to_rg(); - } -} diff --git a/modules/text_server_adv/SCsub b/modules/text_server_adv/SCsub index 304a09515c..f1dcef6667 100644 --- a/modules/text_server_adv/SCsub +++ b/modules/text_server_adv/SCsub @@ -126,6 +126,7 @@ if env["builtin_harfbuzz"]: "src/hb-ucd.cc", "src/hb-unicode.cc", # "src/hb-uniscribe.cc", + "src/OT/Var/VARC/VARC.cc", ] if freetype_enabled: diff --git a/modules/text_server_adv/gdextension_build/SConstruct b/modules/text_server_adv/gdextension_build/SConstruct index 8f4f2cba40..4c305f3b3f 100644 --- a/modules/text_server_adv/gdextension_build/SConstruct +++ b/modules/text_server_adv/gdextension_build/SConstruct @@ -387,6 +387,7 @@ thirdparty_harfbuzz_sources = [ "src/hb-ucd.cc", "src/hb-unicode.cc", # "src/hb-uniscribe.cc", + "src/OT/Var/VARC/VARC.cc", ] if env["freetype_enabled"]: diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp index 3322300dda..1c6e62650a 100644 --- a/modules/text_server_adv/text_server_adv.cpp +++ b/modules/text_server_adv/text_server_adv.cpp @@ -442,6 +442,8 @@ bool TextServerAdvanced::_load_support_data(const String &p_filename) { } #else if (!icu_data_loaded) { + UErrorCode err = U_ZERO_ERROR; +#ifdef ICU_DATA_NAME String filename = (p_filename.is_empty()) ? String("res://") + _MKSTR(ICU_DATA_NAME) : p_filename; Ref<FileAccess> f = FileAccess::open(filename, FileAccess::READ); @@ -451,13 +453,13 @@ bool TextServerAdvanced::_load_support_data(const String &p_filename) { uint64_t len = f->get_length(); icu_data = f->get_buffer(len); - UErrorCode err = U_ZERO_ERROR; udata_setCommonData(icu_data.ptr(), &err); if (U_FAILURE(err)) { ERR_FAIL_V_MSG(false, u_errorName(err)); } err = U_ZERO_ERROR; +#endif u_init(&err); if (U_FAILURE(err)) { ERR_FAIL_V_MSG(false, u_errorName(err)); @@ -1357,7 +1359,7 @@ _FORCE_INLINE_ bool TextServerAdvanced::_ensure_glyph(FontAdvanced *p_font_data, return false; } -_FORCE_INLINE_ bool TextServerAdvanced::_ensure_cache_for_size(FontAdvanced *p_font_data, const Vector2i &p_size, FontForSizeAdvanced *&r_cache_for_size) const { +_FORCE_INLINE_ bool TextServerAdvanced::_ensure_cache_for_size(FontAdvanced *p_font_data, const Vector2i &p_size, FontForSizeAdvanced *&r_cache_for_size, bool p_silent) const { ERR_FAIL_COND_V(p_size.x <= 0, false); HashMap<Vector2i, FontForSizeAdvanced *>::Iterator E = p_font_data->cache.find(p_size); @@ -1378,7 +1380,11 @@ _FORCE_INLINE_ bool TextServerAdvanced::_ensure_cache_for_size(FontAdvanced *p_f error = FT_Init_FreeType(&ft_library); if (error != 0) { memdelete(fd); - ERR_FAIL_V_MSG(false, "FreeType: Error initializing library: '" + String(FT_Error_String(error)) + "'."); + if (p_silent) { + return false; + } else { + ERR_FAIL_V_MSG(false, "FreeType: Error initializing library: '" + String(FT_Error_String(error)) + "'."); + } } #ifdef MODULE_SVG_ENABLED FT_Property_Set(ft_library, "ot-svg", "svg-hooks", get_tvg_svg_in_ot_hooks()); @@ -1412,7 +1418,11 @@ _FORCE_INLINE_ bool TextServerAdvanced::_ensure_cache_for_size(FontAdvanced *p_f FT_Done_Face(fd->face); fd->face = nullptr; memdelete(fd); - ERR_FAIL_V_MSG(false, "FreeType: Error loading font: '" + String(FT_Error_String(error)) + "'."); + if (p_silent) { + return false; + } else { + ERR_FAIL_V_MSG(false, "FreeType: Error loading font: '" + String(FT_Error_String(error)) + "'."); + } } } @@ -1847,7 +1857,11 @@ _FORCE_INLINE_ bool TextServerAdvanced::_ensure_cache_for_size(FontAdvanced *p_f } #else memdelete(fd); - ERR_FAIL_V_MSG(false, "FreeType: Can't load dynamic font, engine is compiled without FreeType support!"); + if (p_silent) { + return false; + } else { + ERR_FAIL_V_MSG(false, "FreeType: Can't load dynamic font, engine is compiled without FreeType support!"); + } #endif } else { // Init bitmap font. @@ -1858,6 +1872,16 @@ _FORCE_INLINE_ bool TextServerAdvanced::_ensure_cache_for_size(FontAdvanced *p_f return true; } +_FORCE_INLINE_ bool TextServerAdvanced::_font_validate(const RID &p_font_rid) const { + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, false); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size(fd, 16); + FontForSizeAdvanced *ffsd = nullptr; + return _ensure_cache_for_size(fd, size, ffsd, true); +} + _FORCE_INLINE_ void TextServerAdvanced::_font_clear_cache(FontAdvanced *p_font_data) { MutexLock ftlock(ft_mutex); @@ -5106,6 +5130,10 @@ RID TextServerAdvanced::_find_sys_font_for_text(const RID &p_fdef, const String SystemFontCacheRec sysf; sysf.rid = _create_font(); _font_set_data_ptr(sysf.rid, font_data.ptr(), font_data.size()); + if (!_font_validate(sysf.rid)) { + _free_rid(sysf.rid); + continue; + } Dictionary var = dvar; // Select matching style from collection. diff --git a/modules/text_server_adv/text_server_adv.h b/modules/text_server_adv/text_server_adv.h index c63389b1c6..9c8d75b358 100644 --- a/modules/text_server_adv/text_server_adv.h +++ b/modules/text_server_adv/text_server_adv.h @@ -365,7 +365,8 @@ class TextServerAdvanced : public TextServerExtension { _FORCE_INLINE_ FontGlyph rasterize_bitmap(FontForSizeAdvanced *p_data, int p_rect_margin, FT_Bitmap p_bitmap, int p_yofs, int p_xofs, const Vector2 &p_advance, bool p_bgra) const; #endif _FORCE_INLINE_ bool _ensure_glyph(FontAdvanced *p_font_data, const Vector2i &p_size, int32_t p_glyph, FontGlyph &r_glyph) const; - _FORCE_INLINE_ bool _ensure_cache_for_size(FontAdvanced *p_font_data, const Vector2i &p_size, FontForSizeAdvanced *&r_cache_for_size) const; + _FORCE_INLINE_ bool _ensure_cache_for_size(FontAdvanced *p_font_data, const Vector2i &p_size, FontForSizeAdvanced *&r_cache_for_size, bool p_silent = false) const; + _FORCE_INLINE_ bool _font_validate(const RID &p_font_rid) const; _FORCE_INLINE_ void _font_clear_cache(FontAdvanced *p_font_data); static void _generateMTSDF_threaded(void *p_td, uint32_t p_y); diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp index 540ba19cac..ce95622f09 100644 --- a/modules/text_server_fb/text_server_fb.cpp +++ b/modules/text_server_fb/text_server_fb.cpp @@ -791,7 +791,7 @@ _FORCE_INLINE_ bool TextServerFallback::_ensure_glyph(FontFallback *p_font_data, return false; } -_FORCE_INLINE_ bool TextServerFallback::_ensure_cache_for_size(FontFallback *p_font_data, const Vector2i &p_size, FontForSizeFallback *&r_cache_for_size) const { +_FORCE_INLINE_ bool TextServerFallback::_ensure_cache_for_size(FontFallback *p_font_data, const Vector2i &p_size, FontForSizeFallback *&r_cache_for_size, bool p_silent) const { ERR_FAIL_COND_V(p_size.x <= 0, false); HashMap<Vector2i, FontForSizeFallback *>::Iterator E = p_font_data->cache.find(p_size); @@ -813,7 +813,11 @@ _FORCE_INLINE_ bool TextServerFallback::_ensure_cache_for_size(FontFallback *p_f error = FT_Init_FreeType(&ft_library); if (error != 0) { memdelete(fd); - ERR_FAIL_V_MSG(false, "FreeType: Error initializing library: '" + String(FT_Error_String(error)) + "'."); + if (p_silent) { + return false; + } else { + ERR_FAIL_V_MSG(false, "FreeType: Error initializing library: '" + String(FT_Error_String(error)) + "'."); + } } #ifdef MODULE_SVG_ENABLED FT_Property_Set(ft_library, "ot-svg", "svg-hooks", get_tvg_svg_in_ot_hooks()); @@ -847,7 +851,11 @@ _FORCE_INLINE_ bool TextServerFallback::_ensure_cache_for_size(FontFallback *p_f FT_Done_Face(fd->face); fd->face = nullptr; memdelete(fd); - ERR_FAIL_V_MSG(false, "FreeType: Error loading font: '" + String(FT_Error_String(error)) + "'."); + if (p_silent) { + return false; + } else { + ERR_FAIL_V_MSG(false, "FreeType: Error loading font: '" + String(FT_Error_String(error)) + "'."); + } } } @@ -980,7 +988,11 @@ _FORCE_INLINE_ bool TextServerFallback::_ensure_cache_for_size(FontFallback *p_f } #else memdelete(fd); - ERR_FAIL_V_MSG(false, "FreeType: Can't load dynamic font, engine is compiled without FreeType support!"); + if (p_silent) { + return false; + } else { + ERR_FAIL_V_MSG(false, "FreeType: Can't load dynamic font, engine is compiled without FreeType support!"); + } #endif } @@ -989,6 +1001,16 @@ _FORCE_INLINE_ bool TextServerFallback::_ensure_cache_for_size(FontFallback *p_f return true; } +_FORCE_INLINE_ bool TextServerFallback::_font_validate(const RID &p_font_rid) const { + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, false); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size(fd, 16); + FontForSizeFallback *ffsd = nullptr; + return _ensure_cache_for_size(fd, size, ffsd, true); +} + _FORCE_INLINE_ void TextServerFallback::_font_clear_cache(FontFallback *p_font_data) { MutexLock ftlock(ft_mutex); @@ -3920,6 +3942,10 @@ RID TextServerFallback::_find_sys_font_for_text(const RID &p_fdef, const String SystemFontCacheRec sysf; sysf.rid = _create_font(); _font_set_data_ptr(sysf.rid, font_data.ptr(), font_data.size()); + if (!_font_validate(sysf.rid)) { + _free_rid(sysf.rid); + continue; + } Dictionary var = dvar; // Select matching style from collection. diff --git a/modules/text_server_fb/text_server_fb.h b/modules/text_server_fb/text_server_fb.h index 7f12ad593b..56626c1f6c 100644 --- a/modules/text_server_fb/text_server_fb.h +++ b/modules/text_server_fb/text_server_fb.h @@ -314,7 +314,8 @@ class TextServerFallback : public TextServerExtension { _FORCE_INLINE_ FontGlyph rasterize_bitmap(FontForSizeFallback *p_data, int p_rect_margin, FT_Bitmap p_bitmap, int p_yofs, int p_xofs, const Vector2 &p_advance, bool p_bgra) const; #endif _FORCE_INLINE_ bool _ensure_glyph(FontFallback *p_font_data, const Vector2i &p_size, int32_t p_glyph, FontGlyph &r_glyph) const; - _FORCE_INLINE_ bool _ensure_cache_for_size(FontFallback *p_font_data, const Vector2i &p_size, FontForSizeFallback *&r_cache_for_size) const; + _FORCE_INLINE_ bool _ensure_cache_for_size(FontFallback *p_font_data, const Vector2i &p_size, FontForSizeFallback *&r_cache_for_size, bool p_silent = false) const; + _FORCE_INLINE_ bool _font_validate(const RID &p_font_rid) const; _FORCE_INLINE_ void _font_clear_cache(FontFallback *p_font_data); static void _generateMTSDF_threaded(void *p_td, uint32_t p_y); diff --git a/modules/vorbis/resource_importer_ogg_vorbis.cpp b/modules/vorbis/resource_importer_ogg_vorbis.cpp index 7b8d14741b..729a6f5561 100644 --- a/modules/vorbis/resource_importer_ogg_vorbis.cpp +++ b/modules/vorbis/resource_importer_ogg_vorbis.cpp @@ -155,7 +155,6 @@ Ref<AudioStreamOggVorbis> ResourceImporterOggVorbis::load_from_buffer(const Vect char *sync_buf = ogg_sync_buffer(&sync_state, OGG_SYNC_BUFFER_SIZE); err = ogg_sync_check(&sync_state); ERR_FAIL_COND_V_MSG(err != 0, Ref<AudioStreamOggVorbis>(), "Ogg sync error " + itos(err)); - ERR_FAIL_COND_V(cursor > size_t(file_data.size()), Ref<AudioStreamOggVorbis>()); size_t copy_size = file_data.size() - cursor; if (copy_size > OGG_SYNC_BUFFER_SIZE) { copy_size = OGG_SYNC_BUFFER_SIZE; diff --git a/modules/websocket/editor/editor_debugger_server_websocket.cpp b/modules/websocket/editor/editor_debugger_server_websocket.cpp index a28fc53440..344a0356c5 100644 --- a/modules/websocket/editor/editor_debugger_server_websocket.cpp +++ b/modules/websocket/editor/editor_debugger_server_websocket.cpp @@ -77,8 +77,8 @@ Error EditorDebuggerServerWebSocket::start(const String &p_uri) { // Optionally override if (!p_uri.is_empty() && p_uri != "ws://") { - String scheme, path; - Error err = p_uri.parse_url(scheme, bind_host, bind_port, path); + String scheme, path, fragment; + Error err = p_uri.parse_url(scheme, bind_host, bind_port, path, fragment); ERR_FAIL_COND_V(err != OK, ERR_INVALID_PARAMETER); ERR_FAIL_COND_V(!bind_host.is_valid_ip_address() && bind_host != "*", ERR_INVALID_PARAMETER); } diff --git a/modules/websocket/emws_peer.cpp b/modules/websocket/emws_peer.cpp index 03a530909b..c5768c9f0b 100644 --- a/modules/websocket/emws_peer.cpp +++ b/modules/websocket/emws_peer.cpp @@ -68,8 +68,9 @@ Error EMWSPeer::connect_to_url(const String &p_url, Ref<TLSOptions> p_tls_option String host; String path; String scheme; + String fragment; int port = 0; - Error err = p_url.parse_url(scheme, host, port, path); + Error err = p_url.parse_url(scheme, host, port, path, fragment); ERR_FAIL_COND_V_MSG(err != OK, err, "Invalid URL: " + p_url); if (scheme.is_empty()) { diff --git a/modules/websocket/wsl_peer.cpp b/modules/websocket/wsl_peer.cpp index 0a9a4053e3..0c0a046805 100644 --- a/modules/websocket/wsl_peer.cpp +++ b/modules/websocket/wsl_peer.cpp @@ -482,8 +482,9 @@ Error WSLPeer::connect_to_url(const String &p_url, Ref<TLSOptions> p_options) { String host; String path; String scheme; + String fragment; int port = 0; - Error err = p_url.parse_url(scheme, host, port, path); + Error err = p_url.parse_url(scheme, host, port, path, fragment); ERR_FAIL_COND_V_MSG(err != OK, err, "Invalid URL: " + p_url); if (scheme.is_empty()) { scheme = "ws://"; diff --git a/modules/webxr/doc_classes/WebXRInterface.xml b/modules/webxr/doc_classes/WebXRInterface.xml index bd7192520a..829279e7bb 100644 --- a/modules/webxr/doc_classes/WebXRInterface.xml +++ b/modules/webxr/doc_classes/WebXRInterface.xml @@ -283,7 +283,7 @@ </signals> <constants> <constant name="TARGET_RAY_MODE_UNKNOWN" value="0" enum="TargetRayMode"> - We don't know the the target ray mode. + We don't know the target ray mode. </constant> <constant name="TARGET_RAY_MODE_GAZE" value="1" enum="TargetRayMode"> Target ray originates at the viewer's eyes and points in the direction they are looking. |