summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/io/resource_loader.cpp7
-rw-r--r--doc/classes/AnimationNodeAnimation.xml1
-rw-r--r--doc/classes/AudioStreamPlayer.xml2
-rw-r--r--doc/classes/Dictionary.xml1
-rw-r--r--doc/classes/EditorPlugin.xml2
-rw-r--r--doc/classes/EditorSettings.xml3
-rw-r--r--doc/classes/JSON.xml10
-rw-r--r--doc/classes/MultiMesh.xml2
-rw-r--r--doc/classes/ProjectSettings.xml4
-rw-r--r--doc/classes/ResourceImporterTexture.xml4
-rw-r--r--doc/classes/SkeletonModifier3D.xml2
-rw-r--r--doc/classes/TileMap.xml2
-rw-r--r--doc/classes/Window.xml7
-rw-r--r--drivers/d3d12/rendering_device_driver_d3d12.cpp1
-rw-r--r--editor/editor_data.cpp10
-rw-r--r--editor/editor_data.h1
-rw-r--r--editor/editor_node.cpp801
-rw-r--r--editor/editor_node.h42
-rw-r--r--editor/plugins/visual_shader_editor_plugin.cpp12
-rw-r--r--editor/project_manager/project_dialog.cpp27
-rw-r--r--editor/project_manager/project_dialog.h3
-rw-r--r--editor/project_manager/quick_settings_dialog.cpp19
-rw-r--r--editor/project_manager/quick_settings_dialog.h8
-rw-r--r--editor/scene_tree_dock.cpp7
-rw-r--r--editor/themes/editor_theme_manager.cpp2
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp52
-rw-r--r--modules/gdscript/gdscript_analyzer.h6
-rw-r--r--modules/gdscript/gdscript_cache.cpp4
-rw-r--r--modules/interactive_music/doc_classes/AudioStreamSynchronized.xml2
-rw-r--r--modules/interactive_music/editor/audio_stream_interactive_editor_plugin.cpp6
-rw-r--r--modules/multiplayer/editor/replication_editor.cpp2
-rw-r--r--modules/multiplayer/scene_multiplayer.cpp6
-rw-r--r--modules/openxr/doc_classes/OpenXRInterface.xml2
-rw-r--r--modules/websocket/wsl_peer.cpp5
-rw-r--r--platform/ios/doc_classes/EditorExportPlatformIOS.xml6
-rw-r--r--platform/linuxbsd/x11/display_server_x11.cpp21
-rw-r--r--platform/macos/SCsub12
-rw-r--r--platform/macos/doc_classes/EditorExportPlatformMacOS.xml6
-rw-r--r--platform/macos/gl_manager_macos_legacy.h1
-rw-r--r--platform/macos/gl_manager_macos_legacy.mm20
-rw-r--r--platform/macos/os_macos.mm7
-rw-r--r--platform/windows/display_server_windows.cpp65
-rw-r--r--scene/2d/tile_map.cpp2
-rw-r--r--scene/3d/skeleton_3d.cpp2
-rw-r--r--scene/animation/animation_blend_tree.cpp1
-rw-r--r--scene/animation/animation_player.cpp5
-rw-r--r--scene/gui/graph_edit.cpp14
-rw-r--r--scene/gui/popup_menu.cpp3
-rw-r--r--scene/gui/tab_bar.cpp2
-rw-r--r--scene/resources/font.cpp27
-rw-r--r--scene/resources/font.h5
-rw-r--r--servers/rendering/dummy/storage/light_storage.cpp86
-rw-r--r--servers/rendering/dummy/storage/light_storage.h36
-rw-r--r--servers/rendering/dummy/storage/utilities.h7
-rw-r--r--thirdparty/enet/godot.cpp19
55 files changed, 867 insertions, 545 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 &apos;Extract TileMap layers as individual TileMapLayer nodes&apos;." 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..f24fa344ae 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -1042,11 +1042,17 @@ void EditorNode::_resources_reimporting(const Vector<String> &p_resources) {
// because if a mesh is present in an inherited scene, the resource will be modified in
// the inherited scene. Then, get_modified_properties_for_node will return the mesh property,
// which will trigger a recopy of the previous mesh, preventing the reload.
+ scenes_modification_table.clear();
+ List<String> scenes;
for (const String &res_path : p_resources) {
if (ResourceLoader::get_resource_type(res_path) == "PackedScene") {
- preload_reimporting_with_path_in_edited_scenes(res_path);
+ scenes.push_back(res_path);
}
}
+
+ if (scenes.size() > 0) {
+ preload_reimporting_with_path_in_edited_scenes(scenes);
+ }
}
void EditorNode::_resources_reimported(const Vector<String> &p_resources) {
@@ -1080,9 +1086,10 @@ void EditorNode::_resources_reimported(const Vector<String> &p_resources) {
for (const String &E : scenes) {
reload_scene(E);
- reload_instances_with_path_in_edited_scenes(E);
}
+ reload_instances_with_path_in_edited_scenes();
+
_set_current_scene_nocheck(current_tab);
}
@@ -3870,17 +3877,18 @@ void EditorNode::_set_current_scene_nocheck(int p_idx) {
changing_scene = true;
editor_data.save_edited_scene_state(editor_selection, &editor_history, _get_main_scene_state());
- if (get_editor_data().get_edited_scene_root()) {
- if (get_editor_data().get_edited_scene_root()->get_parent() == scene_root) {
- scene_root->remove_child(get_editor_data().get_edited_scene_root());
- }
- }
+ Node *old_scene = get_editor_data().get_edited_scene_root();
editor_selection->clear();
editor_data.set_edited_scene(p_idx);
Node *new_scene = editor_data.get_edited_scene_root();
+ // Remove the scene only if it's a new scene, preventing performance issues when adding and removing scenes.
+ if (old_scene && new_scene != old_scene && old_scene->get_parent() == scene_root) {
+ scene_root->remove_child(old_scene);
+ }
+
if (Popup *p = Object::cast_to<Popup>(new_scene)) {
p->show();
}
@@ -4154,21 +4162,23 @@ HashMap<StringName, Variant> EditorNode::get_modified_properties_for_node(Node *
p_node->get_property_list(&pinfo);
for (const PropertyInfo &E : pinfo) {
if (E.usage & PROPERTY_USAGE_STORAGE) {
+ bool node_reference = (E.type == Variant::OBJECT && E.hint == PROPERTY_HINT_NODE_TYPE);
+ if (p_node_references_only && !node_reference) {
+ continue;
+ }
bool is_valid_revert = false;
Variant revert_value = EditorPropertyRevert::get_property_revert_value(p_node, E.name, &is_valid_revert);
Variant current_value = p_node->get(E.name);
if (is_valid_revert) {
if (PropertyUtils::is_property_value_different(p_node, current_value, revert_value)) {
// If this property is a direct node reference, save a NodePath instead to prevent corrupted references.
- if (E.type == Variant::OBJECT && E.hint == PROPERTY_HINT_NODE_TYPE) {
+ if (node_reference) {
Node *target_node = Object::cast_to<Node>(current_value);
if (target_node) {
modified_property_map[E.name] = p_node->get_path_to(target_node);
}
} else {
- if (!p_node_references_only) {
- modified_property_map[E.name] = current_value;
- }
+ modified_property_map[E.name] = current_value;
}
}
}
@@ -4178,6 +4188,27 @@ HashMap<StringName, Variant> EditorNode::get_modified_properties_for_node(Node *
return modified_property_map;
}
+HashMap<StringName, Variant> EditorNode::get_modified_properties_reference_to_nodes(Node *p_node, List<Node *> &p_nodes_referenced_by) {
+ HashMap<StringName, Variant> modified_property_map;
+
+ List<PropertyInfo> pinfo;
+ p_node->get_property_list(&pinfo);
+ for (const PropertyInfo &E : pinfo) {
+ if (E.usage & PROPERTY_USAGE_STORAGE) {
+ if (E.type != Variant::OBJECT || E.hint != PROPERTY_HINT_NODE_TYPE) {
+ continue;
+ }
+ Variant current_value = p_node->get(E.name);
+ Node *target_node = Object::cast_to<Node>(current_value);
+ if (target_node && p_nodes_referenced_by.find(target_node)) {
+ modified_property_map[E.name] = p_node->get_path_to(target_node);
+ }
+ }
+ }
+
+ return modified_property_map;
+}
+
void EditorNode::update_node_from_node_modification_entry(Node *p_node, ModificationNodeEntry &p_node_modification) {
if (p_node) {
// First, attempt to restore the script property since it may affect the get_property_list method.
@@ -4264,28 +4295,6 @@ void EditorNode::update_node_from_node_modification_entry(Node *p_node, Modifica
}
}
-void EditorNode::update_node_reference_modification_table_for_node(
- Node *p_root,
- Node *p_node,
- List<Node *> p_excluded_nodes,
- HashMap<NodePath, ModificationNodeEntry> &p_modification_table) {
- if (!p_excluded_nodes.find(p_node)) {
- HashMap<StringName, Variant> modified_properties = get_modified_properties_for_node(p_node, false);
-
- if (!modified_properties.is_empty()) {
- ModificationNodeEntry modification_node_entry;
- modification_node_entry.property_table = modified_properties;
-
- p_modification_table[p_root->get_path_to(p_node)] = modification_node_entry;
- }
-
- for (int i = 0; i < p_node->get_child_count(); i++) {
- Node *child = p_node->get_child(i);
- update_node_reference_modification_table_for_node(p_root, child, p_excluded_nodes, p_modification_table);
- }
- }
-}
-
bool EditorNode::is_additional_node_in_scene(Node *p_edited_scene, Node *p_reimported_root, Node *p_node) {
if (p_node == p_reimported_root) {
return false;
@@ -4328,11 +4337,39 @@ bool EditorNode::is_additional_node_in_scene(Node *p_edited_scene, Node *p_reimp
void EditorNode::get_preload_scene_modification_table(
Node *p_edited_scene,
Node *p_reimported_root,
- Node *p_node, HashMap<NodePath, ModificationNodeEntry> &p_modification_table) {
- // Only take the nodes that are in the base imported scene. The additional nodes will be managed
- // after the resources are reimported. It's not important to check which property has
- // changed on nodes that will not be reimported because they are not part of the reimported scene.
- if (!is_additional_node_in_scene(p_edited_scene, p_reimported_root, p_node)) {
+ Node *p_node, InstanceModificationsEntry &p_instance_modifications) {
+ if (is_additional_node_in_scene(p_edited_scene, p_reimported_root, p_node)) {
+ // Only save additional nodes which have an owner since this was causing issues transient ownerless nodes
+ // which get recreated upon scene tree entry.
+ // For now instead, assume all ownerless nodes are transient and will have to be recreated.
+ if (p_node->get_owner()) {
+ HashMap<StringName, Variant> modified_properties = get_modified_properties_for_node(p_node, true);
+ if (p_node->get_owner() == p_edited_scene) {
+ AdditiveNodeEntry new_additive_node_entry;
+ new_additive_node_entry.node = p_node;
+ new_additive_node_entry.parent = p_reimported_root->get_path_to(p_node->get_parent());
+ new_additive_node_entry.owner = p_node->get_owner();
+ new_additive_node_entry.index = p_node->get_index();
+
+ Node2D *node_2d = Object::cast_to<Node2D>(p_node);
+ if (node_2d) {
+ new_additive_node_entry.transform_2d = node_2d->get_transform();
+ }
+ Node3D *node_3d = Object::cast_to<Node3D>(p_node);
+ if (node_3d) {
+ new_additive_node_entry.transform_3d = node_3d->get_transform();
+ }
+
+ p_instance_modifications.addition_list.push_back(new_additive_node_entry);
+ }
+ if (!modified_properties.is_empty()) {
+ ModificationNodeEntry modification_node_entry;
+ modification_node_entry.property_table = modified_properties;
+
+ p_instance_modifications.modifications[p_reimported_root->get_path_to(p_node)] = modification_node_entry;
+ }
+ }
+ } else {
HashMap<StringName, Variant> modified_properties = get_modified_properties_for_node(p_node, false);
// Find all valid connections to other nodes.
@@ -4391,58 +4428,56 @@ void EditorNode::get_preload_scene_modification_table(
modification_node_entry.connections_from = valid_connections_from;
modification_node_entry.groups = groups;
- p_modification_table[p_reimported_root->get_path_to(p_node)] = modification_node_entry;
+ p_instance_modifications.modifications[p_reimported_root->get_path_to(p_node)] = modification_node_entry;
}
}
for (int i = 0; i < p_node->get_child_count(); i++) {
- Node *child = p_node->get_child(i);
- get_preload_scene_modification_table(p_edited_scene, p_reimported_root, child, p_modification_table);
+ get_preload_scene_modification_table(p_edited_scene, p_reimported_root, p_node->get_child(i), p_instance_modifications);
}
}
-void EditorNode::update_reimported_diff_data_for_additional_nodes(
- Node *p_edited_scene,
- Node *p_reimported_root,
+void EditorNode::get_preload_modifications_reference_to_nodes(
+ Node *p_root,
Node *p_node,
- HashMap<NodePath, ModificationNodeEntry> &p_modification_table,
- List<AdditiveNodeEntry> &p_addition_list) {
- if (is_additional_node_in_scene(p_edited_scene, p_reimported_root, p_node)) {
- // Only save additional nodes which have an owner since this was causing issues transient ownerless nodes
- // which get recreated upon scene tree entry.
- // For now instead, assume all ownerless nodes are transient and will have to be recreated.
- if (p_node->get_owner()) {
- HashMap<StringName, Variant> modified_properties = get_modified_properties_for_node(p_node, true);
- if (p_node->get_owner() == p_edited_scene) {
- AdditiveNodeEntry new_additive_node_entry;
- new_additive_node_entry.node = p_node;
- new_additive_node_entry.parent = p_reimported_root->get_path_to(p_node->get_parent());
- new_additive_node_entry.owner = p_node->get_owner();
- new_additive_node_entry.index = p_node->get_index();
+ List<Node *> &p_excluded_nodes,
+ List<Node *> &p_instance_list_with_children,
+ HashMap<NodePath, ModificationNodeEntry> &p_modification_table) {
+ if (!p_excluded_nodes.find(p_node)) {
+ HashMap<StringName, Variant> modified_properties = get_modified_properties_reference_to_nodes(p_node, p_instance_list_with_children);
- Node2D *node_2d = Object::cast_to<Node2D>(p_node);
- if (node_2d) {
- new_additive_node_entry.transform_2d = node_2d->get_relative_transform_to_parent(node_2d->get_parent());
- }
- Node3D *node_3d = Object::cast_to<Node3D>(p_node);
- if (node_3d) {
- new_additive_node_entry.transform_3d = node_3d->get_relative_transform(node_3d->get_parent());
- }
+ if (!modified_properties.is_empty()) {
+ ModificationNodeEntry modification_node_entry;
+ modification_node_entry.property_table = modified_properties;
- p_addition_list.push_back(new_additive_node_entry);
- }
- if (!modified_properties.is_empty()) {
- ModificationNodeEntry modification_node_entry;
- modification_node_entry.property_table = modified_properties;
+ p_modification_table[p_root->get_path_to(p_node)] = modification_node_entry;
+ }
- p_modification_table[p_reimported_root->get_path_to(p_node)] = modification_node_entry;
- }
+ for (int i = 0; i < p_node->get_child_count(); i++) {
+ get_preload_modifications_reference_to_nodes(p_root, p_node->get_child(i), p_excluded_nodes, p_instance_list_with_children, p_modification_table);
}
}
+}
+void EditorNode::get_children_nodes(Node *p_node, List<Node *> &p_nodes) {
for (int i = 0; i < p_node->get_child_count(); i++) {
Node *child = p_node->get_child(i);
- update_reimported_diff_data_for_additional_nodes(p_edited_scene, p_reimported_root, child, p_modification_table, p_addition_list);
+ p_nodes.push_back(child);
+ get_children_nodes(child, p_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++) {
+ replace_history_reimported_nodes(p_original_root_node, p_new_root_node, p_node->get_child(i));
}
}
@@ -5816,25 +5851,6 @@ void EditorNode::reload_scene(const String &p_path) {
scene_tabs->set_current_tab(current_tab);
}
-void EditorNode::get_edited_scene_map(const String &p_instance_path, HashMap<int, List<Node *>> &p_edited_scene_map) {
- // Walk through each opened scene to get a global list of all instances which match
- // the current reimported scenes.
- for (int i = 0; i < editor_data.get_edited_scene_count(); i++) {
- if (editor_data.get_scene_path(i) == p_instance_path) {
- continue;
- }
- Node *edited_scene_root = editor_data.get_edited_scene_root(i);
-
- if (edited_scene_root) {
- List<Node *> valid_nodes;
- find_all_instances_inheriting_path_in_node(edited_scene_root, edited_scene_root, p_instance_path, valid_nodes);
- if (valid_nodes.size() > 0) {
- p_edited_scene_map[i] = valid_nodes;
- }
- }
- }
-}
-
void EditorNode::find_all_instances_inheriting_path_in_node(Node *p_root, Node *p_node, const String &p_instance_path, List<Node *> &p_instance_list) {
String scene_file_path = p_node->get_scene_file_path();
@@ -5857,386 +5873,423 @@ void EditorNode::find_all_instances_inheriting_path_in_node(Node *p_root, Node *
}
for (int i = 0; i < p_node->get_child_count(); i++) {
- Node *child = p_node->get_child(i);
- find_all_instances_inheriting_path_in_node(p_root, child, p_instance_path, p_instance_list);
+ find_all_instances_inheriting_path_in_node(p_root, p_node->get_child(i), p_instance_path, p_instance_list);
}
}
-void EditorNode::preload_reimporting_with_path_in_edited_scenes(const String &p_instance_path) {
- HashMap<int, List<Node *>> edited_scene_map;
- scenes_modification_table.clear();
+void EditorNode::preload_reimporting_with_path_in_edited_scenes(const List<String> &p_scenes) {
+ EditorProgress progress("preload_reimporting_scene", TTR("Preparing scenes for reload"), editor_data.get_edited_scene_count());
+
+ int original_edited_scene_idx = editor_data.get_edited_scene();
- get_edited_scene_map(p_instance_path, edited_scene_map);
+ // Walk through each opened scene to get a global list of all instances which match
+ // the current reimported scenes.
+ for (int current_scene_idx = 0; current_scene_idx < editor_data.get_edited_scene_count(); current_scene_idx++) {
+ progress.step(vformat(TTR("Analyzing scene %s"), editor_data.get_scene_title(current_scene_idx)), current_scene_idx);
- if (edited_scene_map.size() > 0) {
- scenes_modification_table.clear();
+ Node *edited_scene_root = editor_data.get_edited_scene_root(current_scene_idx);
+
+ if (edited_scene_root) {
+ SceneModificationsEntry scene_motifications;
- int original_edited_scene_idx = editor_data.get_edited_scene();
- Node *original_edited_scene_root = editor_data.get_edited_scene_root();
+ for (const String &instance_path : p_scenes) {
+ if (editor_data.get_scene_path(current_scene_idx) == instance_path) {
+ continue;
+ }
- // Prevent scene roots with the same name from being in the tree at the same time.
- scene_root->remove_child(original_edited_scene_root);
+ List<Node *> instance_list;
+ find_all_instances_inheriting_path_in_node(edited_scene_root, edited_scene_root, instance_path, instance_list);
+ if (instance_list.size() > 0) {
+ editor_data.set_edited_scene(current_scene_idx);
- for (const KeyValue<int, List<Node *>> &edited_scene_map_elem : edited_scene_map) {
- // Set the current scene.
- int current_scene_idx = edited_scene_map_elem.key;
- editor_data.set_edited_scene(current_scene_idx);
- Node *current_edited_scene = editor_data.get_edited_scene_root(current_scene_idx);
+ List<Node *> instance_list_with_children;
+ for (Node *original_node : instance_list) {
+ InstanceModificationsEntry instance_modifications;
- // Make sure the node is in the tree so that editor_selection can add node smoothly.
- scene_root->add_child(current_edited_scene);
+ // Fetching all the modified properties of the nodes reimported scene.
+ get_preload_scene_modification_table(edited_scene_root, original_node, original_node, instance_modifications);
- for (Node *original_node : edited_scene_map_elem.value) {
- // Fetching all the modified properties of the nodes reimported scene.
- HashMap<NodePath, ModificationNodeEntry> modification_table;
- get_preload_scene_modification_table(current_edited_scene, original_node, original_node, modification_table);
+ instance_modifications.original_node = original_node;
+ instance_modifications.instance_path = instance_path;
+ scene_motifications.instance_list.push_back(instance_modifications);
- if (modification_table.size() > 0) {
- NodePath scene_path_to_node = current_edited_scene->get_path_to(original_node);
- scenes_modification_table[current_scene_idx][scene_path_to_node] = modification_table;
+ instance_list_with_children.push_back(original_node);
+ get_children_nodes(original_node, instance_list_with_children);
+ }
+
+ // Search the scene to find nodes that references the nodes will be recreated.
+ get_preload_modifications_reference_to_nodes(edited_scene_root, edited_scene_root, instance_list, instance_list_with_children, scene_motifications.other_instances_modifications);
}
}
- scene_root->remove_child(current_edited_scene);
+ if (scene_motifications.instance_list.size() > 0) {
+ scenes_modification_table[current_scene_idx] = scene_motifications;
+ }
}
-
- editor_data.set_edited_scene(original_edited_scene_idx);
- scene_root->add_child(original_edited_scene_root);
}
+
+ editor_data.set_edited_scene(original_edited_scene_idx);
+
+ progress.step(TTR("Preparation done."), editor_data.get_edited_scene_count());
}
-void EditorNode::reload_instances_with_path_in_edited_scenes(const String &p_instance_path) {
- HashMap<int, List<Node *>> edited_scene_map;
- Array replaced_nodes;
+void EditorNode::reload_instances_with_path_in_edited_scenes() {
+ if (scenes_modification_table.size() == 0) {
+ return;
+ }
+ EditorProgress progress("reloading_scene", TTR("Scenes reloading"), editor_data.get_edited_scene_count());
+ progress.step(TTR("Reloading..."), 0, true);
- get_edited_scene_map(p_instance_path, edited_scene_map);
+ Error err;
+ Array replaced_nodes;
+ HashMap<String, Ref<PackedScene>> local_scene_cache;
- if (edited_scene_map.size() > 0) {
- // Reload the new instance.
- Error err;
- Ref<PackedScene> instance_scene_packed_scene = ResourceLoader::load(p_instance_path, "", ResourceFormatLoader::CACHE_MODE_REPLACE, &err);
+ // Reload the new instances.
+ for (KeyValue<int, SceneModificationsEntry> &scene_modifications_elem : scenes_modification_table) {
+ for (InstanceModificationsEntry instance_modifications : scene_modifications_elem.value.instance_list) {
+ if (!local_scene_cache.has(instance_modifications.instance_path)) {
+ Ref<PackedScene> instance_scene_packed_scene = ResourceLoader::load(instance_modifications.instance_path, "", ResourceFormatLoader::CACHE_MODE_REPLACE, &err);
- ERR_FAIL_COND(err != OK);
- ERR_FAIL_COND(instance_scene_packed_scene.is_null());
+ ERR_FAIL_COND(err != OK);
+ ERR_FAIL_COND(instance_scene_packed_scene.is_null());
- HashMap<String, Ref<PackedScene>> local_scene_cache;
- local_scene_cache[p_instance_path] = instance_scene_packed_scene;
+ local_scene_cache[instance_modifications.instance_path] = instance_scene_packed_scene;
+ }
+ }
+ }
- // Save the current scene state/selection in case of lost.
- Dictionary editor_state = _get_main_scene_state();
- editor_data.save_edited_scene_state(editor_selection, &editor_history, editor_state);
- editor_selection->clear();
+ // Save the current scene state/selection in case of lost.
+ Dictionary editor_state = _get_main_scene_state();
+ editor_data.save_edited_scene_state(editor_selection, &editor_history, editor_state);
+ editor_selection->clear();
- int original_edited_scene_idx = editor_data.get_edited_scene();
- Node *original_edited_scene_root = editor_data.get_edited_scene_root();
+ int original_edited_scene_idx = editor_data.get_edited_scene();
- // Prevent scene roots with the same name from being in the tree at the same time.
- scene_root->remove_child(original_edited_scene_root);
+ for (KeyValue<int, SceneModificationsEntry> &scene_modifications_elem : scenes_modification_table) {
+ // Set the current scene.
+ int current_scene_idx = scene_modifications_elem.key;
+ SceneModificationsEntry *scene_modifications = &scene_modifications_elem.value;
- for (const KeyValue<int, List<Node *>> &edited_scene_map_elem : edited_scene_map) {
- // Set the current scene.
- int current_scene_idx = edited_scene_map_elem.key;
- editor_data.set_edited_scene(current_scene_idx);
- Node *current_edited_scene = editor_data.get_edited_scene_root(current_scene_idx);
+ editor_data.set_edited_scene(current_scene_idx);
+ Node *current_edited_scene = editor_data.get_edited_scene_root(current_scene_idx);
- // Make sure the node is in the tree so that editor_selection can add node smoothly.
+ // Make sure the node is in the tree so that editor_selection can add node smoothly.
+ if (original_edited_scene_idx != current_scene_idx) {
+ // Prevent scene roots with the same name from being in the tree at the same time.
+ Node *original_edited_scene_root = editor_data.get_edited_scene_root(original_edited_scene_idx);
+ if (original_edited_scene_root && original_edited_scene_root->get_name() == current_edited_scene->get_name()) {
+ scene_root->remove_child(original_edited_scene_root);
+ }
scene_root->add_child(current_edited_scene);
+ }
- // Restore the state so that the selection can be updated.
- editor_state = editor_data.restore_edited_scene_state(editor_selection, &editor_history);
+ // Restore the state so that the selection can be updated.
+ editor_state = editor_data.restore_edited_scene_state(editor_selection, &editor_history);
- int current_history_id = editor_data.get_current_edited_scene_history_id();
- bool is_unsaved = EditorUndoRedoManager::get_singleton()->is_history_unsaved(current_history_id);
+ int current_history_id = editor_data.get_current_edited_scene_history_id();
+ bool is_unsaved = EditorUndoRedoManager::get_singleton()->is_history_unsaved(current_history_id);
- // Clear the history for this affected tab.
- EditorUndoRedoManager::get_singleton()->clear_history(false, current_history_id);
+ // Clear the history for this affected tab.
+ EditorUndoRedoManager::get_singleton()->clear_history(false, current_history_id);
- // Update the version
- editor_data.is_scene_changed(current_scene_idx);
+ // Update the version
+ editor_data.is_scene_changed(current_scene_idx);
- // Contains modifications in the edited scene which reference nodes inside of any nodes we will be reimporting.
- HashMap<NodePath, ModificationNodeEntry> edited_scene_global_modification_table;
- update_node_reference_modification_table_for_node(current_edited_scene, current_edited_scene, edited_scene_map_elem.value, edited_scene_global_modification_table);
+ for (InstanceModificationsEntry instance_modifications : scene_modifications->instance_list) {
+ Node *original_node = instance_modifications.original_node;
+ String original_node_file_path = original_node->get_scene_file_path();
+ Ref<PackedScene> instance_scene_packed_scene = local_scene_cache[instance_modifications.instance_path];
- for (Node *original_node : edited_scene_map_elem.value) {
- String original_node_file_path = original_node->get_scene_file_path();
-
- // Load a replacement scene for the node.
- Ref<PackedScene> current_packed_scene;
- Ref<PackedScene> base_packed_scene;
- if (original_node_file_path == p_instance_path) {
- // If the node file name directly matches the scene we're replacing,
- // just load it since we already cached it.
- current_packed_scene = instance_scene_packed_scene;
- } else {
- // Otherwise, check the inheritance chain, reloading and caching any scenes
- // we require along the way.
- List<String> required_load_paths;
+ // Load a replacement scene for the node.
+ Ref<PackedScene> current_packed_scene;
+ Ref<PackedScene> base_packed_scene;
+ if (original_node_file_path == instance_modifications.instance_path) {
+ // If the node file name directly matches the scene we're replacing,
+ // just load it since we already cached it.
+ current_packed_scene = instance_scene_packed_scene;
+ } else {
+ // Otherwise, check the inheritance chain, reloading and caching any scenes
+ // we require along the way.
+ List<String> required_load_paths;
+ // Do we need to check if the paths are empty?
+ if (!original_node_file_path.is_empty()) {
+ required_load_paths.push_front(original_node_file_path);
+ }
+ Ref<SceneState> inherited_state = original_node->get_scene_inherited_state();
+ while (inherited_state.is_valid()) {
+ String inherited_path = inherited_state->get_path();
// Do we need to check if the paths are empty?
- if (!original_node_file_path.is_empty()) {
- required_load_paths.push_front(original_node_file_path);
- }
- Ref<SceneState> inherited_state = original_node->get_scene_inherited_state();
- while (inherited_state.is_valid()) {
- String inherited_path = inherited_state->get_path();
- // Do we need to check if the paths are empty?
- if (!inherited_path.is_empty()) {
- required_load_paths.push_front(inherited_path);
- }
- inherited_state = inherited_state->get_base_scene_state();
+ if (!inherited_path.is_empty()) {
+ required_load_paths.push_front(inherited_path);
}
+ inherited_state = inherited_state->get_base_scene_state();
+ }
- // Ensure the inheritance chain is loaded in the correct order so that cache can
- // be properly updated.
- for (String path : required_load_paths) {
- if (current_packed_scene.is_valid()) {
- base_packed_scene = current_packed_scene;
- }
- if (!local_scene_cache.find(path)) {
- current_packed_scene = ResourceLoader::load(path, "", ResourceFormatLoader::CACHE_MODE_REPLACE_DEEP, &err);
- local_scene_cache[path] = current_packed_scene;
- } else {
- current_packed_scene = local_scene_cache[path];
- }
+ // Ensure the inheritance chain is loaded in the correct order so that cache can
+ // be properly updated.
+ for (String path : required_load_paths) {
+ if (current_packed_scene.is_valid()) {
+ base_packed_scene = current_packed_scene;
+ }
+ if (!local_scene_cache.find(path)) {
+ current_packed_scene = ResourceLoader::load(path, "", ResourceFormatLoader::CACHE_MODE_REPLACE_DEEP, &err);
+ local_scene_cache[path] = current_packed_scene;
+ } else {
+ current_packed_scene = local_scene_cache[path];
}
}
+ }
- ERR_FAIL_COND(current_packed_scene.is_null());
-
- // Instantiate early so that caches cleared on load in SceneState can be rebuilt early.
- Node *instantiated_node = nullptr;
-
- // If we are in a inherit scene, it's easier to create a new base scene and
- // grab the node from there.
- // When scene_path_to_node is '.' and we have scene_inherited_state, it's because
- // it's a muli-level inheritance scene. We should use
- NodePath scene_path_to_node = current_edited_scene->get_path_to(original_node);
- Ref<SceneState> scene_state = current_edited_scene->get_scene_inherited_state();
- if (scene_path_to_node != "." && scene_state.is_valid() && scene_state->get_path() != p_instance_path && scene_state->find_node_by_path(scene_path_to_node) >= 0) {
- Node *root_node = scene_state->instantiate(SceneState::GenEditState::GEN_EDIT_STATE_INSTANCE);
- instantiated_node = root_node->get_node(scene_path_to_node);
-
- if (instantiated_node) {
- if (instantiated_node->get_parent()) {
- // Remove from the root so we can delete it from memory.
- instantiated_node->get_parent()->remove_child(instantiated_node);
- // No need of the additional children that could have been added to the node
- // in the base scene. That will be managed by the 'addition_list' later.
- _remove_all_not_owned_children(instantiated_node, instantiated_node);
- memdelete(root_node);
- }
- } else {
- // Should not happen because we checked with find_node_by_path before, just in case.
+ ERR_FAIL_COND(current_packed_scene.is_null());
+
+ // Instantiate early so that caches cleared on load in SceneState can be rebuilt early.
+ Node *instantiated_node = nullptr;
+
+ // If we are in a inherit scene, it's easier to create a new base scene and
+ // grab the node from there.
+ // When scene_path_to_node is '.' and we have scene_inherited_state, it's because
+ // it's a muli-level inheritance scene. We should use
+ NodePath scene_path_to_node = current_edited_scene->get_path_to(original_node);
+ Ref<SceneState> scene_state = current_edited_scene->get_scene_inherited_state();
+ if (scene_path_to_node != "." && scene_state.is_valid() && scene_state->get_path() != instance_modifications.instance_path && scene_state->find_node_by_path(scene_path_to_node) >= 0) {
+ Node *root_node = scene_state->instantiate(SceneState::GenEditState::GEN_EDIT_STATE_INSTANCE);
+ instantiated_node = root_node->get_node(scene_path_to_node);
+
+ if (instantiated_node) {
+ if (instantiated_node->get_parent()) {
+ // Remove from the root so we can delete it from memory.
+ instantiated_node->get_parent()->remove_child(instantiated_node);
+ // No need of the additional children that could have been added to the node
+ // in the base scene. That will be managed by the 'addition_list' later.
+ _remove_all_not_owned_children(instantiated_node, instantiated_node);
memdelete(root_node);
}
+ } else {
+ // Should not happen because we checked with find_node_by_path before, just in case.
+ memdelete(root_node);
}
+ }
- if (!instantiated_node) {
- // If no base scene was found to create the node, we will use the reimported packed scene directly.
- // But, when the current edited scene is the reimported scene, it's because it's a inherited scene
- // of the reimported scene. In that case, we will not instantiate current_packed_scene, because
- // we would reinstanciate ourself. Using the base scene is better.
- if (current_edited_scene == original_node) {
- if (base_packed_scene.is_valid()) {
- instantiated_node = base_packed_scene->instantiate(PackedScene::GEN_EDIT_STATE_INSTANCE);
- } else {
- instantiated_node = instance_scene_packed_scene->instantiate(PackedScene::GEN_EDIT_STATE_INSTANCE);
- }
+ if (!instantiated_node) {
+ // If no base scene was found to create the node, we will use the reimported packed scene directly.
+ // But, when the current edited scene is the reimported scene, it's because it's a inherited scene
+ // of the reimported scene. In that case, we will not instantiate current_packed_scene, because
+ // we would reinstanciate ourself. Using the base scene is better.
+ if (current_edited_scene == original_node) {
+ if (base_packed_scene.is_valid()) {
+ instantiated_node = base_packed_scene->instantiate(PackedScene::GEN_EDIT_STATE_INSTANCE);
} else {
- instantiated_node = current_packed_scene->instantiate(PackedScene::GEN_EDIT_STATE_INSTANCE);
+ instantiated_node = instance_scene_packed_scene->instantiate(PackedScene::GEN_EDIT_STATE_INSTANCE);
}
+ } else {
+ instantiated_node = current_packed_scene->instantiate(PackedScene::GEN_EDIT_STATE_INSTANCE);
}
- ERR_FAIL_NULL(instantiated_node);
-
- // Walk the tree for the current node and extract relevant diff data, storing it in the modification table.
- // For additional nodes which are part of the current scene, they get added to the addition table.
- HashMap<NodePath, ModificationNodeEntry> modification_table;
- List<AdditiveNodeEntry> addition_list;
- if (scenes_modification_table.has(current_scene_idx) && scenes_modification_table[current_scene_idx].has(scene_path_to_node)) {
- modification_table = scenes_modification_table[current_scene_idx][scene_path_to_node];
- }
- update_reimported_diff_data_for_additional_nodes(current_edited_scene, original_node, original_node, modification_table, addition_list);
+ }
+ ERR_FAIL_NULL(instantiated_node);
- // Disconnect all relevant connections, all connections from and persistent connections to.
- for (const KeyValue<NodePath, ModificationNodeEntry> &modification_table_entry : modification_table) {
- for (Connection conn : modification_table_entry.value.connections_from) {
+ // Disconnect all relevant connections, all connections from and persistent connections to.
+ for (const KeyValue<NodePath, ModificationNodeEntry> &modification_table_entry : instance_modifications.modifications) {
+ for (Connection conn : modification_table_entry.value.connections_from) {
+ conn.signal.get_object()->disconnect(conn.signal.get_name(), conn.callable);
+ }
+ for (ConnectionWithNodePath cwnp : modification_table_entry.value.connections_to) {
+ Connection conn = cwnp.connection;
+ if (conn.flags & CONNECT_PERSIST) {
conn.signal.get_object()->disconnect(conn.signal.get_name(), conn.callable);
}
- for (ConnectionWithNodePath cwnp : modification_table_entry.value.connections_to) {
- Connection conn = cwnp.connection;
- if (conn.flags & CONNECT_PERSIST) {
- conn.signal.get_object()->disconnect(conn.signal.get_name(), conn.callable);
- }
- }
}
+ }
- // Store all the paths for any selected nodes which are ancestors of the node we're replacing.
- List<NodePath> selected_node_paths;
- for (Node *selected_node : editor_selection->get_selected_node_list()) {
- if (selected_node == original_node || original_node->is_ancestor_of(selected_node)) {
- selected_node_paths.push_back(original_node->get_path_to(selected_node));
- editor_selection->remove_node(selected_node);
- }
+ // Store all the paths for any selected nodes which are ancestors of the node we're replacing.
+ List<NodePath> selected_node_paths;
+ for (Node *selected_node : editor_selection->get_selected_node_list()) {
+ if (selected_node == original_node || original_node->is_ancestor_of(selected_node)) {
+ selected_node_paths.push_back(original_node->get_path_to(selected_node));
+ editor_selection->remove_node(selected_node);
}
+ }
- // Remove all nodes which were added as additional elements (they will be restored later).
- for (AdditiveNodeEntry additive_node_entry : addition_list) {
- Node *addition_node = additive_node_entry.node;
- addition_node->get_parent()->remove_child(addition_node);
- }
+ // Remove all nodes which were added as additional elements (they will be restored later).
+ for (AdditiveNodeEntry additive_node_entry : instance_modifications.addition_list) {
+ Node *addition_node = additive_node_entry.node;
+ addition_node->get_parent()->remove_child(addition_node);
+ }
- // Clear ownership of the nodes (kind of hack to workaround an issue with
- // replace_by when called on nodes in other tabs).
- List<Node *> nodes_owned_by_original_node;
- original_node->get_owned_by(original_node, &nodes_owned_by_original_node);
- for (Node *owned_node : nodes_owned_by_original_node) {
- owned_node->set_owner(nullptr);
- }
+ // Clear ownership of the nodes (kind of hack to workaround an issue with
+ // replace_by when called on nodes in other tabs).
+ List<Node *> nodes_owned_by_original_node;
+ original_node->get_owned_by(original_node, &nodes_owned_by_original_node);
+ for (Node *owned_node : nodes_owned_by_original_node) {
+ owned_node->set_owner(nullptr);
+ }
- // Delete all the remaining node children.
- while (original_node->get_child_count()) {
- Node *child = original_node->get_child(0);
+ // 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);
- original_node->remove_child(child);
- child->queue_free();
- }
+ // Delete all the remaining node children.
+ while (original_node->get_child_count()) {
+ Node *child = original_node->get_child(0);
- // Reset the editable instance state.
- bool is_editable = true;
- Node *owner = original_node->get_owner();
- if (owner) {
- is_editable = owner->is_editable_instance(original_node);
- }
+ original_node->remove_child(child);
+ child->queue_free();
+ }
- bool original_node_is_displayed_folded = original_node->is_displayed_folded();
- bool original_node_scene_instance_load_placeholder = original_node->get_scene_instance_load_placeholder();
+ // Reset the editable instance state.
+ bool is_editable = true;
+ Node *owner = original_node->get_owner();
+ if (owner) {
+ is_editable = owner->is_editable_instance(original_node);
+ }
- // Update the name to match
- instantiated_node->set_name(original_node->get_name());
+ bool original_node_is_displayed_folded = original_node->is_displayed_folded();
+ bool original_node_scene_instance_load_placeholder = original_node->get_scene_instance_load_placeholder();
- // Is this replacing the edited root node?
+ // Update the name to match
+ instantiated_node->set_name(original_node->get_name());
- if (current_edited_scene == original_node) {
- // Set the instance as un inherited scene of itself.
- instantiated_node->set_scene_inherited_state(instantiated_node->get_scene_instance_state());
- instantiated_node->set_scene_instance_state(nullptr);
- instantiated_node->set_scene_file_path(original_node_file_path);
- current_edited_scene = instantiated_node;
- editor_data.set_edited_scene_root(current_edited_scene);
- }
+ // Is this replacing the edited root node?
- // Replace the original node with the instantiated version.
- original_node->replace_by(instantiated_node, false);
+ if (current_edited_scene == original_node) {
+ // Set the instance as un inherited scene of itself.
+ instantiated_node->set_scene_inherited_state(instantiated_node->get_scene_instance_state());
+ instantiated_node->set_scene_instance_state(nullptr);
+ instantiated_node->set_scene_file_path(original_node_file_path);
+ current_edited_scene = instantiated_node;
+ editor_data.set_edited_scene_root(current_edited_scene);
- // Mark the old node for deletion.
- original_node->queue_free();
+ if (original_edited_scene_idx == current_scene_idx) {
+ // How that the editor executes a redraw while destroying or progressing the EditorProgress,
+ // it crashes when the root scene has been replaced because the edited scene
+ // was freed and no longer in the scene tree.
+ SceneTreeDock::get_singleton()->set_edited_scene(current_edited_scene);
+ if (get_tree()) {
+ get_tree()->set_edited_scene_root(current_edited_scene);
+ }
+ }
+ }
- // Restore the folded and placeholder state from the original node.
- instantiated_node->set_display_folded(original_node_is_displayed_folded);
- instantiated_node->set_scene_instance_load_placeholder(original_node_scene_instance_load_placeholder);
+ // Replace the original node with the instantiated version.
+ original_node->replace_by(instantiated_node, false);
- if (owner) {
- Ref<SceneState> ss_inst = owner->get_scene_instance_state();
- if (ss_inst.is_valid()) {
- ss_inst->update_instance_resource(p_instance_path, current_packed_scene);
- }
+ // Mark the old node for deletion.
+ original_node->queue_free();
+
+ // Restore the folded and placeholder state from the original node.
+ instantiated_node->set_display_folded(original_node_is_displayed_folded);
+ instantiated_node->set_scene_instance_load_placeholder(original_node_scene_instance_load_placeholder);
- owner->set_editable_instance(instantiated_node, is_editable);
+ if (owner) {
+ Ref<SceneState> ss_inst = owner->get_scene_instance_state();
+ if (ss_inst.is_valid()) {
+ ss_inst->update_instance_resource(instance_modifications.instance_path, current_packed_scene);
}
- // Attempt to re-add all the additional nodes.
- for (AdditiveNodeEntry additive_node_entry : addition_list) {
- Node *parent_node = instantiated_node->get_node_or_null(additive_node_entry.parent);
+ owner->set_editable_instance(instantiated_node, is_editable);
+ }
- if (!parent_node) {
- parent_node = current_edited_scene;
- }
+ // Attempt to re-add all the additional nodes.
+ for (AdditiveNodeEntry additive_node_entry : instance_modifications.addition_list) {
+ Node *parent_node = instantiated_node->get_node_or_null(additive_node_entry.parent);
- parent_node->add_child(additive_node_entry.node);
- parent_node->move_child(additive_node_entry.node, additive_node_entry.index);
- // If the additive node's owner was the node which got replaced, update it.
- if (additive_node_entry.owner == original_node) {
- additive_node_entry.owner = instantiated_node;
- }
+ if (!parent_node) {
+ parent_node = current_edited_scene;
+ }
- additive_node_entry.node->set_owner(additive_node_entry.owner);
+ parent_node->add_child(additive_node_entry.node);
+ parent_node->move_child(additive_node_entry.node, additive_node_entry.index);
+ // If the additive node's owner was the node which got replaced, update it.
+ if (additive_node_entry.owner == original_node) {
+ additive_node_entry.owner = instantiated_node;
+ }
- // If the parent node was lost, attempt to restore the original global transform.
- {
- Node2D *node_2d = Object::cast_to<Node2D>(additive_node_entry.node);
- if (node_2d) {
- node_2d->set_transform(additive_node_entry.transform_2d);
- }
+ additive_node_entry.node->set_owner(additive_node_entry.owner);
- Node3D *node_3d = Object::cast_to<Node3D>(additive_node_entry.node);
- if (node_3d) {
- node_3d->set_transform(additive_node_entry.transform_3d);
- }
+ // If the parent node was lost, attempt to restore the original global transform.
+ {
+ Node2D *node_2d = Object::cast_to<Node2D>(additive_node_entry.node);
+ if (node_2d) {
+ node_2d->set_transform(additive_node_entry.transform_2d);
}
- }
- // Restore the selection.
- if (selected_node_paths.size()) {
- for (NodePath selected_node_path : selected_node_paths) {
- Node *selected_node = instantiated_node->get_node_or_null(selected_node_path);
- if (selected_node) {
- editor_selection->add_node(selected_node);
- }
+ Node3D *node_3d = Object::cast_to<Node3D>(additive_node_entry.node);
+ if (node_3d) {
+ node_3d->set_transform(additive_node_entry.transform_3d);
}
- editor_selection->update();
}
+ }
- // Attempt to restore the modified properties and signals for the instantitated node and all its owned children.
- for (KeyValue<NodePath, ModificationNodeEntry> &E : modification_table) {
- NodePath new_current_path = E.key;
- Node *modifiable_node = instantiated_node->get_node_or_null(new_current_path);
-
- update_node_from_node_modification_entry(modifiable_node, E.value);
+ // Restore the selection.
+ if (selected_node_paths.size()) {
+ for (NodePath selected_node_path : selected_node_paths) {
+ Node *selected_node = instantiated_node->get_node_or_null(selected_node_path);
+ if (selected_node) {
+ editor_selection->add_node(selected_node);
+ }
}
- // Add the newly instantiated node to the edited scene's replaced node list.
- replaced_nodes.push_back(instantiated_node);
+ editor_selection->update();
}
// Attempt to restore the modified properties and signals for the instantitated node and all its owned children.
- for (KeyValue<NodePath, ModificationNodeEntry> &E : edited_scene_global_modification_table) {
+ for (KeyValue<NodePath, ModificationNodeEntry> &E : instance_modifications.modifications) {
NodePath new_current_path = E.key;
- Node *modifiable_node = current_edited_scene->get_node_or_null(new_current_path);
+ Node *modifiable_node = instantiated_node->get_node_or_null(new_current_path);
- if (modifiable_node) {
- update_node_from_node_modification_entry(modifiable_node, E.value);
- }
+ update_node_from_node_modification_entry(modifiable_node, E.value);
}
+ // Add the newly instantiated node to the edited scene's replaced node list.
+ replaced_nodes.push_back(instantiated_node);
+ }
- if (is_unsaved) {
- EditorUndoRedoManager::get_singleton()->set_history_as_unsaved(current_history_id);
+ // Attempt to restore the modified properties and signals for the instantitated node and all its owned children.
+ for (KeyValue<NodePath, ModificationNodeEntry> &E : scene_modifications->other_instances_modifications) {
+ NodePath new_current_path = E.key;
+ Node *modifiable_node = current_edited_scene->get_node_or_null(new_current_path);
+
+ if (modifiable_node) {
+ update_node_from_node_modification_entry(modifiable_node, E.value);
}
+ }
- // Save the current handled scene state.
- editor_data.save_edited_scene_state(editor_selection, &editor_history, editor_state);
- editor_selection->clear();
+ if (is_unsaved) {
+ EditorUndoRedoManager::get_singleton()->set_history_as_unsaved(current_history_id);
+ }
- // Cleanup the history of the changes.
- editor_history.cleanup_history();
+ // Save the current handled scene state.
+ editor_data.save_edited_scene_state(editor_selection, &editor_history, editor_state);
+ editor_selection->clear();
- scene_root->remove_child(current_edited_scene);
- }
+ // Cleanup the history of the changes.
+ editor_history.cleanup_history();
- // For the whole editor, call the _notify_nodes_scene_reimported with a list of replaced nodes.
- // To inform anything that depends on them that they should update as appropriate.
- _notify_nodes_scene_reimported(this, replaced_nodes);
+ if (original_edited_scene_idx != current_scene_idx) {
+ scene_root->remove_child(current_edited_scene);
- edited_scene_map.clear();
+ // Ensure the current edited scene is re-added if removed earlier because it has the same name
+ // as the reimported scene. The editor could crash when reloading SceneTreeDock if the current
+ // edited scene is not in the scene tree.
+ Node *original_edited_scene_root = editor_data.get_edited_scene_root(original_edited_scene_idx);
+ if (original_edited_scene_root && !original_edited_scene_root->get_parent()) {
+ scene_root->add_child(original_edited_scene_root);
+ }
+ }
+ }
- editor_data.set_edited_scene(original_edited_scene_idx);
+ // For the whole editor, call the _notify_nodes_scene_reimported with a list of replaced nodes.
+ // To inform anything that depends on them that they should update as appropriate.
+ _notify_nodes_scene_reimported(this, replaced_nodes);
- original_edited_scene_root = editor_data.get_edited_scene_root();
- scene_root->add_child(original_edited_scene_root);
+ editor_data.set_edited_scene(original_edited_scene_idx);
- editor_data.restore_edited_scene_state(editor_selection, &editor_history);
- }
+ editor_data.restore_edited_scene_state(editor_selection, &editor_history);
scenes_modification_table.clear();
+
+ progress.step(TTR("Reloading done."), editor_data.get_edited_scene_count());
}
void EditorNode::_remove_all_not_owned_children(Node *p_node, Node *p_owner) {
diff --git a/editor/editor_node.h b/editor/editor_node.h
index 4d55eaf1b2..0877d458a7 100644
--- a/editor/editor_node.h
+++ b/editor/editor_node.h
@@ -797,6 +797,7 @@ public:
Error load_resource(const String &p_resource, bool p_ignore_broken_deps = false);
HashMap<StringName, Variant> get_modified_properties_for_node(Node *p_node, bool p_node_references_only);
+ HashMap<StringName, Variant> get_modified_properties_reference_to_nodes(Node *p_node, List<Node *> &p_nodes_referenced_by);
struct AdditiveNodeEntry {
Node *node = nullptr;
@@ -820,29 +821,39 @@ public:
List<Node::GroupInfo> groups;
};
- HashMap<int, HashMap<NodePath, HashMap<NodePath, ModificationNodeEntry>>> scenes_modification_table;
+ struct InstanceModificationsEntry {
+ Node *original_node;
+ String instance_path;
+ List<Node *> instance_list;
+ HashMap<NodePath, ModificationNodeEntry> modifications;
+ List<AdditiveNodeEntry> addition_list;
+ };
- void update_node_from_node_modification_entry(Node *p_node, ModificationNodeEntry &p_node_modification);
+ struct SceneModificationsEntry {
+ List<InstanceModificationsEntry> instance_list;
+ HashMap<NodePath, ModificationNodeEntry> other_instances_modifications;
+ };
- void update_node_reference_modification_table_for_node(
- Node *p_root,
- Node *p_node,
- List<Node *> p_excluded_nodes,
- HashMap<NodePath, ModificationNodeEntry> &p_modification_table);
+ HashMap<int, SceneModificationsEntry> scenes_modification_table;
+
+ void update_node_from_node_modification_entry(Node *p_node, ModificationNodeEntry &p_node_modification);
void get_preload_scene_modification_table(
Node *p_edited_scene,
Node *p_reimported_root,
- Node *p_node, HashMap<NodePath, ModificationNodeEntry> &p_modification_table);
+ Node *p_node, InstanceModificationsEntry &p_instance_modifications);
- void update_reimported_diff_data_for_additional_nodes(
- Node *p_edited_scene,
- Node *p_reimported_root,
+ void get_preload_modifications_reference_to_nodes(
+ Node *p_root,
Node *p_node,
- HashMap<NodePath, ModificationNodeEntry> &p_modification_table,
- List<AdditiveNodeEntry> &p_addition_list);
+ List<Node *> &p_excluded_nodes,
+ List<Node *> &p_instance_list_with_children,
+ HashMap<NodePath, ModificationNodeEntry> &p_modification_table);
+ void get_children_nodes(Node *p_node, List<Node *> &p_nodes);
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;
@@ -896,10 +907,9 @@ public:
void reload_scene(const String &p_path);
- void get_edited_scene_map(const String &p_instance_path, HashMap<int, List<Node *>> &p_edited_scene_map);
void find_all_instances_inheriting_path_in_node(Node *p_root, Node *p_node, const String &p_instance_path, List<Node *> &p_instance_list);
- void preload_reimporting_with_path_in_edited_scenes(const String &p_path);
- void reload_instances_with_path_in_edited_scenes(const String &p_path);
+ void preload_reimporting_with_path_in_edited_scenes(const List<String> &p_scenes);
+ void reload_instances_with_path_in_edited_scenes();
bool is_exiting() const { return exiting; }
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/interactive_music/editor/audio_stream_interactive_editor_plugin.cpp b/modules/interactive_music/editor/audio_stream_interactive_editor_plugin.cpp
index e29cc753c9..fcb477995f 100644
--- a/modules/interactive_music/editor/audio_stream_interactive_editor_plugin.cpp
+++ b/modules/interactive_music/editor/audio_stream_interactive_editor_plugin.cpp
@@ -34,6 +34,7 @@
#include "editor/editor_node.h"
#include "editor/editor_string_names.h"
#include "editor/editor_undo_redo_manager.h"
+#include "editor/themes/editor_scale.h"
#include "scene/gui/check_box.h"
#include "scene/gui/option_button.h"
#include "scene/gui/spin_box.h"
@@ -290,7 +291,6 @@ void AudioStreamInteractiveTransitionEditor::edit(Object *p_obj) {
}
int min_w = header_font->get_string_size(name + "XX").width;
- tree->set_column_expand(cell_index, false);
tree->set_column_custom_minimum_width(cell_index, min_w);
max_w = MAX(max_w, min_w);
@@ -314,11 +314,10 @@ void AudioStreamInteractiveTransitionEditor::edit(Object *p_obj) {
}
}
- tree->set_column_expand(header_index, false);
tree->set_column_custom_minimum_width(header_index, max_w);
selection_order.clear();
_update_selection();
- popup_centered_ratio(0.6);
+ popup_centered_clamped(Size2(900, 450) * EDSCALE);
updating = false;
_update_transitions();
}
@@ -332,6 +331,7 @@ AudioStreamInteractiveTransitionEditor::AudioStreamInteractiveTransitionEditor()
tree->set_hide_root(true);
tree->add_theme_constant_override("draw_guides", 1);
tree->set_select_mode(Tree::SELECT_MULTI);
+ tree->set_custom_minimum_size(Size2(400, 0) * EDSCALE);
split->add_child(tree);
tree->set_h_size_flags(Control::SIZE_EXPAND_FILL);
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;