diff options
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; |