diff options
54 files changed, 435 insertions, 152 deletions
diff --git a/core/io/resource_loader.cpp b/core/io/resource_loader.cpp index 2b5e83264e..c5582ad231 100644 --- a/core/io/resource_loader.cpp +++ b/core/io/resource_loader.cpp @@ -408,7 +408,10 @@ void ResourceLoader::_thread_load_function(void *p_userdata) { MessageQueue::set_thread_singleton_override(nullptr); memdelete(own_mq_override); } - memdelete(load_paths_stack); + if (load_paths_stack) { + memdelete(load_paths_stack); + load_paths_stack = nullptr; + } } } @@ -1304,7 +1307,7 @@ bool ResourceLoader::timestamp_on_load = false; thread_local int ResourceLoader::load_nesting = 0; thread_local WorkerThreadPool::TaskID ResourceLoader::caller_task_id = 0; -thread_local Vector<String> *ResourceLoader::load_paths_stack; +thread_local Vector<String> *ResourceLoader::load_paths_stack = nullptr; thread_local HashMap<int, HashMap<String, Ref<Resource>>> ResourceLoader::res_ref_overrides; template <> diff --git a/doc/classes/AnimationNodeAnimation.xml b/doc/classes/AnimationNodeAnimation.xml index 0c85e8e670..70c3e5a26e 100644 --- a/doc/classes/AnimationNodeAnimation.xml +++ b/doc/classes/AnimationNodeAnimation.xml @@ -17,6 +17,7 @@ </member> <member name="loop_mode" type="int" setter="set_loop_mode" getter="get_loop_mode" enum="Animation.LoopMode"> If [member use_custom_timeline] is [code]true[/code], override the loop settings of the original [Animation] resource with the value. + [b]Note:[/b] If the [member Animation.loop_mode] isn't set to looping, the [method Animation.track_set_interpolation_loop_wrap] option will not be respected. If you cannot get the expected behavior, consider duplicating the [Animation] resource and changing the loop settings. </member> <member name="play_mode" type="int" setter="set_play_mode" getter="get_play_mode" enum="AnimationNodeAnimation.PlayMode" default="0"> Determines the playback direction of the animation. diff --git a/doc/classes/AudioStreamPlayer.xml b/doc/classes/AudioStreamPlayer.xml index 7b4b7c289b..eecbb05540 100644 --- a/doc/classes/AudioStreamPlayer.xml +++ b/doc/classes/AudioStreamPlayer.xml @@ -5,7 +5,7 @@ </brief_description> <description> The [AudioStreamPlayer] node plays an audio stream non-positionally. It is ideal for user interfaces, menus, or background music. - To use this node, [member stream] needs to be set to a valid [AudioStream] resource. Playing more than one sound at the time is also supported, see [member max_polyphony]. + To use this node, [member stream] needs to be set to a valid [AudioStream] resource. Playing more than one sound at the same time is also supported, see [member max_polyphony]. If you need to play audio at a specific position, use [AudioStreamPlayer2D] or [AudioStreamPlayer3D] instead. </description> <tutorials> diff --git a/doc/classes/Dictionary.xml b/doc/classes/Dictionary.xml index cb59afd880..b8b4fc7b08 100644 --- a/doc/classes/Dictionary.xml +++ b/doc/classes/Dictionary.xml @@ -355,7 +355,6 @@ # Prints { "fruit": "apple", "vegetable": "potato", "dressing": "vinegar" } print(extra.merged(base, true)) [/codeblock] - See also [method merge]. </description> </method> <method name="recursive_equal" qualifiers="const"> diff --git a/doc/classes/EditorPlugin.xml b/doc/classes/EditorPlugin.xml index c3ea440aea..9c1a6f6af6 100644 --- a/doc/classes/EditorPlugin.xml +++ b/doc/classes/EditorPlugin.xml @@ -60,7 +60,7 @@ [gdscript] func _forward_3d_draw_over_viewport(overlay): # Draw a circle at cursor position. - overlay.draw_circle(overlay.get_local_mouse_position(), 64) + overlay.draw_circle(overlay.get_local_mouse_position(), 64, Color.WHITE) func _forward_3d_gui_input(camera, event): if event is InputEventMouseMotion: diff --git a/doc/classes/EditorSettings.xml b/doc/classes/EditorSettings.xml index d63c71a351..4d82a32dae 100644 --- a/doc/classes/EditorSettings.xml +++ b/doc/classes/EditorSettings.xml @@ -719,7 +719,8 @@ [b]Note:[/b] If the editor was started with the [code]--debug-canvas-item-redraw[/code] [url=$DOCS_URL/tutorials/editor/command_line_tutorial.html]command line argument[/url], the update spinner will [i]never[/i] display regardless of this setting's value. This is to avoid confusion with what would cause redrawing in real world scenarios. </member> <member name="interface/editor/single_window_mode" type="bool" setter="" getter=""> - If [code]true[/code], embed modal windows such as docks inside the main editor window. When single-window mode is enabled, tooltips will also be embedded inside the main editor window, which means they can't be displayed outside of the editor window. + If [code]true[/code], embed modal windows such as docks inside the main editor window. When single-window mode is enabled, tooltips will also be embedded inside the main editor window, which means they can't be displayed outside of the editor window. Single-window mode can be faster as it does not need to create a separate window for every popup and tooltip, which can be a slow operation depending on the operating system and rendering method in use. + This is equivalent to [member ProjectSettings.display/window/subwindows/embed_subwindows] in the running project, except the setting's value is inverted. [b]Note:[/b] To query whether the editor can use multiple windows in an editor plugin, use [method EditorInterface.is_multi_window_enabled] instead of querying the value of this editor setting. </member> <member name="interface/editor/ui_layout_direction" type="int" setter="" getter=""> diff --git a/doc/classes/JSON.xml b/doc/classes/JSON.xml index b73ca4a1a6..d97a68cf2e 100644 --- a/doc/classes/JSON.xml +++ b/doc/classes/JSON.xml @@ -4,7 +4,7 @@ Helper class for creating and parsing JSON data. </brief_description> <description> - The [JSON] enables all data types to be converted to and from a JSON string. This useful for serializing data to save to a file or send over the network. + The [JSON] class enables all data types to be converted to and from a JSON string. This is useful for serializing data, e.g. to save to a file or send over the network. [method stringify] is used to convert any data type into a JSON string. [method parse] is used to convert any existing JSON data into a [Variant] that can be used within Godot. If successfully parsed, use [member data] to retrieve the [Variant], and use [code]typeof[/code] to check if the Variant's type is what you expect. JSON Objects are converted into a [Dictionary], but JSON data can be used to store [Array]s, numbers, [String]s and even just a boolean. [b]Example[/b] @@ -25,7 +25,7 @@ else: print("JSON Parse Error: ", json.get_error_message(), " in ", json_string, " at line ", json.get_error_line()) [/codeblock] - Alternatively, you can parse string using the static [method parse_string] method, but it doesn't allow to handle errors. + Alternatively, you can parse strings using the static [method parse_string] method, but it doesn't handle errors. [codeblock] var data = JSON.parse_string(json_string) # Returns null if parsing failed. [/codeblock] @@ -53,7 +53,7 @@ <method name="get_parsed_text" qualifiers="const"> <return type="String" /> <description> - Return the text parsed by [method parse] as long as the function is instructed to keep it. + Return the text parsed by [method parse] (requires passing [code]keep_text[/code] to [method parse]). </description> </method> <method name="parse"> @@ -62,7 +62,7 @@ <param index="1" name="keep_text" type="bool" default="false" /> <description> Attempts to parse the [param json_text] provided. - Returns an [enum Error]. If the parse was successful, it returns [constant OK] and the result can be retrieved using [member data]. If unsuccessful, use [method get_error_line] and [method get_error_message] for identifying the source of the failure. + Returns an [enum Error]. If the parse was successful, it returns [constant OK] and the result can be retrieved using [member data]. If unsuccessful, use [method get_error_line] and [method get_error_message] to identify the source of the failure. Non-static variant of [method parse_string], if you want custom error handling. The optional [param keep_text] argument instructs the parser to keep a copy of the original text. This text can be obtained later by using the [method get_parsed_text] function and is used when saving the resource (instead of generating new text from [member data]). </description> @@ -84,7 +84,7 @@ Converts a [Variant] var to JSON text and returns the result. Useful for serializing data to store or send over the network. [b]Note:[/b] The JSON specification does not define integer or float types, but only a [i]number[/i] type. Therefore, converting a Variant to JSON text will convert all numerical values to [float] types. [b]Note:[/b] If [param full_precision] is [code]true[/code], when stringifying floats, the unreliable digits are stringified in addition to the reliable digits to guarantee exact decoding. - The [param indent] parameter controls if and how something is indented, the string used for this parameter will be used where there should be an indent in the output, even spaces like [code]" "[/code] will work. [code]\t[/code] and [code]\n[/code] can also be used for a tab indent, or to make a newline for each indent respectively. + The [param indent] parameter controls if and how something is indented; its contents will be used where there should be an indent in the output. Even spaces like [code]" "[/code] will work. [code]\t[/code] and [code]\n[/code] can also be used for a tab indent, or to make a newline for each indent respectively. [b]Example output:[/b] [codeblock] ## JSON.stringify(my_dictionary) diff --git a/doc/classes/MultiMesh.xml b/doc/classes/MultiMesh.xml index 406aacc75c..529912171c 100644 --- a/doc/classes/MultiMesh.xml +++ b/doc/classes/MultiMesh.xml @@ -57,6 +57,7 @@ <param index="1" name="color" type="Color" /> <description> Sets the color of a specific instance by [i]multiplying[/i] the mesh's existing vertex colors. This allows for different color tinting per instance. + [b]Note:[/b] Each component is stored in 32 bits in the Forward+ and Mobile rendering methods, but is packed into 16 bits in the Compatibility rendering method. For the color to take effect, ensure that [member use_colors] is [code]true[/code] on the [MultiMesh] and [member BaseMaterial3D.vertex_color_use_as_albedo] is [code]true[/code] on the material. If you intend to set an absolute color instead of tinting, make sure the material's albedo color is set to pure white ([code]Color(1, 1, 1)[/code]). </description> </method> @@ -66,6 +67,7 @@ <param index="1" name="custom_data" type="Color" /> <description> Sets custom data for a specific instance. [param custom_data] is a [Color] type only to contain 4 floating-point numbers. + [b]Note:[/b] Each number is stored in 32 bits in the Forward+ and Mobile rendering methods, but is packed into 16 bits in the Compatibility rendering method. For the custom data to be used, ensure that [member use_custom_data] is [code]true[/code]. This custom instance data has to be manually accessed in your custom shader using [code]INSTANCE_CUSTOM[/code]. </description> diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index 22c25dd5e5..b20b374382 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -913,7 +913,9 @@ [b]Note:[/b] When using integer scaling with a stretch mode, resizing the window to be smaller than the base viewport size will clip the contents. Consider preventing that by setting [member Window.min_size] to the same value as the base viewport size defined in [member display/window/size/viewport_width] and [member display/window/size/viewport_height]. </member> <member name="display/window/subwindows/embed_subwindows" type="bool" setter="" getter="" default="true"> - If [code]true[/code] subwindows are embedded in the main window. + If [code]true[/code], subwindows are embedded in the main window (this is also called single-window mode). Single-window mode can be faster as it does not need to create a separate window for every popup and tooltip, which can be a slow operation depending on the operating system and rendering method in use. + If [code]false[/code], subwindows are created as separate windows (this is also called multi-window mode). This allows them to be moved outside the main window and use native operating system window decorations. + This is equivalent to [member EditorSettings.interface/editor/single_window_mode] in the editor, except the setting's value is inverted. </member> <member name="display/window/vsync/vsync_mode" type="int" setter="" getter="" default="1"> Sets the V-Sync mode for the main game window. The editor's own V-Sync mode can be set using [member EditorSettings.interface/editor/vsync_mode]. diff --git a/doc/classes/ResourceImporterTexture.xml b/doc/classes/ResourceImporterTexture.xml index 678aa2101c..0761702aa1 100644 --- a/doc/classes/ResourceImporterTexture.xml +++ b/doc/classes/ResourceImporterTexture.xml @@ -84,8 +84,8 @@ </member> <member name="process/premult_alpha" type="bool" setter="" getter="" default="false"> An alternative to fixing darkened borders with [member process/fix_alpha_border] is to use premultiplied alpha. By enabling this option, the texture will be converted to this format. A premultiplied alpha texture requires specific materials to be displayed correctly: - - In 2D, a [CanvasItemMaterial] will need to be created and configured to use the [constant CanvasItemMaterial.BLEND_MODE_PREMULT_ALPHA] blend mode on [CanvasItem]s that use this texture. - - In 3D, there is no support for premultiplied alpha blend mode yet, so this option is only suited for 2D. + - In 2D, a [CanvasItemMaterial] will need to be created and configured to use the [constant CanvasItemMaterial.BLEND_MODE_PREMULT_ALPHA] blend mode on [CanvasItem]s that use this texture. In custom [code]@canvas_item[/code] shaders, [code]render_mode blend_premul_alpha;[/code] should be used. + - In 3D, a [BaseMaterial3D] will need to be created and configured to use the [constant BaseMaterial3D.BLEND_MODE_PREMULT_ALPHA] blend mode on materials that use this texture. In custom [code]spatial[/code] shaders, [code]render_mode blend_premul_alpha;[/code] should be used. </member> <member name="process/size_limit" type="int" setter="" getter="" default="0"> If set to a value greater than [code]0[/code], the size of the texture is limited on import to a value smaller than or equal to the value specified here. For non-square textures, the size limit affects the longer dimension, with the shorter dimension scaled to preserve aspect ratio. Resizing is performed using cubic interpolation. diff --git a/doc/classes/SkeletonModifier3D.xml b/doc/classes/SkeletonModifier3D.xml index 620eed9b70..cb51ab6f89 100644 --- a/doc/classes/SkeletonModifier3D.xml +++ b/doc/classes/SkeletonModifier3D.xml @@ -6,7 +6,7 @@ <description> [SkeletonModifier3D] retrieves a target [Skeleton3D] by having a [Skeleton3D] parent. If there is [AnimationMixer], modification always performs after playback process of the [AnimationMixer]. - This node should be used to implement custom IK solvers, constraints, or skeleton physics + This node should be used to implement custom IK solvers, constraints, or skeleton physics. </description> <tutorials> </tutorials> diff --git a/doc/classes/TileMap.xml b/doc/classes/TileMap.xml index 3f70810a7f..d3197efc6b 100644 --- a/doc/classes/TileMap.xml +++ b/doc/classes/TileMap.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="TileMap" inherits="Node2D" deprecated="Use multiple [TileMapLayer] nodes instead." keywords="gridmap" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> +<class name="TileMap" inherits="Node2D" deprecated="Use multiple [TileMapLayer] nodes instead. To convert a TileMap to a set of TileMapLayer nodes, open the TileMap bottom panel with the node selected, click the toolbox icon in the top-right corner and choose 'Extract TileMap layers as individual TileMapLayer nodes'." keywords="gridmap" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> Node for 2D tile-based maps. </brief_description> diff --git a/doc/classes/Window.xml b/doc/classes/Window.xml index 286b35d642..ca155881c8 100644 --- a/doc/classes/Window.xml +++ b/doc/classes/Window.xml @@ -370,6 +370,9 @@ <param index="0" name="rect" type="Rect2i" default="Rect2i(0, 0, 0, 0)" /> <description> Shows the [Window] and makes it transient (see [member transient]). If [param rect] is provided, it will be set as the [Window]'s size. Fails if called on the main window. + If [member ProjectSettings.display/window/subwindows/embed_subwindows] is [code]true[/code] (single-window mode), [param rect]'s coordinates are global and relative to the main window's top-left corner (excluding window decorations). If [param rect]'s position coordinates are negative, the window will be located outside the main window and may not be visible as a result. + If [member ProjectSettings.display/window/subwindows/embed_subwindows] is [code]false[/code] (multi-window mode), [param rect]'s coordinates are global and relative to the top-left corner of the leftmost screen. If [param rect]'s position coordinates are negative, the window will be placed at the top-left corner of the screen. + [b]Note:[/b] [param rect] must be in global coordinates if specified. </description> </method> <method name="popup_centered"> @@ -380,7 +383,7 @@ [b]Note:[/b] Calling it with the default value of [param minsize] is equivalent to calling it with [member size]. </description> </method> - <method name="popup_centered_clamped"> + <method name="popup_centered_clamped" keywords="minsize"> <return type="void" /> <param index="0" name="minsize" type="Vector2i" default="Vector2i(0, 0)" /> <param index="1" name="fallback_ratio" type="float" default="0.75" /> @@ -415,7 +418,7 @@ See also [method set_unparent_when_invisible] and [method Node.get_last_exclusive_window]. </description> </method> - <method name="popup_exclusive_centered_clamped"> + <method name="popup_exclusive_centered_clamped" keywords="minsize"> <return type="void" /> <param index="0" name="from_node" type="Node" /> <param index="1" name="minsize" type="Vector2i" default="Vector2i(0, 0)" /> diff --git a/drivers/d3d12/rendering_device_driver_d3d12.cpp b/drivers/d3d12/rendering_device_driver_d3d12.cpp index 122585e595..a33fc977c6 100644 --- a/drivers/d3d12/rendering_device_driver_d3d12.cpp +++ b/drivers/d3d12/rendering_device_driver_d3d12.cpp @@ -5057,6 +5057,7 @@ void RenderingDeviceDriverD3D12::command_begin_render_pass(CommandBufferID p_cmd if (pass_info->attachments[i].load_op == ATTACHMENT_LOAD_OP_CLEAR) { clear.aspect.set_flag(TEXTURE_ASPECT_COLOR_BIT); clear.color_attachment = i; + tex_info->pending_clear.remove_from_list(); } } else if ((tex_info->desc.Flags & D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL)) { if (pass_info->attachments[i].stencil_load_op == ATTACHMENT_LOAD_OP_CLEAR) { diff --git a/editor/editor_data.cpp b/editor/editor_data.cpp index 11fea8b728..80c4c49c87 100644 --- a/editor/editor_data.cpp +++ b/editor/editor_data.cpp @@ -141,6 +141,16 @@ void EditorSelectionHistory::add_object(ObjectID p_object, const String &p_prope current_elem_idx++; } +void EditorSelectionHistory::replace_object(ObjectID p_old_object, ObjectID p_new_object) { + for (HistoryElement &element : history) { + for (int index = 0; index < element.path.size(); index++) { + if (element.path[index].object == p_old_object) { + element.path.write[index].object = p_new_object; + } + } + } +} + int EditorSelectionHistory::get_history_len() { return history.size(); } diff --git a/editor/editor_data.h b/editor/editor_data.h index 42b2d2ed0c..524c93807b 100644 --- a/editor/editor_data.h +++ b/editor/editor_data.h @@ -74,6 +74,7 @@ public: // Adds an object to the selection history. A property name can be passed if the target is a subresource of the given object. // If the object should not change the main screen plugin, it can be set as inspector only. void add_object(ObjectID p_object, const String &p_property = String(), bool p_inspector_only = false); + void replace_object(ObjectID p_old_object, ObjectID p_new_object); int get_history_len(); int get_history_pos(); diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 8b6d316dd1..1256b1d7ce 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -4446,6 +4446,21 @@ void EditorNode::update_reimported_diff_data_for_additional_nodes( } } +void EditorNode::replace_history_reimported_nodes(Node *p_original_root_node, Node *p_new_root_node, Node *p_node) { + NodePath scene_path_to_node = p_original_root_node->get_path_to(p_node); + Node *new_node = p_new_root_node->get_node_or_null(scene_path_to_node); + if (new_node) { + editor_history.replace_object(p_node->get_instance_id(), new_node->get_instance_id()); + } else { + editor_history.replace_object(p_node->get_instance_id(), ObjectID()); + } + + for (int i = 0; i < p_node->get_child_count(); i++) { + Node *child = p_node->get_child(i); + replace_history_reimported_nodes(p_original_root_node, p_new_root_node, child); + } +} + void EditorNode::open_request(const String &p_path) { if (!opening_prev) { List<String>::Element *prev_scene_item = previous_scenes.find(p_path); @@ -6094,6 +6109,13 @@ void EditorNode::reload_instances_with_path_in_edited_scenes(const String &p_ins owned_node->set_owner(nullptr); } + // Replace the old nodes in the history with the new ones. + // Otherwise, the history will contain old nodes, and some could still be + // instantiated if used elsewhere, causing the "current edited item" to be + // linked to a node that will be destroyed later. This caused the editor to + // crash when reimporting scenes with animations when "Editable children" was enabled. + replace_history_reimported_nodes(original_node, instantiated_node, original_node); + // Delete all the remaining node children. while (original_node->get_child_count()) { Node *child = original_node->get_child(0); diff --git a/editor/editor_node.h b/editor/editor_node.h index 4d55eaf1b2..5719e671b4 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -843,6 +843,8 @@ public: List<AdditiveNodeEntry> &p_addition_list); bool is_additional_node_in_scene(Node *p_edited_scene, Node *p_reimported_root, Node *p_node); + void replace_history_reimported_nodes(Node *p_original_root_node, Node *p_new_root_node, Node *p_node); + bool is_scene_open(const String &p_path); bool is_multi_window_enabled() const; diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp index 351adc569c..2da9d66d9a 100644 --- a/editor/plugins/visual_shader_editor_plugin.cpp +++ b/editor/plugins/visual_shader_editor_plugin.cpp @@ -1096,14 +1096,14 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id, bool default_value = vsnode->get_input_port_default_value(j); } - Button *button = memnew(Button); - hb->add_child(button); - register_default_input_button(p_id, j, button); - button->connect(SceneStringName(pressed), callable_mp(editor, &VisualShaderEditor::_edit_port_default_input).bind(button, p_id, j)); + Button *default_input_btn = memnew(Button); + hb->add_child(default_input_btn); + register_default_input_button(p_id, j, default_input_btn); + default_input_btn->connect(SceneStringName(pressed), callable_mp(editor, &VisualShaderEditor::_edit_port_default_input).bind(default_input_btn, p_id, j)); if (default_value.get_type() != Variant::NIL) { // only a label set_input_port_default_value(p_type, p_id, j, default_value); } else { - button->hide(); + default_input_btn->hide(); } if (j == 0 && custom_editor) { @@ -1144,7 +1144,7 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id, bool Label *label = memnew(Label); label->set_auto_translate_mode(Node::AUTO_TRANSLATE_MODE_DISABLED); // TODO: Implement proper translation switch. label->set_text(name_left); - label->add_theme_style_override(CoreStringName(normal), editor->get_theme_stylebox(SNAME("label_style"), SNAME("VShaderEditor"))); //more compact + label->add_theme_style_override(CoreStringName(normal), editor->get_theme_stylebox(SNAME("label_style"), SNAME("VShaderEditor"))); hb->add_child(label); if (vsnode->is_input_port_default(j, mode) && !port_left_used) { diff --git a/editor/project_manager/project_dialog.cpp b/editor/project_manager/project_dialog.cpp index 52d86a1a95..b4aa00ee0a 100644 --- a/editor/project_manager/project_dialog.cpp +++ b/editor/project_manager/project_dialog.cpp @@ -351,15 +351,19 @@ void ProjectDialog::_install_path_changed() { } void ProjectDialog::_browse_project_path() { + String path = project_path->get_text(); + if (path.is_empty()) { + path = EDITOR_GET("filesystem/directories/default_project_path"); + } if (mode == MODE_IMPORT && install_path->is_visible_in_tree()) { // Select last ZIP file. - fdialog_project->set_current_path(project_path->get_text()); + fdialog_project->set_current_path(path); } else if ((mode == MODE_NEW || mode == MODE_INSTALL) && create_dir->is_pressed()) { // Select parent directory of project path. - fdialog_project->set_current_dir(project_path->get_text().get_base_dir()); + fdialog_project->set_current_dir(path.get_base_dir()); } else { // Select project path. - fdialog_project->set_current_dir(project_path->get_text()); + fdialog_project->set_current_dir(path); } if (mode == MODE_IMPORT) { @@ -370,6 +374,8 @@ void ProjectDialog::_browse_project_path() { } else { fdialog_project->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_DIR); } + + hide(); fdialog_project->popup_file_dialog(); } @@ -389,7 +395,7 @@ void ProjectDialog::_browse_install_path() { } void ProjectDialog::_project_path_selected(const String &p_path) { - show_dialog(); + show_dialog(false); if (create_dir->is_pressed() && (mode == MODE_NEW || mode == MODE_INSTALL)) { // Replace parent directory, but keep target dir name. @@ -423,6 +429,10 @@ void ProjectDialog::_install_path_selected(const String &p_path) { get_ok_button()->grab_focus(); } +void ProjectDialog::_reset_name() { + project_name->set_text(TTR("New Game Project")); +} + void ProjectDialog::_renderer_selected() { ERR_FAIL_NULL(renderer_button_group->get_pressed_button()); @@ -688,10 +698,11 @@ void ProjectDialog::set_project_path(const String &p_path) { } void ProjectDialog::ask_for_path_and_show() { + _reset_name(); _browse_project_path(); } -void ProjectDialog::show_dialog() { +void ProjectDialog::show_dialog(bool p_reset_name) { if (mode == MODE_RENAME) { // Name and path are set in `ProjectManager::_rename_project`. project_path->set_editable(false); @@ -711,8 +722,9 @@ void ProjectDialog::show_dialog() { callable_mp((Control *)project_name, &Control::grab_focus).call_deferred(); callable_mp(project_name, &LineEdit::select_all).call_deferred(); } else { - String proj = TTR("New Game Project"); - project_name->set_text(proj); + if (p_reset_name) { + _reset_name(); + } project_path->set_editable(true); String fav_dir = EDITOR_GET("filesystem/directories/default_project_path"); @@ -793,6 +805,7 @@ void ProjectDialog::_notification(int p_what) { fdialog_project->set_access(EditorFileDialog::ACCESS_FILESYSTEM); fdialog_project->connect("dir_selected", callable_mp(this, &ProjectDialog::_project_path_selected)); fdialog_project->connect("file_selected", callable_mp(this, &ProjectDialog::_project_path_selected)); + fdialog_project->connect("canceled", callable_mp(this, &ProjectDialog::show_dialog).bind(false), CONNECT_DEFERRED); callable_mp((Node *)this, &Node::add_sibling).call_deferred(fdialog_project, false); } break; } diff --git a/editor/project_manager/project_dialog.h b/editor/project_manager/project_dialog.h index 8517189e5a..b985492f84 100644 --- a/editor/project_manager/project_dialog.h +++ b/editor/project_manager/project_dialog.h @@ -122,6 +122,7 @@ private: void _project_path_selected(const String &p_path); void _install_path_selected(const String &p_path); + void _reset_name(); void _renderer_selected(); void _nonempty_confirmation_ok_pressed(); @@ -139,7 +140,7 @@ public: void set_zip_title(const String &p_title); void ask_for_path_and_show(); - void show_dialog(); + void show_dialog(bool p_reset_name = true); ProjectDialog(); }; diff --git a/editor/project_manager/quick_settings_dialog.cpp b/editor/project_manager/quick_settings_dialog.cpp index ee53bd7927..a98d9073b0 100644 --- a/editor/project_manager/quick_settings_dialog.cpp +++ b/editor/project_manager/quick_settings_dialog.cpp @@ -43,7 +43,9 @@ #include "scene/gui/panel_container.h" void QuickSettingsDialog::_fetch_setting_values() { +#ifndef ANDROID_ENABLED editor_languages.clear(); +#endif editor_themes.clear(); editor_scales.clear(); editor_network_modes.clear(); @@ -55,7 +57,9 @@ void QuickSettingsDialog::_fetch_setting_values() { for (const PropertyInfo &pi : editor_settings_properties) { if (pi.name == "interface/editor/editor_language") { +#ifndef ANDROID_ENABLED editor_languages = pi.hint_string.split(","); +#endif } else if (pi.name == "interface/theme/preset") { editor_themes = pi.hint_string.split(","); } else if (pi.name == "interface/editor/display_scale") { @@ -70,6 +74,7 @@ void QuickSettingsDialog::_fetch_setting_values() { } void QuickSettingsDialog::_update_current_values() { +#ifndef ANDROID_ENABLED // Language options. { const String current_lang = EDITOR_GET("interface/editor/editor_language"); @@ -82,6 +87,7 @@ void QuickSettingsDialog::_update_current_values() { } } } +#endif // Theme options. { @@ -151,10 +157,12 @@ void QuickSettingsDialog::_add_setting_control(const String &p_text, Control *p_ container->add_child(p_control); } +#ifndef ANDROID_ENABLED void QuickSettingsDialog::_language_selected(int p_id) { const String selected_language = language_option_button->get_item_metadata(p_id); _set_setting_value("interface/editor/editor_language", selected_language, true); } +#endif void QuickSettingsDialog::_theme_selected(int p_id) { const String selected_theme = theme_option_button->get_item_text(p_id); @@ -195,7 +203,9 @@ void QuickSettingsDialog::_request_restart() { } void QuickSettingsDialog::update_size_limits(const Size2 &p_max_popup_size) { +#ifndef ANDROID_ENABLED language_option_button->get_popup()->set_max_size(p_max_popup_size); +#endif } void QuickSettingsDialog::_notification(int p_what) { @@ -237,6 +247,7 @@ QuickSettingsDialog::QuickSettingsDialog() { settings_list = memnew(VBoxContainer); settings_list_panel->add_child(settings_list); +#ifndef ANDROID_ENABLED // Language options. { language_option_button = memnew(OptionButton); @@ -252,6 +263,7 @@ QuickSettingsDialog::QuickSettingsDialog() { _add_setting_control(TTR("Language"), language_option_button); } +#endif // Theme options. { @@ -319,13 +331,6 @@ QuickSettingsDialog::QuickSettingsDialog() { } _update_current_values(); - -#ifdef ANDROID_ENABLED - // The language selection dropdown doesn't work on Android (as the setting isn't saved), see GH-60353. - // Also, the dropdown it spawns is very tall and can't be scrolled without a hardware mouse. - language_option_button->hide(); - scale_option_button->hide(); -#endif } // Restart required panel. diff --git a/editor/project_manager/quick_settings_dialog.h b/editor/project_manager/quick_settings_dialog.h index 7a03996934..938a6de16d 100644 --- a/editor/project_manager/quick_settings_dialog.h +++ b/editor/project_manager/quick_settings_dialog.h @@ -43,7 +43,9 @@ class VBoxContainer; class QuickSettingsDialog : public AcceptDialog { GDCLASS(QuickSettingsDialog, AcceptDialog); +#ifndef ANDROID_ENABLED Vector<String> editor_languages; +#endif Vector<String> editor_themes; Vector<String> editor_scales; Vector<String> editor_network_modes; @@ -57,7 +59,11 @@ class QuickSettingsDialog : public AcceptDialog { void _add_setting_control(const String &p_text, Control *p_control); +#ifndef ANDROID_ENABLED + // The language selection dropdown doesn't work on Android (as the setting isn't saved), see GH-60353. + // Also, the dropdown it spawns is very tall and can't be scrolled without a hardware mouse. OptionButton *language_option_button = nullptr; +#endif OptionButton *theme_option_button = nullptr; OptionButton *scale_option_button = nullptr; OptionButton *network_mode_option_button = nullptr; @@ -65,7 +71,9 @@ class QuickSettingsDialog : public AcceptDialog { Label *custom_theme_label = nullptr; +#ifndef ANDROID_ENABLED void _language_selected(int p_id); +#endif void _theme_selected(int p_id); void _scale_selected(int p_id); void _network_mode_selected(int p_id); diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index 2cb2120d11..56924fc1fe 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -3331,9 +3331,10 @@ void SceneTreeDock::_files_dropped(const Vector<String> &p_files, NodePath p_to, const String &res_path = p_files[0]; const StringName res_type = EditorFileSystem::get_singleton()->get_file_type(res_path); + const bool is_dropping_scene = ClassDB::is_parent_class(res_type, "PackedScene"); - // Dropping as property when possible. - if (p_type == 0 && p_files.size() == 1) { + // Dropping as property. + if (p_type == 0 && p_files.size() == 1 && !is_dropping_scene) { List<String> valid_properties; List<PropertyInfo> pinfo; @@ -3378,7 +3379,7 @@ void SceneTreeDock::_files_dropped(const Vector<String> &p_files, NodePath p_to, // Either instantiate scenes or create AudioStreamPlayers. int to_pos = -1; _normalize_drop(node, to_pos, p_type); - if (ClassDB::is_parent_class(res_type, "PackedScene")) { + if (is_dropping_scene) { _perform_instantiate_scenes(p_files, node, to_pos); } else if (ClassDB::is_parent_class(res_type, "AudioStream")) { _perform_create_audio_stream_players(p_files, node, to_pos); diff --git a/editor/themes/editor_theme_manager.cpp b/editor/themes/editor_theme_manager.cpp index a63b6d4e14..9d8cbb053d 100644 --- a/editor/themes/editor_theme_manager.cpp +++ b/editor/themes/editor_theme_manager.cpp @@ -2346,7 +2346,7 @@ void EditorThemeManager::_populate_editor_styles(const Ref<EditorTheme> &p_theme } // VisualShader editor. - p_theme->set_stylebox("label_style", "VShaderEditor", make_empty_stylebox(2, 1, 2, 1)); + p_theme->set_stylebox("label_style", "VShaderEditor", make_empty_stylebox(4, 6, 4, 6)); // StateMachine graph. { diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index e73b4c945c..e98cae765b 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -299,12 +299,14 @@ void GDScriptAnalyzer::get_class_node_current_scope_classes(GDScriptParser::Clas // Prioritize node base type over its outer class if (p_node->base_type.class_type != nullptr) { - ensure_cached_parser_for_class(p_node->base_type.class_type, p_node, vformat(R"(Trying to fetch classes in the current scope: base class of "%s")", p_node->fqcn), p_source); + // TODO: 'ensure_cached_external_parser_for_class()' is only necessary because 'resolve_class_inheritance()' is not getting called here. + ensure_cached_external_parser_for_class(p_node->base_type.class_type, p_node, "Trying to fetch classes in the current scope", p_source); get_class_node_current_scope_classes(p_node->base_type.class_type, p_list, p_source); } if (p_node->outer != nullptr) { - ensure_cached_parser_for_class(p_node->outer, p_node, vformat(R"(Trying to fetch classes in the current scope: outer class of "%s")", p_node->fqcn), p_source); + // TODO: 'ensure_cached_external_parser_for_class()' is only necessary because 'resolve_class_inheritance()' is not getting called here. + ensure_cached_external_parser_for_class(p_node->outer, p_node, "Trying to fetch classes in the current scope", p_source); get_class_node_current_scope_classes(p_node->outer, p_list, p_source); } } @@ -314,10 +316,10 @@ Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_c p_source = p_class; } - Ref<GDScriptParserRef> parser_ref = ensure_cached_parser_for_class(p_class, nullptr, vformat(R"(Trying to resolve class inheritance of "%s")", p_class->fqcn), p_source); + Ref<GDScriptParserRef> parser_ref = ensure_cached_external_parser_for_class(p_class, nullptr, "Trying to resolve class inheritance", p_source); Finally finally([&]() { for (GDScriptParser::ClassNode *look_class = p_class; look_class != nullptr; look_class = look_class->base_type.class_type) { - ensure_cached_parser_for_class(look_class->base_type.class_type, look_class, vformat(R"(Trying to resolve class inheritance of "%s")", p_class->fqcn), p_source); + ensure_cached_external_parser_for_class(look_class->base_type.class_type, look_class, "Trying to resolve class inheritance", p_source); } }); @@ -888,12 +890,12 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class, p_source = member.get_source_node(); } - Ref<GDScriptParserRef> parser_ref = ensure_cached_parser_for_class(p_class, nullptr, vformat(R"(Trying to resolve class member "%s" of "%s")", member.get_name(), p_class->fqcn), p_source); + Ref<GDScriptParserRef> parser_ref = ensure_cached_external_parser_for_class(p_class, nullptr, "Trying to resolve class member", p_source); Finally finally([&]() { - ensure_cached_parser_for_class(member.get_datatype().class_type, p_class, vformat(R"(Trying to resolve datatype of class member "%s" of "%s")", member.get_name(), p_class->fqcn), p_source); + ensure_cached_external_parser_for_class(member.get_datatype().class_type, p_class, "Trying to resolve datatype of class member", p_source); GDScriptParser::DataType member_type = member.get_datatype(); if (member_type.has_container_element_type(0)) { - ensure_cached_parser_for_class(member_type.get_container_element_type(0).class_type, p_class, vformat(R"(Trying to resolve datatype of class member "%s" of "%s")", member.get_name(), p_class->fqcn), p_source); + ensure_cached_external_parser_for_class(member_type.get_container_element_type(0).class_type, p_class, "Trying to resolve datatype of class member", p_source); } }); @@ -1181,7 +1183,7 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas p_source = p_class; } - Ref<GDScriptParserRef> parser_ref = ensure_cached_parser_for_class(p_class, nullptr, vformat(R"(Trying to resolve class interface of "%s")", p_class->fqcn), p_source); + Ref<GDScriptParserRef> parser_ref = ensure_cached_external_parser_for_class(p_class, nullptr, "Trying to resolve class interface", p_source); if (!p_class->resolved_interface) { #ifdef DEBUG_ENABLED @@ -1271,7 +1273,7 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class, co p_source = p_class; } - Ref<GDScriptParserRef> parser_ref = ensure_cached_parser_for_class(p_class, nullptr, vformat(R"(Trying to resolve class body of "%s")", p_class->fqcn), p_source); + Ref<GDScriptParserRef> parser_ref = ensure_cached_external_parser_for_class(p_class, nullptr, "Trying to resolve class body", p_source); if (p_class->resolved_body) { return; @@ -3654,19 +3656,19 @@ GDScriptParser::DataType GDScriptAnalyzer::make_global_class_meta_type(const Str } } -Ref<GDScriptParserRef> GDScriptAnalyzer::ensure_cached_parser_for_class(const GDScriptParser::ClassNode *p_class, const GDScriptParser::ClassNode *p_from_class, const String &p_context, const GDScriptParser::Node *p_source) { +Ref<GDScriptParserRef> GDScriptAnalyzer::ensure_cached_external_parser_for_class(const GDScriptParser::ClassNode *p_class, const GDScriptParser::ClassNode *p_from_class, const char *p_context, const GDScriptParser::Node *p_source) { if (p_class == nullptr) { return nullptr; } - if (parser->has_class(p_class)) { - return nullptr; - } - if (HashMap<const GDScriptParser::ClassNode *, Ref<GDScriptParserRef>>::Iterator E = external_class_parser_cache.find(p_class)) { return E->value; } + if (parser->has_class(p_class)) { + return nullptr; + } + if (p_from_class == nullptr) { p_from_class = parser->head; } @@ -3676,14 +3678,14 @@ Ref<GDScriptParserRef> GDScriptAnalyzer::ensure_cached_parser_for_class(const GD Ref<GDScriptParserRef> parser_ref; for (const GDScriptParser::ClassNode *look_class = p_from_class; look_class != nullptr; look_class = look_class->base_type.class_type) { if (parser->has_class(look_class)) { - parser_ref = find_cached_parser_for_class(p_class, parser); + parser_ref = find_cached_external_parser_for_class(p_class, parser); if (parser_ref.is_valid()) { break; } } if (HashMap<const GDScriptParser::ClassNode *, Ref<GDScriptParserRef>>::Iterator E = external_class_parser_cache.find(look_class)) { - parser_ref = find_cached_parser_for_class(p_class, E->value); + parser_ref = find_cached_external_parser_for_class(p_class, E->value); if (parser_ref.is_valid()) { break; } @@ -3691,7 +3693,7 @@ Ref<GDScriptParserRef> GDScriptAnalyzer::ensure_cached_parser_for_class(const GD String look_class_script_path = look_class->get_datatype().script_path; if (HashMap<String, Ref<GDScriptParserRef>>::Iterator E = parser->depended_parsers.find(look_class_script_path)) { - parser_ref = find_cached_parser_for_class(p_class, E->value); + parser_ref = find_cached_external_parser_for_class(p_class, E->value); if (parser_ref.is_valid()) { break; } @@ -3699,8 +3701,8 @@ Ref<GDScriptParserRef> GDScriptAnalyzer::ensure_cached_parser_for_class(const GD } if (parser_ref.is_null()) { - push_error(vformat(R"(Parser bug: Could not find external parser. (%s))", p_context), p_source); - // A null parser will be inserted into the cache and this error won't spam for the same class. + push_error(vformat(R"(Parser bug (please report): Could not find external parser for class "%s". (%s))", p_class->fqcn, p_context), p_source); + // A null parser will be inserted into the cache, so this error won't spam for the same class. // This is ok, the values of external_class_parser_cache are not assumed to be valid references. } @@ -3708,7 +3710,7 @@ Ref<GDScriptParserRef> GDScriptAnalyzer::ensure_cached_parser_for_class(const GD return parser_ref; } -Ref<GDScriptParserRef> GDScriptAnalyzer::find_cached_parser_for_class(const GDScriptParser::ClassNode *p_class, const Ref<GDScriptParserRef> &p_dependant_parser) { +Ref<GDScriptParserRef> GDScriptAnalyzer::find_cached_external_parser_for_class(const GDScriptParser::ClassNode *p_class, const Ref<GDScriptParserRef> &p_dependant_parser) { if (p_dependant_parser.is_null()) { return nullptr; } @@ -3729,10 +3731,10 @@ Ref<GDScriptParserRef> GDScriptAnalyzer::find_cached_parser_for_class(const GDSc // Silently ensure it's parsed. p_dependant_parser->raise_status(GDScriptParserRef::PARSED); - return find_cached_parser_for_class(p_class, p_dependant_parser->get_parser()); + return find_cached_external_parser_for_class(p_class, p_dependant_parser->get_parser()); } -Ref<GDScriptParserRef> GDScriptAnalyzer::find_cached_parser_for_class(const GDScriptParser::ClassNode *p_class, GDScriptParser *p_dependant_parser) { +Ref<GDScriptParserRef> GDScriptAnalyzer::find_cached_external_parser_for_class(const GDScriptParser::ClassNode *p_class, GDScriptParser *p_dependant_parser) { if (p_dependant_parser == nullptr) { return nullptr; } @@ -4474,7 +4476,11 @@ void GDScriptAnalyzer::reduce_preload(GDScriptParser::PreloadNode *p_preload) { p_preload->is_constant = true; p_preload->reduced_value = p_preload->resource; p_preload->set_datatype(type_from_variant(p_preload->reduced_value, p_preload)); - ensure_cached_parser_for_class(p_preload->get_datatype().class_type, nullptr, vformat(R"(Trying to resolve preload of "%s")", p_preload->resolved_path), p_preload); + + // TODO: Not sure if this is necessary anymore. + // 'type_from_variant()' should call 'resolve_class_inheritance()' which would call 'ensure_cached_external_parser_for_class()' + // Better safe than sorry. + ensure_cached_external_parser_for_class(p_preload->get_datatype().class_type, nullptr, "Trying to resolve preload", p_preload); } void GDScriptAnalyzer::reduce_self(GDScriptParser::SelfNode *p_self) { diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h index 7ec3544f52..25e5aa9a2c 100644 --- a/modules/gdscript/gdscript_analyzer.h +++ b/modules/gdscript/gdscript_analyzer.h @@ -145,9 +145,9 @@ class GDScriptAnalyzer { void resolve_pending_lambda_bodies(); bool class_exists(const StringName &p_class) const; void reduce_identifier_from_base_set_class(GDScriptParser::IdentifierNode *p_identifier, GDScriptParser::DataType p_identifier_datatype); - Ref<GDScriptParserRef> ensure_cached_parser_for_class(const GDScriptParser::ClassNode *p_class, const GDScriptParser::ClassNode *p_from_class, const String &p_context, const GDScriptParser::Node *p_source); - Ref<GDScriptParserRef> find_cached_parser_for_class(const GDScriptParser::ClassNode *p_class, const Ref<GDScriptParserRef> &p_dependant_parser); - Ref<GDScriptParserRef> find_cached_parser_for_class(const GDScriptParser::ClassNode *p_class, GDScriptParser *p_dependant_parser); + Ref<GDScriptParserRef> ensure_cached_external_parser_for_class(const GDScriptParser::ClassNode *p_class, const GDScriptParser::ClassNode *p_from_class, const char *p_context, const GDScriptParser::Node *p_source); + Ref<GDScriptParserRef> find_cached_external_parser_for_class(const GDScriptParser::ClassNode *p_class, const Ref<GDScriptParserRef> &p_dependant_parser); + Ref<GDScriptParserRef> find_cached_external_parser_for_class(const GDScriptParser::ClassNode *p_class, GDScriptParser *p_dependant_parser); Ref<GDScript> get_depended_shallow_script(const String &p_path, Error &r_error); #ifdef DEBUG_ENABLED void is_shadowing(GDScriptParser::IdentifierNode *p_identifier, const String &p_context, const bool p_in_local_scope); diff --git a/modules/gdscript/gdscript_cache.cpp b/modules/gdscript/gdscript_cache.cpp index 2b1184bcb9..6eb35c9ac1 100644 --- a/modules/gdscript/gdscript_cache.cpp +++ b/modules/gdscript/gdscript_cache.cpp @@ -135,8 +135,10 @@ void GDScriptParserRef::clear() { GDScriptParserRef::~GDScriptParserRef() { clear(); + if (!abandoned) { - GDScriptCache::remove_parser(path); + MutexLock lock(GDScriptCache::singleton->mutex); + GDScriptCache::singleton->parser_map.erase(path); } } diff --git a/modules/interactive_music/doc_classes/AudioStreamSynchronized.xml b/modules/interactive_music/doc_classes/AudioStreamSynchronized.xml index ea914715a3..5353dc7376 100644 --- a/modules/interactive_music/doc_classes/AudioStreamSynchronized.xml +++ b/modules/interactive_music/doc_classes/AudioStreamSynchronized.xml @@ -47,7 +47,7 @@ </members> <constants> <constant name="MAX_STREAMS" value="32"> - Maximum amount of streams that can be synchrohized. + Maximum amount of streams that can be synchronized. </constant> </constants> </class> diff --git a/modules/multiplayer/editor/replication_editor.cpp b/modules/multiplayer/editor/replication_editor.cpp index d242b06c29..851ad85876 100644 --- a/modules/multiplayer/editor/replication_editor.cpp +++ b/modules/multiplayer/editor/replication_editor.cpp @@ -430,7 +430,7 @@ void ReplicationEditor::_tree_item_edited() { undo_redo->add_do_method(config.ptr(), "property_set_spawn", prop, value); undo_redo->add_undo_method(config.ptr(), "property_set_spawn", prop, !value); undo_redo->add_do_method(this, "_update_value", prop, column, value ? 1 : 0); - undo_redo->add_undo_method(this, "_update_value", prop, column, value ? 1 : 0); + undo_redo->add_undo_method(this, "_update_value", prop, column, value ? 0 : 1); undo_redo->commit_action(); } else if (column == 2) { undo_redo->create_action(TTR("Set sync property")); diff --git a/modules/multiplayer/scene_multiplayer.cpp b/modules/multiplayer/scene_multiplayer.cpp index e2ebca0c88..6694376b30 100644 --- a/modules/multiplayer/scene_multiplayer.cpp +++ b/modules/multiplayer/scene_multiplayer.cpp @@ -307,8 +307,10 @@ void SceneMultiplayer::_process_sys(int p_from, const uint8_t *p_packet, int p_p int len = p_packet_len - SYS_CMD_SIZE; bool should_process = false; if (get_unique_id() == 1) { // I am the server. - // Direct messages to server should not go through relay. - ERR_FAIL_COND(peer > 0 && !connected_peers.has(peer)); + // The requested target might have disconnected while the packet was in transit. + if (unlikely(peer > 0 && !connected_peers.has(peer))) { + return; + } // Send relay packet. relay_buffer->seek(0); relay_buffer->put_u8(NETWORK_COMMAND_SYS); diff --git a/modules/openxr/doc_classes/OpenXRInterface.xml b/modules/openxr/doc_classes/OpenXRInterface.xml index 309cbe0d72..ed5810da3c 100644 --- a/modules/openxr/doc_classes/OpenXRInterface.xml +++ b/modules/openxr/doc_classes/OpenXRInterface.xml @@ -176,7 +176,7 @@ <param index="0" name="refresh_rate" type="float" /> <description> Informs the user the HMD refresh rate has changed. - [b]Node:[/b] Only emitted if XR runtime supports the refresh rate extension. + [b]Note:[/b] Only emitted if XR runtime supports the refresh rate extension. </description> </signal> <signal name="session_begun"> diff --git a/modules/websocket/wsl_peer.cpp b/modules/websocket/wsl_peer.cpp index 3c63aa4fbf..0a9a4053e3 100644 --- a/modules/websocket/wsl_peer.cpp +++ b/modules/websocket/wsl_peer.cpp @@ -99,6 +99,8 @@ void WSLPeer::Resolver::try_next_candidate(Ref<StreamPeerTCP> &p_tcp) { p_tcp->poll(); StreamPeerTCP::Status status = p_tcp->get_status(); if (status == StreamPeerTCP::STATUS_CONNECTED) { + // On Windows, setting TCP_NODELAY may fail if the socket is still connecting. + p_tcp->set_no_delay(true); ip_candidates.clear(); return; } else if (status == StreamPeerTCP::STATUS_CONNECTING) { @@ -112,7 +114,6 @@ void WSLPeer::Resolver::try_next_candidate(Ref<StreamPeerTCP> &p_tcp) { while (ip_candidates.size()) { Error err = p_tcp->connect_to_host(ip_candidates.pop_front(), port); if (err == OK) { - p_tcp->set_no_delay(true); return; } else { p_tcp->disconnect_from_host(); @@ -311,7 +312,7 @@ void WSLPeer::_do_client_handshake() { ERR_FAIL_COND(tcp.is_null()); // Try to connect to candidates. - if (resolver.has_more_candidates()) { + if (resolver.has_more_candidates() || tcp->get_status() == StreamPeerTCP::STATUS_CONNECTING) { resolver.try_next_candidate(tcp); if (resolver.has_more_candidates()) { return; // Still pending. diff --git a/platform/ios/doc_classes/EditorExportPlatformIOS.xml b/platform/ios/doc_classes/EditorExportPlatformIOS.xml index 87994ef22b..1d4a944dc4 100644 --- a/platform/ios/doc_classes/EditorExportPlatformIOS.xml +++ b/platform/ios/doc_classes/EditorExportPlatformIOS.xml @@ -151,16 +151,16 @@ Indicates whether your app uses advertising data for tracking. </member> <member name="privacy/collected_data/audio_data/collected" type="bool" setter="" getter=""> - Indicates whether your app collects audio data data. + Indicates whether your app collects audio data. </member> <member name="privacy/collected_data/audio_data/collection_purposes" type="int" setter="" getter=""> The reasons your app collects audio data. See [url=https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_data_use_in_privacy_manifests]Describing data use in privacy manifests[/url]. </member> <member name="privacy/collected_data/audio_data/linked_to_user" type="bool" setter="" getter=""> - Indicates whether your app links audio data data to the user's identity. + Indicates whether your app links audio data to the user's identity. </member> <member name="privacy/collected_data/audio_data/used_for_tracking" type="bool" setter="" getter=""> - Indicates whether your app uses audio data data for tracking. + Indicates whether your app uses audio data for tracking. </member> <member name="privacy/collected_data/browsing_history/collected" type="bool" setter="" getter=""> Indicates whether your app collects browsing history. diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp index 4a52e26373..8a2f83be2d 100644 --- a/platform/linuxbsd/x11/display_server_x11.cpp +++ b/platform/linuxbsd/x11/display_server_x11.cpp @@ -4970,6 +4970,23 @@ void DisplayServerX11::process_events() { pos = Point2i(windows[focused_window_id].size.width / 2, windows[focused_window_id].size.height / 2); } + BitField<MouseButtonMask> last_button_state = 0; + if (event.xmotion.state & Button1Mask) { + last_button_state.set_flag(MouseButtonMask::LEFT); + } + if (event.xmotion.state & Button2Mask) { + last_button_state.set_flag(MouseButtonMask::MIDDLE); + } + if (event.xmotion.state & Button3Mask) { + last_button_state.set_flag(MouseButtonMask::RIGHT); + } + if (event.xmotion.state & Button4Mask) { + last_button_state.set_flag(MouseButtonMask::MB_XBUTTON1); + } + if (event.xmotion.state & Button5Mask) { + last_button_state.set_flag(MouseButtonMask::MB_XBUTTON2); + } + Ref<InputEventMouseMotion> mm; mm.instantiate(); @@ -4977,13 +4994,13 @@ void DisplayServerX11::process_events() { if (xi.pressure_supported) { mm->set_pressure(xi.pressure); } else { - mm->set_pressure(bool(mouse_get_button_state().has_flag(MouseButtonMask::LEFT)) ? 1.0f : 0.0f); + mm->set_pressure(bool(last_button_state.has_flag(MouseButtonMask::LEFT)) ? 1.0f : 0.0f); } mm->set_tilt(xi.tilt); mm->set_pen_inverted(xi.pen_inverted); _get_key_modifier_state(event.xmotion.state, mm); - mm->set_button_mask(mouse_get_button_state()); + mm->set_button_mask(last_button_state); mm->set_position(pos); mm->set_global_position(pos); mm->set_velocity(Input::get_singleton()->get_last_mouse_velocity()); diff --git a/platform/macos/SCsub b/platform/macos/SCsub index c965e875c1..a10262c524 100644 --- a/platform/macos/SCsub +++ b/platform/macos/SCsub @@ -23,10 +23,10 @@ def generate_bundle(target, source, env): prefix += ".double" # Lipo editor executable. - target_bin = lipo(bin_dir + "/" + prefix, env.extra_suffix) + target_bin = lipo(bin_dir + "/" + prefix, env.extra_suffix + env.module_version_string) # Assemble .app bundle and update version info. - app_dir = Dir("#bin/" + (prefix + env.extra_suffix).replace(".", "_") + ".app").abspath + app_dir = Dir("#bin/" + (prefix + env.extra_suffix + env.module_version_string).replace(".", "_") + ".app").abspath templ = Dir("#misc/dist/macos_tools.app").abspath if os.path.exists(app_dir): shutil.rmtree(app_dir) @@ -35,6 +35,8 @@ def generate_bundle(target, source, env): os.mkdir(app_dir + "/Contents/MacOS") if target_bin != "": shutil.copy(target_bin, app_dir + "/Contents/MacOS/Godot") + if "mono" in env.module_version_string: + shutil.copytree(Dir("#bin/GodotSharp").abspath, app_dir + "/Contents/Resources/GodotSharp") version = get_build_version(False) short_version = get_build_version(True) with open(Dir("#misc/dist/macos").abspath + "/editor_info_plist.template", "rt", encoding="utf-8") as fin: @@ -76,8 +78,8 @@ def generate_bundle(target, source, env): dbg_prefix += ".double" # Lipo template executables. - rel_target_bin = lipo(bin_dir + "/" + rel_prefix, env.extra_suffix) - dbg_target_bin = lipo(bin_dir + "/" + dbg_prefix, env.extra_suffix) + rel_target_bin = lipo(bin_dir + "/" + rel_prefix, env.extra_suffix + env.module_version_string) + dbg_target_bin = lipo(bin_dir + "/" + dbg_prefix, env.extra_suffix + env.module_version_string) # Assemble .app bundle. app_dir = Dir("#bin/macos_template.app").abspath @@ -93,7 +95,7 @@ def generate_bundle(target, source, env): shutil.copy(dbg_target_bin, app_dir + "/Contents/MacOS/godot_macos_debug.universal") # ZIP .app bundle. - zip_dir = Dir("#bin/" + (app_prefix + env.extra_suffix).replace(".", "_")).abspath + zip_dir = Dir("#bin/" + (app_prefix + env.extra_suffix + env.module_version_string).replace(".", "_")).abspath shutil.make_archive(zip_dir, "zip", root_dir=bin_dir, base_dir="macos_template.app") shutil.rmtree(app_dir) diff --git a/platform/macos/doc_classes/EditorExportPlatformMacOS.xml b/platform/macos/doc_classes/EditorExportPlatformMacOS.xml index 92ade4b77a..34ad52bbf6 100644 --- a/platform/macos/doc_classes/EditorExportPlatformMacOS.xml +++ b/platform/macos/doc_classes/EditorExportPlatformMacOS.xml @@ -224,16 +224,16 @@ Indicates whether your app uses advertising data for tracking. </member> <member name="privacy/collected_data/audio_data/collected" type="bool" setter="" getter=""> - Indicates whether your app collects audio data data. + Indicates whether your app collects audio data. </member> <member name="privacy/collected_data/audio_data/collection_purposes" type="int" setter="" getter=""> The reasons your app collects audio data. See [url=https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_data_use_in_privacy_manifests]Describing data use in privacy manifests[/url]. </member> <member name="privacy/collected_data/audio_data/linked_to_user" type="bool" setter="" getter=""> - Indicates whether your app links audio data data to the user's identity. + Indicates whether your app links audio data to the user's identity. </member> <member name="privacy/collected_data/audio_data/used_for_tracking" type="bool" setter="" getter=""> - Indicates whether your app uses audio data data for tracking. + Indicates whether your app uses audio data for tracking. </member> <member name="privacy/collected_data/browsing_history/collected" type="bool" setter="" getter=""> Indicates whether your app collects browsing history. diff --git a/platform/macos/gl_manager_macos_legacy.h b/platform/macos/gl_manager_macos_legacy.h index af9be8f5ba..383c5c3306 100644 --- a/platform/macos/gl_manager_macos_legacy.h +++ b/platform/macos/gl_manager_macos_legacy.h @@ -62,6 +62,7 @@ class GLManagerLegacy_MacOS { Error create_context(GLWindow &win); + bool framework_loaded = false; bool use_vsync = false; CGLEnablePtr CGLEnable = nullptr; CGLSetParameterPtr CGLSetParameter = nullptr; diff --git a/platform/macos/gl_manager_macos_legacy.mm b/platform/macos/gl_manager_macos_legacy.mm index 6ce3831d9c..a0d037144e 100644 --- a/platform/macos/gl_manager_macos_legacy.mm +++ b/platform/macos/gl_manager_macos_legacy.mm @@ -32,6 +32,7 @@ #if defined(MACOS_ENABLED) && defined(GLES3_ENABLED) +#include <dlfcn.h> #include <stdio.h> #include <stdlib.h> @@ -156,7 +157,7 @@ void GLManagerLegacy_MacOS::window_set_per_pixel_transparency_enabled(DisplaySer } Error GLManagerLegacy_MacOS::initialize() { - return OK; + return framework_loaded ? OK : ERR_CANT_CREATE; } void GLManagerLegacy_MacOS::set_use_vsync(bool p_use) { @@ -186,12 +187,17 @@ NSOpenGLContext *GLManagerLegacy_MacOS::get_context(DisplayServer::WindowID p_wi } GLManagerLegacy_MacOS::GLManagerLegacy_MacOS() { - CFBundleRef framework = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.opengl")); - CFBundleLoadExecutable(framework); - - CGLEnable = (CGLEnablePtr)CFBundleGetFunctionPointerForName(framework, CFSTR("CGLEnable")); - CGLSetParameter = (CGLSetParameterPtr)CFBundleGetFunctionPointerForName(framework, CFSTR("CGLSetParameter")); - CGLGetCurrentContext = (CGLGetCurrentContextPtr)CFBundleGetFunctionPointerForName(framework, CFSTR("CGLGetCurrentContext")); + NSBundle *framework = [NSBundle bundleWithPath:@"/System/Library/Frameworks/OpenGL.framework"]; + if (framework) { + void *library_handle = dlopen([framework.executablePath UTF8String], RTLD_NOW); + if (library_handle) { + CGLEnable = (CGLEnablePtr)dlsym(library_handle, "CGLEnable"); + CGLSetParameter = (CGLSetParameterPtr)dlsym(library_handle, "CGLSetParameter"); + CGLGetCurrentContext = (CGLGetCurrentContextPtr)dlsym(library_handle, "CGLGetCurrentContext"); + + framework_loaded = CGLEnable && CGLSetParameter && CGLGetCurrentContext; + } + } } GLManagerLegacy_MacOS::~GLManagerLegacy_MacOS() { diff --git a/platform/macos/os_macos.mm b/platform/macos/os_macos.mm index 078be53a8d..495246344b 100644 --- a/platform/macos/os_macos.mm +++ b/platform/macos/os_macos.mm @@ -680,8 +680,11 @@ Error OS_MacOS::kill(const ProcessID &p_pid) { if (!app) { return OS_Unix::kill(p_pid); } - - return [app forceTerminate] ? OK : ERR_INVALID_PARAMETER; + bool terminated = [app terminate]; + if (!terminated) { + terminated = [app forceTerminate]; + } + return terminated ? OK : ERR_INVALID_PARAMETER; } String OS_MacOS::get_unique_id() const { diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index 36f3f632d5..7e368b4c1e 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -1333,13 +1333,15 @@ DisplayServer::WindowID DisplayServerWindows::create_sub_window(WindowMode p_mod wd.is_popup = true; } if (p_flags & WINDOW_FLAG_TRANSPARENT_BIT) { - DWM_BLURBEHIND bb; - ZeroMemory(&bb, sizeof(bb)); - HRGN hRgn = CreateRectRgn(0, 0, -1, -1); - bb.dwFlags = DWM_BB_ENABLE | DWM_BB_BLURREGION; - bb.hRgnBlur = hRgn; - bb.fEnable = TRUE; - DwmEnableBlurBehindWindow(wd.hWnd, &bb); + if (OS::get_singleton()->is_layered_allowed()) { + DWM_BLURBEHIND bb; + ZeroMemory(&bb, sizeof(bb)); + HRGN hRgn = CreateRectRgn(0, 0, -1, -1); + bb.dwFlags = DWM_BB_ENABLE | DWM_BB_BLURREGION; + bb.hRgnBlur = hRgn; + bb.fEnable = TRUE; + DwmEnableBlurBehindWindow(wd.hWnd, &bb); + } wd.layered_window = true; } @@ -2119,28 +2121,29 @@ void DisplayServerWindows::window_set_flag(WindowFlags p_flag, bool p_enabled, W } break; case WINDOW_FLAG_TRANSPARENT: { if (p_enabled) { - //enable per-pixel alpha - - DWM_BLURBEHIND bb; - ZeroMemory(&bb, sizeof(bb)); - HRGN hRgn = CreateRectRgn(0, 0, -1, -1); - bb.dwFlags = DWM_BB_ENABLE | DWM_BB_BLURREGION; - bb.hRgnBlur = hRgn; - bb.fEnable = TRUE; - DwmEnableBlurBehindWindow(wd.hWnd, &bb); - + // Enable per-pixel alpha. + if (OS::get_singleton()->is_layered_allowed()) { + DWM_BLURBEHIND bb; + ZeroMemory(&bb, sizeof(bb)); + HRGN hRgn = CreateRectRgn(0, 0, -1, -1); + bb.dwFlags = DWM_BB_ENABLE | DWM_BB_BLURREGION; + bb.hRgnBlur = hRgn; + bb.fEnable = TRUE; + DwmEnableBlurBehindWindow(wd.hWnd, &bb); + } wd.layered_window = true; } else { - //disable per-pixel alpha + // Disable per-pixel alpha. wd.layered_window = false; - - DWM_BLURBEHIND bb; - ZeroMemory(&bb, sizeof(bb)); - HRGN hRgn = CreateRectRgn(0, 0, -1, -1); - bb.dwFlags = DWM_BB_ENABLE | DWM_BB_BLURREGION; - bb.hRgnBlur = hRgn; - bb.fEnable = FALSE; - DwmEnableBlurBehindWindow(wd.hWnd, &bb); + if (OS::get_singleton()->is_layered_allowed()) { + DWM_BLURBEHIND bb; + ZeroMemory(&bb, sizeof(bb)); + HRGN hRgn = CreateRectRgn(0, 0, -1, -1); + bb.dwFlags = DWM_BB_ENABLE | DWM_BB_BLURREGION; + bb.hRgnBlur = hRgn; + bb.fEnable = FALSE; + DwmEnableBlurBehindWindow(wd.hWnd, &bb); + } } } break; case WINDOW_FLAG_NO_FOCUS: { @@ -4250,6 +4253,16 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA break; } + uint32_t pointer_id = LOWORD(wParam); + POINTER_INPUT_TYPE pointer_type = PT_POINTER; + if (!win8p_GetPointerType(pointer_id, &pointer_type)) { + break; + } + + if (pointer_type != PT_PEN) { + break; + } + Ref<InputEventMouseButton> mb; mb.instantiate(); mb->set_window_id(window_id); diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp index 48ade1e5cc..c12b95314e 100644 --- a/scene/2d/tile_map.cpp +++ b/scene/2d/tile_map.cpp @@ -817,6 +817,8 @@ TypedArray<Vector2i> TileMap::get_surrounding_cells(const Vector2i &p_coords) { PackedStringArray TileMap::get_configuration_warnings() const { PackedStringArray warnings = Node::get_configuration_warnings(); + warnings.push_back(RTR("The TileMap node is deprecated as it is superseded by the use of multiple TileMapLayer nodes.\nTo convert a TileMap to a set of TileMapLayer nodes, open the TileMap bottom panel with this node selected, click the toolbox icon in the top-right corner and choose \"Extract TileMap layers as individual TileMapLayer nodes\".")); + // Retrieve the set of Z index values with a Y-sorted layer. RBSet<int> y_sorted_z_index; for (const TileMapLayer *layer : layers) { diff --git a/scene/3d/skeleton_3d.cpp b/scene/3d/skeleton_3d.cpp index 3d24b3bbe9..5a4e176e99 100644 --- a/scene/3d/skeleton_3d.cpp +++ b/scene/3d/skeleton_3d.cpp @@ -300,7 +300,7 @@ void Skeleton3D::setup_simulator() { simulator = sim; sim->is_compat = true; sim->set_active(false); // Don't run unneeded process. - add_child(simulator); + add_child(simulator, false, INTERNAL_MODE_BACK); set_animate_physical_bones(animate_physical_bones); } #endif // _DISABLE_DEPRECATED diff --git a/scene/animation/animation_blend_tree.cpp b/scene/animation/animation_blend_tree.cpp index a27da73b89..e4baae1afb 100644 --- a/scene/animation/animation_blend_tree.cpp +++ b/scene/animation/animation_blend_tree.cpp @@ -237,6 +237,7 @@ AnimationNode::NodeTimeInfo AnimationNodeAnimation::_process(const AnimationMixe } // Finished. if (Animation::is_less_approx(prev_playback_time, anim_size) && Animation::is_greater_or_equal_approx(cur_playback_time, anim_size)) { + cur_playback_time = anim_size; process_state->tree->call_deferred(SNAME("emit_signal"), SceneStringName(animation_finished), animation); } } diff --git a/scene/animation/animation_player.cpp b/scene/animation/animation_player.cpp index 19229f405a..24b777e2eb 100644 --- a/scene/animation/animation_player.cpp +++ b/scene/animation/animation_player.cpp @@ -204,19 +204,20 @@ void AnimationPlayer::_process_playback_data(PlaybackData &cd, double p_delta, f } double prev_pos = cd.pos; // The animation may be changed during process, so it is safer that the state is changed before process. - cd.pos = next_pos; // End detection. if (p_is_current) { if (cd.from->animation->get_loop_mode() == Animation::LOOP_NONE) { if (!backwards && Animation::is_less_or_equal_approx(prev_pos, len) && Math::is_equal_approx(next_pos, len)) { // Playback finished. + next_pos = len; // Snap to the edge. end_reached = true; end_notify = Animation::is_less_approx(prev_pos, len); // Notify only if not already at the end. p_blend = 1.0; } if (backwards && Animation::is_greater_or_equal_approx(prev_pos, 0) && Math::is_equal_approx(next_pos, 0)) { // Playback finished. + next_pos = 0; // Snap to the edge. end_reached = true; end_notify = Animation::is_greater_approx(prev_pos, 0); // Notify only if not already at the beginning. p_blend = 1.0; @@ -224,6 +225,8 @@ void AnimationPlayer::_process_playback_data(PlaybackData &cd, double p_delta, f } } + cd.pos = next_pos; + PlaybackInfo pi; if (p_started) { pi.time = prev_pos; diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp index 9b762f7b3c..60b3e371a0 100644 --- a/scene/gui/graph_edit.cpp +++ b/scene/gui/graph_edit.cpp @@ -912,7 +912,7 @@ bool GraphEdit::_filter_input(const Point2 &p_point) { // This prevents interactions with a port hotzone that is behind another node. Rect2 graph_node_rect = Rect2(graph_node->get_position(), graph_node->get_size() * zoom); - if (graph_node_rect.has_point(click_pos * zoom)) { + if (graph_node_rect.has_point(p_point)) { break; } } @@ -1044,12 +1044,6 @@ void GraphEdit::_top_connection_layer_input(const Ref<InputEvent> &p_ev) { return; } } - - // This prevents interactions with a port hotzone that is behind another node. - Rect2 graph_node_rect = Rect2(graph_node->get_position(), graph_node->get_size() * zoom); - if (graph_node_rect.has_point(click_pos * zoom)) { - break; - } } } @@ -1121,6 +1115,12 @@ void GraphEdit::_top_connection_layer_input(const Ref<InputEvent> &p_ev) { } connecting_target_valid = false; } + + // This prevents interactions with a port hotzone that is behind another node. + Rect2 graph_node_rect = Rect2(graph_node->get_position(), graph_node->get_size() * zoom); + if (graph_node_rect.has_point(mm->get_position())) { + break; + } } } } diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp index f62421061b..443fe4774a 100644 --- a/scene/gui/popup_menu.cpp +++ b/scene/gui/popup_menu.cpp @@ -608,7 +608,7 @@ void PopupMenu::_input_from_window_internal(const Ref<InputEvent> &p_event) { return; } // Disable clicks under a time threshold to avoid selection right when opening the popup. - if (was_during_grabbed_click && OS::get_singleton()->get_ticks_msec() - popup_time_msec < 150) { + if (was_during_grabbed_click && OS::get_singleton()->get_ticks_msec() - popup_time_msec < 400) { return; } @@ -1064,6 +1064,7 @@ void PopupMenu::_notification(int p_what) { } break; case NOTIFICATION_POST_POPUP: { + popup_time_msec = OS::get_singleton()->get_ticks_msec(); initial_button_mask = Input::get_singleton()->get_mouse_button_mask(); during_grabbed_click = (bool)initial_button_mask; } break; diff --git a/scene/gui/tab_bar.cpp b/scene/gui/tab_bar.cpp index 1ae18f5728..3e0d6adf42 100644 --- a/scene/gui/tab_bar.cpp +++ b/scene/gui/tab_bar.cpp @@ -620,6 +620,8 @@ void TabBar::_draw_tab(Ref<StyleBox> &p_tab_style, Color &p_font_color, int p_in } cb->draw(ci, Point2i(cb_rect.position.x + style->get_margin(SIDE_LEFT), cb_rect.position.y + style->get_margin(SIDE_TOP))); + } else { + tabs.write[p_index].cb_rect = Rect2(); } } diff --git a/scene/resources/font.cpp b/scene/resources/font.cpp index 37d9d57722..104187775d 100644 --- a/scene/resources/font.cpp +++ b/scene/resources/font.cpp @@ -149,6 +149,25 @@ bool Font::_is_cyclic(const Ref<Font> &p_f, int p_depth) const { return false; } +bool Font::_is_base_cyclic(const Ref<Font> &p_f, int p_depth) const { + ERR_FAIL_COND_V(p_depth > MAX_FALLBACK_DEPTH, true); + if (p_f.is_null()) { + return false; + } + if (p_f == this) { + return true; + } + Ref<FontVariation> fv = p_f; + if (fv.is_valid()) { + return _is_base_cyclic(fv->get_base_font(), p_depth + 1); + } + Ref<SystemFont> fs = p_f; + if (fs.is_valid()) { + return _is_base_cyclic(fs->get_base_font(), p_depth + 1); + } + return false; +} + void Font::reset_state() { _invalidate_rids(); } @@ -2910,7 +2929,7 @@ Ref<Font> FontVariation::_get_base_font_or_default() const { } Ref<Font> f = theme->get_font(theme_name, E); - if (f == this) { + if (_is_base_cyclic(f, 0)) { continue; } if (f.is_valid()) { @@ -2922,7 +2941,7 @@ Ref<Font> FontVariation::_get_base_font_or_default() const { } Ref<Font> f = global_context->get_fallback_theme()->get_font(theme_name, StringName()); - if (f != this) { + if (!_is_base_cyclic(f, 0)) { if (f.is_valid()) { theme_font = f; theme_font->connect_changed(callable_mp(reinterpret_cast<Font *>(const_cast<FontVariation *>(this)), &Font::_invalidate_rids), CONNECT_REFERENCE_COUNTED); @@ -3273,7 +3292,7 @@ Ref<Font> SystemFont::_get_base_font_or_default() const { } Ref<Font> f = theme->get_font(theme_name, E); - if (f == this) { + if (_is_base_cyclic(f, 0)) { continue; } if (f.is_valid()) { @@ -3285,7 +3304,7 @@ Ref<Font> SystemFont::_get_base_font_or_default() const { } Ref<Font> f = global_context->get_fallback_theme()->get_font(theme_name, StringName()); - if (f != this) { + if (!_is_base_cyclic(f, 0)) { if (f.is_valid()) { theme_font = f; theme_font->connect_changed(callable_mp(reinterpret_cast<Font *>(const_cast<SystemFont *>(this)), &Font::_invalidate_rids), CONNECT_REFERENCE_COUNTED); diff --git a/scene/resources/font.h b/scene/resources/font.h index 1878539a3f..68c391c35e 100644 --- a/scene/resources/font.h +++ b/scene/resources/font.h @@ -99,8 +99,6 @@ protected: virtual void _update_rids_fb(const Ref<Font> &p_f, int p_depth) const; virtual void _update_rids() const; - virtual bool _is_cyclic(const Ref<Font> &p_f, int p_depth) const; - virtual void reset_state() override; #ifndef DISABLE_DEPRECATED @@ -110,6 +108,8 @@ protected: #endif public: + virtual bool _is_cyclic(const Ref<Font> &p_f, int p_depth) const; + virtual bool _is_base_cyclic(const Ref<Font> &p_f, int p_depth) const; virtual void _invalidate_rids(); static constexpr int DEFAULT_FONT_SIZE = 16; @@ -494,6 +494,7 @@ protected: virtual void reset_state() override; public: + virtual Ref<Font> get_base_font() const { return base_font; } virtual Ref<Font> _get_base_font_or_default() const; virtual void set_antialiasing(TextServer::FontAntialiasing p_antialiasing); diff --git a/servers/rendering/dummy/storage/light_storage.cpp b/servers/rendering/dummy/storage/light_storage.cpp new file mode 100644 index 0000000000..443e047b37 --- /dev/null +++ b/servers/rendering/dummy/storage/light_storage.cpp @@ -0,0 +1,86 @@ +/**************************************************************************/ +/* light_storage.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 "light_storage.h" + +using namespace RendererDummy; + +LightStorage *LightStorage::singleton = nullptr; + +LightStorage *LightStorage::get_singleton() { + return singleton; +} + +LightStorage::LightStorage() { + singleton = this; +} + +LightStorage::~LightStorage() { + singleton = nullptr; +} + +bool LightStorage::free(RID p_rid) { + if (owns_lightmap(p_rid)) { + lightmap_free(p_rid); + return true; + } else if (owns_lightmap_instance(p_rid)) { + lightmap_instance_free(p_rid); + return true; + } + + return false; +} + +/* LIGHTMAP API */ + +RID LightStorage::lightmap_allocate() { + return lightmap_owner.allocate_rid(); +} + +void LightStorage::lightmap_initialize(RID p_lightmap) { + lightmap_owner.initialize_rid(p_lightmap, Lightmap()); +} + +void LightStorage::lightmap_free(RID p_rid) { + lightmap_set_textures(p_rid, RID(), false); + lightmap_owner.free(p_rid); +} + +/* LIGHTMAP INSTANCE */ + +RID LightStorage::lightmap_instance_create(RID p_lightmap) { + LightmapInstance li; + li.lightmap = p_lightmap; + return lightmap_instance_owner.make_rid(li); +} + +void LightStorage::lightmap_instance_free(RID p_lightmap) { + lightmap_instance_owner.free(p_lightmap); +} diff --git a/servers/rendering/dummy/storage/light_storage.h b/servers/rendering/dummy/storage/light_storage.h index 0a9602b603..c3b63cdbf6 100644 --- a/servers/rendering/dummy/storage/light_storage.h +++ b/servers/rendering/dummy/storage/light_storage.h @@ -36,7 +36,29 @@ namespace RendererDummy { class LightStorage : public RendererLightStorage { +private: + static LightStorage *singleton; + /* LIGHTMAP */ + struct Lightmap { + // dummy lightmap, no data + }; + + mutable RID_Owner<Lightmap, true> lightmap_owner; + /* LIGHTMAP INSTANCE */ + + struct LightmapInstance { + RID lightmap; + }; + + mutable RID_Owner<LightmapInstance> lightmap_instance_owner; + public: + static LightStorage *get_singleton(); + + LightStorage(); + virtual ~LightStorage(); + + bool free(RID p_rid); /* Light API */ virtual RID directional_light_allocate() override { return RID(); } @@ -146,9 +168,11 @@ public: /* LIGHTMAP CAPTURE */ - virtual RID lightmap_allocate() override { return RID(); } - virtual void lightmap_initialize(RID p_rid) override {} - virtual void lightmap_free(RID p_rid) override {} + bool owns_lightmap(RID p_rid) { return lightmap_owner.owns(p_rid); } + + virtual RID lightmap_allocate() override; + virtual void lightmap_initialize(RID p_rid) override; + virtual void lightmap_free(RID p_rid) override; virtual void lightmap_set_textures(RID p_lightmap, RID p_light, bool p_uses_spherical_haromics) override {} virtual void lightmap_set_probe_bounds(RID p_lightmap, const AABB &p_bounds) override {} @@ -167,8 +191,10 @@ public: /* LIGHTMAP INSTANCE */ - RID lightmap_instance_create(RID p_lightmap) override { return RID(); } - void lightmap_instance_free(RID p_lightmap) override {} + bool owns_lightmap_instance(RID p_rid) { return lightmap_instance_owner.owns(p_rid); } + + RID lightmap_instance_create(RID p_lightmap) override; + void lightmap_instance_free(RID p_lightmap) override; void lightmap_instance_set_transform(RID p_lightmap, const Transform3D &p_transform) override {} /* SHADOW ATLAS API */ diff --git a/servers/rendering/dummy/storage/utilities.h b/servers/rendering/dummy/storage/utilities.h index 6e8af9afac..ae83547afd 100644 --- a/servers/rendering/dummy/storage/utilities.h +++ b/servers/rendering/dummy/storage/utilities.h @@ -31,6 +31,7 @@ #ifndef UTILITIES_DUMMY_H #define UTILITIES_DUMMY_H +#include "light_storage.h" #include "material_storage.h" #include "mesh_storage.h" #include "servers/rendering/storage/utilities.h" @@ -55,12 +56,16 @@ public: return RS::INSTANCE_MESH; } else if (RendererDummy::MeshStorage::get_singleton()->owns_multimesh(p_rid)) { return RS::INSTANCE_MULTIMESH; + } else if (RendererDummy::LightStorage::get_singleton()->owns_lightmap(p_rid)) { + return RS::INSTANCE_LIGHTMAP; } return RS::INSTANCE_NONE; } virtual bool free(RID p_rid) override { - if (RendererDummy::TextureStorage::get_singleton()->owns_texture(p_rid)) { + if (RendererDummy::LightStorage::get_singleton()->free(p_rid)) { + return true; + } else if (RendererDummy::TextureStorage::get_singleton()->owns_texture(p_rid)) { RendererDummy::TextureStorage::get_singleton()->texture_free(p_rid); return true; } else if (RendererDummy::MeshStorage::get_singleton()->owns_mesh(p_rid)) { diff --git a/thirdparty/enet/godot.cpp b/thirdparty/enet/godot.cpp index ecf0a7e6dd..9e766e52c3 100644 --- a/thirdparty/enet/godot.cpp +++ b/thirdparty/enet/godot.cpp @@ -299,7 +299,12 @@ public: Error sendto(const uint8_t *p_buffer, int p_len, int &r_sent, IPAddress p_ip, uint16_t p_port) { String key = String(p_ip) + ":" + itos(p_port); - ERR_FAIL_COND_V(!peers.has(key), ERR_UNAVAILABLE); + if (unlikely(!peers.has(key))) { + // The peer might have been disconnected due to a DTLS error. + // We need to wait for it to time out, just mark the packet as sent. + r_sent = p_len; + return OK; + } Ref<PacketPeerDTLS> peer = peers[key]; Error err = peer->put_packet(p_buffer, p_len); if (err == OK) { @@ -307,7 +312,10 @@ public: } else if (err == ERR_BUSY) { r_sent = 0; } else { - r_sent = -1; + // The peer might have been disconnected due to a DTLS error. + // We need to wait for it to time out, just mark the packet as sent. + r_sent = p_len; + return OK; } return err; } @@ -331,7 +339,7 @@ public: Error err = ERR_BUSY; // TODO this needs to be fair! - for (KeyValue<String, Ref<PacketPeerDTLS>> & E : peers) { + for (KeyValue<String, Ref<PacketPeerDTLS>> &E : peers) { Ref<PacketPeerDTLS> peer = E.value; peer->poll(); @@ -349,7 +357,8 @@ public: if (err != OK || p_len < r_read) { // Something wrong with this peer, removing it. remove.push_back(E.key); - err = FAILED; + err = ERR_BUSY; + r_read = 0; continue; } @@ -549,7 +558,7 @@ int enet_socket_receive(ENetSocket socket, ENetAddress *address, ENetBuffer *buf return read; } -int enet_socket_get_address (ENetSocket socket, ENetAddress * address) { +int enet_socket_get_address(ENetSocket socket, ENetAddress *address) { IPAddress ip; uint16_t port; ENetGodotSocket *sock = (ENetGodotSocket *)socket; |