summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/classes/LightmapGI.xml18
-rw-r--r--doc/classes/Node.xml3
-rw-r--r--editor/editor_data.h1
-rw-r--r--editor/editor_node.cpp479
-rw-r--r--editor/editor_node.h35
-rw-r--r--editor/plugins/lightmap_gi_editor_plugin.cpp56
-rw-r--r--editor/plugins/voxel_gi_editor_plugin.cpp29
-rw-r--r--modules/gdscript/gdscript_byte_codegen.cpp28
-rw-r--r--modules/gdscript/gdscript_byte_codegen.h5
-rw-r--r--modules/gdscript/gdscript_codegen.h4
-rw-r--r--modules/gdscript/gdscript_compiler.cpp11
-rw-r--r--modules/gdscript/gdscript_parser.cpp20
-rw-r--r--modules/gdscript/gdscript_parser.h2
-rw-r--r--modules/gdscript/tests/scripts/parser/features/match.gd2
-rw-r--r--modules/gdscript/tests/scripts/parser/features/match.out1
-rw-r--r--modules/gdscript/tests/scripts/parser/features/nested_match.gd7
-rw-r--r--modules/gdscript/tests/scripts/parser/features/nested_match.out1
-rw-r--r--modules/navigation/editor/navigation_mesh_editor_plugin.cpp30
-rw-r--r--scene/3d/lightmap_gi.cpp2
-rw-r--r--scene/3d/lightmap_gi.h2
-rw-r--r--scene/main/node.cpp1
-rw-r--r--scene/main/node.h1
-rw-r--r--scene/resources/packed_scene.cpp19
-rw-r--r--scene/resources/packed_scene.h2
24 files changed, 663 insertions, 96 deletions
diff --git a/doc/classes/LightmapGI.xml b/doc/classes/LightmapGI.xml
index 53dae1a8e6..723e6bbf21 100644
--- a/doc/classes/LightmapGI.xml
+++ b/doc/classes/LightmapGI.xml
@@ -93,22 +93,28 @@
<constant name="BAKE_ERROR_OK" value="0" enum="BakeError">
Lightmap baking was successful.
</constant>
- <constant name="BAKE_ERROR_NO_LIGHTMAPPER" value="1" enum="BakeError">
+ <constant name="BAKE_ERROR_NO_SCENE_ROOT" value="1" enum="BakeError">
+ Lightmap baking failed because the root node for the edited scene could not be accessed.
+ </constant>
+ <constant name="BAKE_ERROR_FOREIGN_DATA" value="2" enum="BakeError">
+ Lightmap baking failed as the lightmap data resource is embedded in a foreign resource.
+ </constant>
+ <constant name="BAKE_ERROR_NO_LIGHTMAPPER" value="3" enum="BakeError">
Lightmap baking failed as there is no lightmapper available in this Godot build.
</constant>
- <constant name="BAKE_ERROR_NO_SAVE_PATH" value="2" enum="BakeError">
+ <constant name="BAKE_ERROR_NO_SAVE_PATH" value="4" enum="BakeError">
Lightmap baking failed as the [LightmapGIData] save path isn't configured in the resource.
</constant>
- <constant name="BAKE_ERROR_NO_MESHES" value="3" enum="BakeError">
+ <constant name="BAKE_ERROR_NO_MESHES" value="5" enum="BakeError">
Lightmap baking failed as there are no meshes whose [member GeometryInstance3D.gi_mode] is [constant GeometryInstance3D.GI_MODE_STATIC] and with valid UV2 mapping in the current scene. You may need to select 3D scenes in the Import dock and change their global illumination mode accordingly.
</constant>
- <constant name="BAKE_ERROR_MESHES_INVALID" value="4" enum="BakeError">
+ <constant name="BAKE_ERROR_MESHES_INVALID" value="6" enum="BakeError">
Lightmap baking failed as the lightmapper failed to analyze some of the meshes marked as static for baking.
</constant>
- <constant name="BAKE_ERROR_CANT_CREATE_IMAGE" value="5" enum="BakeError">
+ <constant name="BAKE_ERROR_CANT_CREATE_IMAGE" value="7" enum="BakeError">
Lightmap baking failed as the resulting image couldn't be saved or imported by Godot after it was saved.
</constant>
- <constant name="BAKE_ERROR_USER_ABORTED" value="6" enum="BakeError">
+ <constant name="BAKE_ERROR_USER_ABORTED" value="8" enum="BakeError">
The user aborted the lightmap baking operation (typically by clicking the [b]Cancel[/b] button in the progress dialog).
</constant>
<constant name="ENVIRONMENT_MODE_DISABLED" value="0" enum="EnvironmentMode">
diff --git a/doc/classes/Node.xml b/doc/classes/Node.xml
index 02fd6dae30..7b27f16d82 100644
--- a/doc/classes/Node.xml
+++ b/doc/classes/Node.xml
@@ -909,6 +909,9 @@
<constant name="NOTIFICATION_ENABLED" value="29">
Notification received when the node is enabled again after being disabled. See [constant PROCESS_MODE_DISABLED].
</constant>
+ <constant name="NOTIFICATION_NODE_RECACHE_REQUESTED" value="30">
+ Notification received when other nodes in the tree may have been removed/replaced and node pointers may require re-caching.
+ </constant>
<constant name="NOTIFICATION_EDITOR_PRE_SAVE" value="9001">
Notification received right before the scene with the node is saved in the editor. This notification is only sent in the Godot editor and will not occur in exported projects.
</constant>
diff --git a/editor/editor_data.h b/editor/editor_data.h
index 6a89b3572c..d00501280d 100644
--- a/editor/editor_data.h
+++ b/editor/editor_data.h
@@ -201,7 +201,6 @@ public:
String get_scene_type(int p_idx) const;
void set_scene_path(int p_idx, const String &p_path);
Ref<Script> get_scene_root_script(int p_idx) const;
- void set_edited_scene_version(uint64_t version, int p_scene_idx = -1);
void set_scene_modified_time(int p_idx, uint64_t p_time);
uint64_t get_scene_modified_time(int p_idx) const;
void clear_edited_scenes();
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index 19d991b112..c344812065 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -58,6 +58,7 @@
#include "scene/gui/tab_bar.h"
#include "scene/gui/tab_container.h"
#include "scene/main/window.h"
+#include "scene/property_utils.h"
#include "scene/resources/packed_scene.h"
#include "servers/display_server.h"
#include "servers/navigation_server_3d.h"
@@ -997,6 +998,7 @@ 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);
}
scene_tabs->set_current_tab(current_tab);
@@ -2112,6 +2114,7 @@ void EditorNode::edit_item(Object *p_object, Object *p_editing_owner) {
}
}
active_plugins[owner_id].insert(plugin);
+ editor_plugins_over->add_plugin(plugin);
plugin->edit(p_object);
plugin->make_visible(true);
}
@@ -3898,7 +3901,7 @@ Error EditorNode::load_scene(const String &p_scene, bool p_ignore_broken_deps, b
Ref<SceneState> state = sdata->get_state();
state->set_path(lpath);
new_scene->set_scene_inherited_state(state);
- new_scene->set_scene_file_path(String());
+ new_scene->set_scene_file_path(lpath);
}
new_scene->set_scene_instance_state(Ref<SceneState>());
@@ -3932,6 +3935,134 @@ Error EditorNode::load_scene(const String &p_scene, bool p_ignore_broken_deps, b
return OK;
}
+HashMap<StringName, Variant> EditorNode::get_modified_properties_for_node(Node *p_node) {
+ 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) {
+ 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(current_value, revert_value)) {
+ modified_property_map[E.name] = current_value;
+ }
+ }
+ }
+ }
+
+ return modified_property_map;
+}
+
+void EditorNode::update_diff_data_for_node(
+ Node *p_edited_scene,
+ Node *p_root,
+ Node *p_node,
+ HashMap<NodePath, ModificationNodeEntry> &p_modification_table,
+ List<AdditiveNodeEntry> &p_addition_list) {
+ bool node_part_of_subscene = p_node != p_edited_scene &&
+ p_edited_scene->get_scene_inherited_state().is_valid() &&
+ p_edited_scene->get_scene_inherited_state()->find_node_by_path(p_edited_scene->get_path_to(p_node)) >= 0;
+
+ // Loop through the owners until either we reach the root node or nullptr
+ Node *valid_node_owner = p_node->get_owner();
+ while (valid_node_owner) {
+ if (valid_node_owner == p_root) {
+ break;
+ }
+ valid_node_owner = valid_node_owner->get_owner();
+ }
+
+ if ((valid_node_owner == p_root && (p_root != p_edited_scene || !p_edited_scene->get_scene_file_path().is_empty())) || node_part_of_subscene || p_node == p_root) {
+ HashMap<StringName, Variant> modified_properties = get_modified_properties_for_node(p_node);
+
+ // Find all valid connections to other nodes.
+ List<Connection> connections_to;
+ p_node->get_all_signal_connections(&connections_to);
+
+ List<ConnectionWithNodePath> valid_connections_to;
+ for (const Connection &c : connections_to) {
+ Node *connection_target_node = Object::cast_to<Node>(c.callable.get_object());
+ if (connection_target_node) {
+ // TODO: add support for reinstating custom callables
+ if (!c.callable.is_custom()) {
+ ConnectionWithNodePath connection_to;
+ connection_to.connection = c;
+ connection_to.node_path = p_node->get_path_to(connection_target_node);
+ valid_connections_to.push_back(connection_to);
+ }
+ }
+ }
+
+ // Find all valid connections from other nodes.
+ List<Connection> connections_from;
+ p_node->get_signals_connected_to_this(&connections_from);
+
+ List<Connection> valid_connections_from;
+ for (const Connection &c : connections_from) {
+ Node *source_node = Object::cast_to<Node>(c.signal.get_object());
+
+ Node *valid_source_owner = nullptr;
+ if (source_node) {
+ valid_source_owner = source_node->get_owner();
+ while (valid_source_owner) {
+ if (valid_source_owner == p_root) {
+ break;
+ }
+ valid_source_owner = valid_source_owner->get_owner();
+ }
+ }
+
+ if (!source_node || valid_source_owner == nullptr) {
+ // TODO: add support for reinstating custom callables
+ if (!c.callable.is_custom()) {
+ valid_connections_from.push_back(c);
+ }
+ }
+ }
+
+ // Find all node groups.
+ List<Node::GroupInfo> groups;
+ p_node->get_groups(&groups);
+
+ if (!modified_properties.is_empty() || !valid_connections_to.is_empty() || !valid_connections_from.is_empty() || !groups.is_empty()) {
+ ModificationNodeEntry modification_node_entry;
+ modification_node_entry.property_table = modified_properties;
+ modification_node_entry.connections_to = valid_connections_to;
+ modification_node_entry.connections_from = valid_connections_from;
+ modification_node_entry.groups = groups;
+
+ p_modification_table[p_root->get_path_to(p_node)] = modification_node_entry;
+ }
+ } else {
+ AdditiveNodeEntry new_additive_node_entry;
+ new_additive_node_entry.node = p_node;
+ new_additive_node_entry.parent = p_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_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());
+ }
+ p_addition_list.push_back(new_additive_node_entry);
+
+ return;
+ }
+
+ for (int i = 0; i < p_node->get_child_count(); i++) {
+ Node *child = p_node->get_child(i);
+ update_diff_data_for_node(p_edited_scene, p_root, child, p_modification_table, p_addition_list);
+ }
+}
+//
+
void EditorNode::open_request(const String &p_path) {
if (!opening_prev) {
List<String>::Element *prev_scene_item = previous_scenes.find(p_path);
@@ -5788,6 +5919,352 @@ void EditorNode::reload_scene(const String &p_path) {
scene_tabs->set_current_tab(current_tab);
}
+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();
+
+ // This is going to get messy...
+ if (p_node->get_scene_file_path() == p_instance_path) {
+ p_instance_list.push_back(p_node);
+ } else {
+ Node *current_node = p_node;
+
+ Ref<SceneState> inherited_state = current_node->get_scene_inherited_state();
+ while (inherited_state.is_valid()) {
+ String inherited_path = inherited_state->get_path();
+ if (inherited_path == p_instance_path) {
+ p_instance_list.push_back(p_node);
+ break;
+ }
+
+ inherited_state = inherited_state->get_base_scene_state();
+ }
+ }
+
+ 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);
+ }
+}
+
+void EditorNode::reload_instances_with_path_in_edited_scenes(const String &p_instance_path) {
+ int original_edited_scene_idx = editor_data.get_edited_scene();
+ HashMap<int, List<Node *>> 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) {
+ 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) {
+ edited_scene_map[i] = valid_nodes;
+ }
+ }
+ }
+ }
+
+ 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_IGNORE, &err);
+ instance_scene_packed_scene->set_path(p_instance_path, true);
+
+ 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;
+
+ 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);
+
+ // Clear the history for this tab (should we allow history to be retained?).
+ EditorUndoRedoManager::get_singleton()->clear_history();
+
+ // Update the version
+ editor_data.is_scene_changed(current_scene_idx);
+
+ for (Node *original_node : edited_scene_map_elem.value) {
+ // 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;
+ update_diff_data_for_node(current_edited_scene, original_node, original_node, modification_table, addition_list);
+
+ // 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) {
+ 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);
+ }
+ }
+
+ // 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);
+ }
+
+ // 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);
+
+ original_node->remove_child(child);
+ child->queue_free();
+ }
+
+ // 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);
+ }
+
+ // Load a replacement scene for the node.
+ Ref<PackedScene> current_packed_scene;
+ if (original_node->get_scene_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;
+ String scene_path = original_node->get_scene_file_path();
+ // Do we need to check if the paths are empty?
+ if (!scene_path.is_empty()) {
+ required_load_paths.push_front(scene_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();
+ }
+
+ // Ensure the inheritance chain is loaded in the correct order so that cache can
+ // be properly updated.
+ for (String path : required_load_paths) {
+ if (!local_scene_cache.find(path)) {
+ current_packed_scene = ResourceLoader::load(path, "", ResourceFormatLoader::CACHE_MODE_IGNORE, &err);
+ current_packed_scene->set_path(path, true);
+ local_scene_cache[path] = current_packed_scene;
+ } else {
+ current_packed_scene = local_scene_cache[path];
+ }
+ }
+ }
+
+ ERR_FAIL_COND(current_packed_scene.is_null());
+
+ // Instantiate the node.
+ Node *instantiated_node = nullptr;
+ if (current_packed_scene.is_valid()) {
+ instantiated_node = current_packed_scene->instantiate(PackedScene::GEN_EDIT_STATE_INSTANCE);
+ }
+
+ ERR_FAIL_COND(!instantiated_node);
+
+ 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();
+
+ // Update the name to match
+ instantiated_node->set_name(original_node->get_name());
+
+ // Is this replacing the edited root node?
+ String original_node_file_path = original_node->get_scene_file_path();
+
+ if (current_edited_scene == original_node) {
+ instantiated_node->set_scene_instance_state(original_node->get_scene_instance_state());
+ // Fix unsaved inherited scene
+ if (original_node_file_path.is_empty()) {
+ Ref<SceneState> state = current_packed_scene->get_state();
+ state->set_path(current_packed_scene->get_path());
+ instantiated_node->set_scene_inherited_state(state);
+ }
+ editor_data.set_edited_scene_root(instantiated_node);
+ current_edited_scene = instantiated_node;
+
+ if (original_node->is_inside_tree()) {
+ SceneTreeDock::get_singleton()->set_edited_scene(current_edited_scene);
+ original_node->get_tree()->set_edited_scene_root(instantiated_node);
+ }
+ }
+
+ // Replace the original node with the instantiated version.
+ original_node->replace_by(instantiated_node, false);
+
+ // 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);
+
+ 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);
+ }
+
+ owner->set_editable_instance(instantiated_node, is_editable);
+ }
+
+ // 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);
+
+ if (!parent_node) {
+ parent_node = current_edited_scene;
+ }
+
+ 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;
+ }
+
+ additive_node_entry.node->set_owner(additive_node_entry.owner);
+
+ // 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);
+ }
+
+ Node3D *node_3d = Object::cast_to<Node3D>(additive_node_entry.node);
+ if (node_3d) {
+ node_3d->set_transform(additive_node_entry.transform_3d);
+ }
+ }
+ }
+
+ // 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);
+ }
+ }
+ 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);
+
+ if (modifiable_node) {
+ // Get properties for this node.
+ List<PropertyInfo> pinfo;
+ modifiable_node->get_property_list(&pinfo);
+
+ // Get names of all valid property names (TODO: make this more efficent).
+ List<String> property_names;
+ for (const PropertyInfo &E2 : pinfo) {
+ if (E2.usage & PROPERTY_USAGE_STORAGE) {
+ property_names.push_back(E2.name);
+ }
+ }
+
+ // Restore the modified properties for this node.
+ for (const KeyValue<StringName, Variant> &E2 : E.value.property_table) {
+ if (property_names.find(E2.key)) {
+ modifiable_node->set(E2.key, E2.value);
+ }
+ }
+ // Restore the connections to other nodes.
+ for (const ConnectionWithNodePath &E2 : E.value.connections_to) {
+ Connection conn = E2.connection;
+
+ // Get the node the callable is targetting.
+ Node *target_node = cast_to<Node>(conn.callable.get_object());
+
+ // If the callable object no longer exists or is marked for deletion,
+ // attempt to reaccquire the closest match by using the node path
+ // we saved earlier.
+ if (!target_node || !target_node->is_queued_for_deletion()) {
+ target_node = modifiable_node->get_node_or_null(E2.node_path);
+ }
+
+ if (target_node) {
+ // Reconstruct the callable.
+ Callable new_callable = Callable(target_node, conn.callable.get_method());
+
+ if (!modifiable_node->is_connected(conn.signal.get_name(), new_callable)) {
+ ERR_FAIL_COND(modifiable_node->connect(conn.signal.get_name(), new_callable, conn.flags) != OK);
+ }
+ }
+ }
+
+ // Restore the connections from other nodes.
+ for (const Connection &E2 : E.value.connections_from) {
+ Connection conn = E2;
+
+ bool valid = modifiable_node->has_method(conn.callable.get_method()) || Ref<Script>(modifiable_node->get_script()).is_null() || Ref<Script>(modifiable_node->get_script())->has_method(conn.callable.get_method());
+ ERR_CONTINUE_MSG(!valid, vformat("Attempt to connect signal '%s.%s' to nonexistent method '%s.%s'.", conn.signal.get_object()->get_class(), conn.signal.get_name(), conn.callable.get_object()->get_class(), conn.callable.get_method()));
+
+ // Get the object which the signal is connected from.
+ Object *source_object = conn.signal.get_object();
+
+ if (source_object) {
+ ERR_FAIL_COND(source_object->connect(conn.signal.get_name(), Callable(modifiable_node, conn.callable.get_method()), conn.flags) != OK);
+ }
+ }
+
+ // Re-add the groups.
+ for (const Node::GroupInfo &E2 : E.value.groups) {
+ modifiable_node->add_to_group(E2.name, E2.persistent);
+ }
+ }
+ }
+ }
+ // Cleanup the history of the changes.
+ editor_history.cleanup_history();
+
+ current_edited_scene->propagate_notification(NOTIFICATION_NODE_RECACHE_REQUESTED);
+ }
+ edited_scene_map.clear();
+ }
+ editor_data.set_edited_scene(original_edited_scene_idx);
+
+ _edit_current();
+}
+
int EditorNode::plugin_init_callback_count = 0;
void EditorNode::add_plugin_init_callback(EditorPluginInitializeCallback p_callback) {
diff --git a/editor/editor_node.h b/editor/editor_node.h
index ef4ea34da2..bb10abb589 100644
--- a/editor/editor_node.h
+++ b/editor/editor_node.h
@@ -87,6 +87,7 @@ class ImportDock;
class LinkButton;
class MenuBar;
class MenuButton;
+class Node2D;
class NodeDock;
class OptionButton;
class OrphanResourcesDialog;
@@ -818,6 +819,37 @@ public:
Error load_scene(const String &p_scene, bool p_ignore_broken_deps = false, bool p_set_inherited = false, bool p_clear_errors = true, bool p_force_open_imported = false, bool p_silent_change_tab = false);
Error load_resource(const String &p_resource, bool p_ignore_broken_deps = false);
+ HashMap<StringName, Variant> get_modified_properties_for_node(Node *p_node);
+
+ struct AdditiveNodeEntry {
+ Node *node = nullptr;
+ NodePath parent = NodePath();
+ Node *owner = nullptr;
+ int index = 0;
+ // Used if the original parent node is lost
+ Transform2D transform_2d;
+ Transform3D transform_3d;
+ };
+
+ struct ConnectionWithNodePath {
+ Connection connection;
+ NodePath node_path;
+ };
+
+ struct ModificationNodeEntry {
+ HashMap<StringName, Variant> property_table;
+ List<ConnectionWithNodePath> connections_to;
+ List<Connection> connections_from;
+ List<Node::GroupInfo> groups;
+ };
+
+ void update_diff_data_for_node(
+ Node *p_edited_scene,
+ Node *p_root,
+ Node *p_node,
+ HashMap<NodePath, ModificationNodeEntry> &p_modification_table,
+ List<AdditiveNodeEntry> &p_addition_list);
+
bool is_scene_open(const String &p_path);
void set_current_scene(int p_idx);
@@ -870,6 +902,9 @@ public:
void reload_scene(const String &p_path);
+ 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 reload_instances_with_path_in_edited_scenes(const String &p_path);
+
bool is_exiting() const { return exiting; }
Button *get_pause_button() { return pause_button; }
diff --git a/editor/plugins/lightmap_gi_editor_plugin.cpp b/editor/plugins/lightmap_gi_editor_plugin.cpp
index 1adcc2a3b4..519cfcaa94 100644
--- a/editor/plugins/lightmap_gi_editor_plugin.cpp
+++ b/editor/plugins/lightmap_gi_editor_plugin.cpp
@@ -35,12 +35,43 @@
void LightmapGIEditorPlugin::_bake_select_file(const String &p_file) {
if (lightmap) {
- LightmapGI::BakeError err;
+ LightmapGI::BakeError err = LightmapGI::BAKE_ERROR_OK;
const uint64_t time_started = OS::get_singleton()->get_ticks_msec();
- if (get_tree()->get_edited_scene_root() && get_tree()->get_edited_scene_root() == lightmap) {
- err = lightmap->bake(lightmap, p_file, bake_func_step);
+ if (get_tree()->get_edited_scene_root()) {
+ Ref<LightmapGIData> lightmapGIData = lightmap->get_light_data();
+
+ if (lightmapGIData.is_valid()) {
+ String path = lightmapGIData->get_path();
+ if (!path.is_resource_file()) {
+ int srpos = path.find("::");
+ if (srpos != -1) {
+ String base = path.substr(0, srpos);
+ if (ResourceLoader::get_resource_type(base) == "PackedScene") {
+ if (!get_tree()->get_edited_scene_root() || get_tree()->get_edited_scene_root()->get_scene_file_path() != base) {
+ err = LightmapGI::BAKE_ERROR_FOREIGN_DATA;
+ }
+ } else {
+ if (FileAccess::exists(base + ".import")) {
+ err = LightmapGI::BAKE_ERROR_FOREIGN_DATA;
+ }
+ }
+ }
+ } else {
+ if (FileAccess::exists(path + ".import")) {
+ err = LightmapGI::BAKE_ERROR_FOREIGN_DATA;
+ }
+ }
+ }
+
+ if (err == LightmapGI::BAKE_ERROR_OK) {
+ if (get_tree()->get_edited_scene_root() == lightmap) {
+ err = lightmap->bake(lightmap, p_file, bake_func_step);
+ } else {
+ err = lightmap->bake(lightmap->get_parent(), p_file, bake_func_step);
+ }
+ }
} else {
- err = lightmap->bake(lightmap->get_parent(), p_file, bake_func_step);
+ err = LightmapGI::BAKE_ERROR_NO_SCENE_ROOT;
}
bake_func_end(time_started);
@@ -59,16 +90,21 @@ void LightmapGIEditorPlugin::_bake_select_file(const String &p_file) {
file_dialog->set_current_path(scene_path);
file_dialog->popup_file_dialog();
-
} break;
- case LightmapGI::BAKE_ERROR_NO_MESHES:
+ case LightmapGI::BAKE_ERROR_NO_MESHES: {
EditorNode::get_singleton()->show_warning(TTR("No meshes to bake. Make sure they contain an UV2 channel and that the 'Bake Light' flag is on."));
- break;
- case LightmapGI::BAKE_ERROR_CANT_CREATE_IMAGE:
+ } break;
+ case LightmapGI::BAKE_ERROR_CANT_CREATE_IMAGE: {
EditorNode::get_singleton()->show_warning(TTR("Failed creating lightmap images, make sure path is writable."));
- break;
+ } break;
+ case LightmapGI::BAKE_ERROR_NO_SCENE_ROOT: {
+ EditorNode::get_singleton()->show_warning(TTR("No editor scene root found."));
+ } break;
+ case LightmapGI::BAKE_ERROR_FOREIGN_DATA: {
+ EditorNode::get_singleton()->show_warning(TTR("Lightmap data is not local to the scene."));
+ } break;
default: {
- }
+ } break;
}
}
}
diff --git a/editor/plugins/voxel_gi_editor_plugin.cpp b/editor/plugins/voxel_gi_editor_plugin.cpp
index a3ccf392e6..f9f72fee77 100644
--- a/editor/plugins/voxel_gi_editor_plugin.cpp
+++ b/editor/plugins/voxel_gi_editor_plugin.cpp
@@ -35,7 +35,9 @@
void VoxelGIEditorPlugin::_bake() {
if (voxel_gi) {
- if (voxel_gi->get_probe_data().is_null()) {
+ Ref<VoxelGIData> voxel_gi_data = voxel_gi->get_probe_data();
+
+ if (voxel_gi_data.is_null()) {
String path = get_tree()->get_edited_scene_root()->get_scene_file_path();
if (path.is_empty()) {
path = "res://" + voxel_gi->get_name() + "_data.res";
@@ -46,7 +48,32 @@ void VoxelGIEditorPlugin::_bake() {
probe_file->set_current_path(path);
probe_file->popup_file_dialog();
return;
+ } else {
+ String path = voxel_gi_data->get_path();
+ if (!path.is_resource_file()) {
+ int srpos = path.find("::");
+ if (srpos != -1) {
+ String base = path.substr(0, srpos);
+ if (ResourceLoader::get_resource_type(base) == "PackedScene") {
+ if (!get_tree()->get_edited_scene_root() || get_tree()->get_edited_scene_root()->get_scene_file_path() != base) {
+ EditorNode::get_singleton()->show_warning(TTR("Voxel GI data is not local to the scene."));
+ return;
+ }
+ } else {
+ if (FileAccess::exists(base + ".import")) {
+ EditorNode::get_singleton()->show_warning(TTR("Voxel GI data is part of an imported resource."));
+ return;
+ }
+ }
+ }
+ } else {
+ if (FileAccess::exists(path + ".import")) {
+ EditorNode::get_singleton()->show_warning(TTR("Voxel GI data is an imported resource."));
+ return;
+ }
+ }
}
+
voxel_gi->bake();
}
}
diff --git a/modules/gdscript/gdscript_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp
index 6c80fb7665..5e721bc179 100644
--- a/modules/gdscript/gdscript_byte_codegen.cpp
+++ b/modules/gdscript/gdscript_byte_codegen.cpp
@@ -1543,28 +1543,6 @@ void GDScriptByteCodeGenerator::write_endwhile() {
current_breaks_to_patch.pop_back();
}
-void GDScriptByteCodeGenerator::start_match() {
- match_continues_to_patch.push_back(List<int>());
-}
-
-void GDScriptByteCodeGenerator::start_match_branch() {
- // Patch continue statements.
- for (const int &E : match_continues_to_patch.back()->get()) {
- patch_jump(E);
- }
- match_continues_to_patch.pop_back();
- // Start a new list for next branch.
- match_continues_to_patch.push_back(List<int>());
-}
-
-void GDScriptByteCodeGenerator::end_match() {
- // Patch continue statements.
- for (const int &E : match_continues_to_patch.back()->get()) {
- patch_jump(E);
- }
- match_continues_to_patch.pop_back();
-}
-
void GDScriptByteCodeGenerator::write_break() {
append_opcode(GDScriptFunction::OPCODE_JUMP);
current_breaks_to_patch.back()->get().push_back(opcodes.size());
@@ -1576,12 +1554,6 @@ void GDScriptByteCodeGenerator::write_continue() {
append(continue_addrs.back()->get());
}
-void GDScriptByteCodeGenerator::write_continue_match() {
- append_opcode(GDScriptFunction::OPCODE_JUMP);
- match_continues_to_patch.back()->get().push_back(opcodes.size());
- append(0);
-}
-
void GDScriptByteCodeGenerator::write_breakpoint() {
append_opcode(GDScriptFunction::OPCODE_BREAKPOINT);
}
diff --git a/modules/gdscript/gdscript_byte_codegen.h b/modules/gdscript/gdscript_byte_codegen.h
index 171c505116..aadfa2f444 100644
--- a/modules/gdscript/gdscript_byte_codegen.h
+++ b/modules/gdscript/gdscript_byte_codegen.h
@@ -113,7 +113,6 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator {
List<int> ternary_jump_skip_pos;
List<List<int>> current_breaks_to_patch;
- List<List<int>> match_continues_to_patch;
void add_stack_identifier(const StringName &p_id, int p_stackpos) {
if (locals.size() > max_locals) {
@@ -496,12 +495,8 @@ public:
virtual void start_while_condition() override;
virtual void write_while(const Address &p_condition) override;
virtual void write_endwhile() override;
- virtual void start_match() override;
- virtual void start_match_branch() override;
- virtual void end_match() override;
virtual void write_break() override;
virtual void write_continue() override;
- virtual void write_continue_match() override;
virtual void write_breakpoint() override;
virtual void write_newline(int p_line) override;
virtual void write_return(const Address &p_return_value) override;
diff --git a/modules/gdscript/gdscript_codegen.h b/modules/gdscript/gdscript_codegen.h
index e885938eba..87fc663d4c 100644
--- a/modules/gdscript/gdscript_codegen.h
+++ b/modules/gdscript/gdscript_codegen.h
@@ -148,12 +148,8 @@ public:
virtual void start_while_condition() = 0; // Used to allow a jump to the expression evaluation.
virtual void write_while(const Address &p_condition) = 0;
virtual void write_endwhile() = 0;
- virtual void start_match() = 0;
- virtual void start_match_branch() = 0;
- virtual void end_match() = 0;
virtual void write_break() = 0;
virtual void write_continue() = 0;
- virtual void write_continue_match() = 0;
virtual void write_breakpoint() = 0;
virtual void write_newline(int p_line) = 0;
virtual void write_return(const Address &p_return_value) = 0;
diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp
index d63a1b4536..4f04c62eb2 100644
--- a/modules/gdscript/gdscript_compiler.cpp
+++ b/modules/gdscript/gdscript_compiler.cpp
@@ -1679,7 +1679,6 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
case GDScriptParser::Node::MATCH: {
const GDScriptParser::MatchNode *match = static_cast<const GDScriptParser::MatchNode *>(s);
- gen->start_match();
codegen.start_block();
// Evaluate the match expression.
@@ -1718,7 +1717,6 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
const GDScriptParser::MatchBranchNode *branch = match->branches[j];
- gen->start_match_branch(); // Need so lower level code can patch 'continue' jumps.
codegen.start_block(); // Create an extra block around for binds.
// Add locals in block before patterns, so temporaries don't use the stack address for binds.
@@ -1756,8 +1754,6 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
for (int j = 0; j < match->branches.size(); j++) {
gen->write_endif();
}
-
- gen->end_match();
} break;
case GDScriptParser::Node::IF: {
const GDScriptParser::IfNode *if_n = static_cast<const GDScriptParser::IfNode *>(s);
@@ -1845,12 +1841,7 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
gen->write_break();
} break;
case GDScriptParser::Node::CONTINUE: {
- const GDScriptParser::ContinueNode *cont = static_cast<const GDScriptParser::ContinueNode *>(s);
- if (cont->is_for_match) {
- gen->write_continue_match();
- } else {
- gen->write_continue();
- }
+ gen->write_continue();
} break;
case GDScriptParser::Node::RETURN: {
const GDScriptParser::ReturnNode *return_n = static_cast<const GDScriptParser::ReturnNode *>(s);
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index 4228046ba2..6dc63c502c 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -1789,11 +1789,10 @@ GDScriptParser::BreakNode *GDScriptParser::parse_break() {
GDScriptParser::ContinueNode *GDScriptParser::parse_continue() {
if (!can_continue) {
- push_error(R"(Cannot use "continue" outside of a loop or pattern matching block.)");
+ push_error(R"(Cannot use "continue" outside of a loop.)");
}
current_suite->has_continue = true;
ContinueNode *cont = alloc_node<ContinueNode>();
- cont->is_for_match = is_continue_match;
complete_extents(cont);
end_statement(R"("continue")");
return cont;
@@ -1819,12 +1818,10 @@ GDScriptParser::ForNode *GDScriptParser::parse_for() {
// Save break/continue state.
bool could_break = can_break;
bool could_continue = can_continue;
- bool was_continue_match = is_continue_match;
// Allow break/continue.
can_break = true;
can_continue = true;
- is_continue_match = false;
SuiteNode *suite = alloc_node<SuiteNode>();
if (n_for->variable) {
@@ -1842,7 +1839,6 @@ GDScriptParser::ForNode *GDScriptParser::parse_for() {
// Reset break/continue state.
can_break = could_break;
can_continue = could_continue;
- is_continue_match = was_continue_match;
return n_for;
}
@@ -1979,13 +1975,6 @@ GDScriptParser::MatchBranchNode *GDScriptParser::parse_match_branch() {
return nullptr;
}
- // Save continue state.
- bool could_continue = can_continue;
- bool was_continue_match = is_continue_match;
- // Allow continue for match.
- can_continue = true;
- is_continue_match = true;
-
SuiteNode *suite = alloc_node<SuiteNode>();
if (branch->patterns.size() > 0) {
for (const KeyValue<StringName, IdentifierNode *> &E : branch->patterns[0]->binds) {
@@ -1998,10 +1987,6 @@ GDScriptParser::MatchBranchNode *GDScriptParser::parse_match_branch() {
branch->block = parse_suite("match pattern block", suite);
complete_extents(branch);
- // Restore continue state.
- can_continue = could_continue;
- is_continue_match = was_continue_match;
-
return branch;
}
@@ -2160,12 +2145,10 @@ GDScriptParser::WhileNode *GDScriptParser::parse_while() {
// Save break/continue state.
bool could_break = can_break;
bool could_continue = can_continue;
- bool was_continue_match = is_continue_match;
// Allow break/continue.
can_break = true;
can_continue = true;
- is_continue_match = false;
n_while->loop = parse_suite(R"("while" block)");
n_while->loop->is_loop = true;
@@ -2174,7 +2157,6 @@ GDScriptParser::WhileNode *GDScriptParser::parse_while() {
// Reset break/continue state.
can_break = could_break;
can_continue = could_continue;
- is_continue_match = was_continue_match;
return n_while;
}
diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h
index f6d2a8feee..5da709e8cd 100644
--- a/modules/gdscript/gdscript_parser.h
+++ b/modules/gdscript/gdscript_parser.h
@@ -758,7 +758,6 @@ public:
};
struct ContinueNode : public Node {
- bool is_for_match = false;
ContinueNode() {
type = CONTINUE;
}
@@ -1254,7 +1253,6 @@ private:
bool panic_mode = false;
bool can_break = false;
bool can_continue = false;
- bool is_continue_match = false; // Whether a `continue` will act on a `match`.
List<bool> multiline_stack;
ClassNode *head = nullptr;
diff --git a/modules/gdscript/tests/scripts/parser/features/match.gd b/modules/gdscript/tests/scripts/parser/features/match.gd
index 4d05490aa5..59b5ba2426 100644
--- a/modules/gdscript/tests/scripts/parser/features/match.gd
+++ b/modules/gdscript/tests/scripts/parser/features/match.gd
@@ -3,8 +3,6 @@ func test():
match i:
"Hello":
print("hello")
- # This will fall through to the default case below.
- continue
"Good bye":
print("bye")
_:
diff --git a/modules/gdscript/tests/scripts/parser/features/match.out b/modules/gdscript/tests/scripts/parser/features/match.out
index 732885c7a2..a2cb94399c 100644
--- a/modules/gdscript/tests/scripts/parser/features/match.out
+++ b/modules/gdscript/tests/scripts/parser/features/match.out
@@ -1,4 +1,3 @@
GDTEST_OK
hello
-default
This will match
diff --git a/modules/gdscript/tests/scripts/parser/features/nested_match.gd b/modules/gdscript/tests/scripts/parser/features/nested_match.gd
index aaddcc7e83..491d917a8e 100644
--- a/modules/gdscript/tests/scripts/parser/features/nested_match.gd
+++ b/modules/gdscript/tests/scripts/parser/features/nested_match.gd
@@ -8,11 +8,10 @@ func test():
1234:
print("2")
match number:
- 1234:
- print("3")
- continue
+ 4321:
+ print("Should not be printed")
_:
- print("Should also be printed")
+ print("3")
match number:
1234:
print("4")
diff --git a/modules/gdscript/tests/scripts/parser/features/nested_match.out b/modules/gdscript/tests/scripts/parser/features/nested_match.out
index 651d76cc59..c2d2e29a06 100644
--- a/modules/gdscript/tests/scripts/parser/features/nested_match.out
+++ b/modules/gdscript/tests/scripts/parser/features/nested_match.out
@@ -2,7 +2,6 @@ GDTEST_OK
1
2
3
-Should also be printed
4
5
6
diff --git a/modules/navigation/editor/navigation_mesh_editor_plugin.cpp b/modules/navigation/editor/navigation_mesh_editor_plugin.cpp
index 54f7abda8d..557d45b386 100644
--- a/modules/navigation/editor/navigation_mesh_editor_plugin.cpp
+++ b/modules/navigation/editor/navigation_mesh_editor_plugin.cpp
@@ -60,12 +60,40 @@ void NavigationMeshEditor::_bake_pressed() {
button_bake->set_pressed(false);
ERR_FAIL_COND(!node);
- if (!node->get_navigation_mesh().is_valid()) {
+ Ref<NavigationMesh> navmesh = node->get_navigation_mesh();
+ if (!navmesh.is_valid()) {
err_dialog->set_text(TTR("A NavigationMesh resource must be set or created for this node to work."));
err_dialog->popup_centered();
return;
}
+ String path = navmesh->get_path();
+ if (!path.is_resource_file()) {
+ int srpos = path.find("::");
+ if (srpos != -1) {
+ String base = path.substr(0, srpos);
+ if (ResourceLoader::get_resource_type(base) == "PackedScene") {
+ if (!get_tree()->get_edited_scene_root() || get_tree()->get_edited_scene_root()->get_scene_file_path() != base) {
+ err_dialog->set_text(TTR("Cannot generate navigation mesh because it does not belong to the edited scene. Make it unique first."));
+ err_dialog->popup_centered();
+ return;
+ }
+ } else {
+ if (FileAccess::exists(base + ".import")) {
+ err_dialog->set_text(TTR("Cannot generate navigation mesh because it belongs to a resource which was imported."));
+ err_dialog->popup_centered();
+ return;
+ }
+ }
+ }
+ } else {
+ if (FileAccess::exists(path + ".import")) {
+ err_dialog->set_text(TTR("Cannot generate navigation mesh because the resource was imported from another type."));
+ err_dialog->popup_centered();
+ return;
+ }
+ }
+
NavigationMeshGenerator::get_singleton()->clear(node->get_navigation_mesh());
NavigationMeshGenerator::get_singleton()->bake(node->get_navigation_mesh(), node);
diff --git a/scene/3d/lightmap_gi.cpp b/scene/3d/lightmap_gi.cpp
index f308ad0999..fb74cffc94 100644
--- a/scene/3d/lightmap_gi.cpp
+++ b/scene/3d/lightmap_gi.cpp
@@ -1545,6 +1545,8 @@ void LightmapGI::_bind_methods() {
BIND_ENUM_CONSTANT(GENERATE_PROBES_SUBDIV_32);
BIND_ENUM_CONSTANT(BAKE_ERROR_OK);
+ BIND_ENUM_CONSTANT(BAKE_ERROR_NO_SCENE_ROOT);
+ BIND_ENUM_CONSTANT(BAKE_ERROR_FOREIGN_DATA);
BIND_ENUM_CONSTANT(BAKE_ERROR_NO_LIGHTMAPPER);
BIND_ENUM_CONSTANT(BAKE_ERROR_NO_SAVE_PATH);
BIND_ENUM_CONSTANT(BAKE_ERROR_NO_MESHES);
diff --git a/scene/3d/lightmap_gi.h b/scene/3d/lightmap_gi.h
index 1294571cc0..40ff9e4cad 100644
--- a/scene/3d/lightmap_gi.h
+++ b/scene/3d/lightmap_gi.h
@@ -124,6 +124,8 @@ public:
enum BakeError {
BAKE_ERROR_OK,
+ BAKE_ERROR_NO_SCENE_ROOT,
+ BAKE_ERROR_FOREIGN_DATA,
BAKE_ERROR_NO_LIGHTMAPPER,
BAKE_ERROR_NO_SAVE_PATH,
BAKE_ERROR_NO_MESHES,
diff --git a/scene/main/node.cpp b/scene/main/node.cpp
index def91c424c..0b1f8f101c 100644
--- a/scene/main/node.cpp
+++ b/scene/main/node.cpp
@@ -2947,6 +2947,7 @@ void Node::_bind_methods() {
BIND_CONSTANT(NOTIFICATION_POST_ENTER_TREE);
BIND_CONSTANT(NOTIFICATION_DISABLED);
BIND_CONSTANT(NOTIFICATION_ENABLED);
+ BIND_CONSTANT(NOTIFICATION_NODE_RECACHE_REQUESTED);
BIND_CONSTANT(NOTIFICATION_EDITOR_PRE_SAVE);
BIND_CONSTANT(NOTIFICATION_EDITOR_POST_SAVE);
diff --git a/scene/main/node.h b/scene/main/node.h
index dbdcca6170..493578bc5b 100644
--- a/scene/main/node.h
+++ b/scene/main/node.h
@@ -270,6 +270,7 @@ public:
NOTIFICATION_POST_ENTER_TREE = 27,
NOTIFICATION_DISABLED = 28,
NOTIFICATION_ENABLED = 29,
+ NOTIFICATION_NODE_RECACHE_REQUESTED = 30,
//keep these linked to node
NOTIFICATION_WM_MOUSE_ENTER = 1002,
diff --git a/scene/resources/packed_scene.cpp b/scene/resources/packed_scene.cpp
index c24186a109..e497a628aa 100644
--- a/scene/resources/packed_scene.cpp
+++ b/scene/resources/packed_scene.cpp
@@ -1046,6 +1046,25 @@ Ref<SceneState> SceneState::get_base_scene_state() const {
return Ref<SceneState>();
}
+void SceneState::update_instance_resource(String p_path, Ref<PackedScene> p_packed_scene) {
+ ERR_FAIL_COND(p_packed_scene.is_null());
+
+ for (const NodeData &nd : nodes) {
+ if (nd.instance >= 0) {
+ if (!(nd.instance & FLAG_INSTANCE_IS_PLACEHOLDER)) {
+ int instance_id = nd.instance & FLAG_MASK;
+ Ref<PackedScene> original_packed_scene = variants[instance_id];
+ if (original_packed_scene.is_valid()) {
+ if (original_packed_scene->get_path() == p_path) {
+ variants.remove_at(instance_id);
+ variants.insert(instance_id, p_packed_scene);
+ }
+ }
+ }
+ }
+ }
+}
+
int SceneState::find_node_by_path(const NodePath &p_node) const {
ERR_FAIL_COND_V_MSG(node_path_cache.size() == 0, -1, "This operation requires the node cache to have been built.");
diff --git a/scene/resources/packed_scene.h b/scene/resources/packed_scene.h
index ef7363dd44..5c53ffdb45 100644
--- a/scene/resources/packed_scene.h
+++ b/scene/resources/packed_scene.h
@@ -150,6 +150,8 @@ public:
Ref<SceneState> get_base_scene_state() const;
+ void update_instance_resource(String p_path, Ref<PackedScene> p_packed_scene);
+
//unbuild API
int get_node_count() const;