From d31a7f89431f22eebb453e7d82e02c33f30e3932 Mon Sep 17 00:00:00 2001 From: HP van Braam Date: Tue, 26 Nov 2024 00:04:25 +0100 Subject: [PATCH] Improve Scene Tree editor performance We now cache the Node*<>TreeItem* mapping in the SceneTreeEditor. This allows us to make targeted updates to the Tree used to display the scene tree in the editor. Previously on almost all changes to the scene tree the editor would rebuild the entire widget, causing a large number of deallocations an allocations. We now carefully manipulate the Tree widget in-situ saving a large number of these allocations. There is definitely more that could be done, but this is already a massive improvement. This fixes #83460 --- doc/classes/Node.xml | 5 + editor/connections_dialog.cpp | 1 + editor/gui/scene_tree_editor.cpp | 608 +++++++++++++++++++++++++------ editor/gui/scene_tree_editor.h | 57 ++- editor/reparent_dialog.cpp | 1 + scene/gui/tree.cpp | 12 +- scene/gui/tree.h | 1 + scene/main/node.cpp | 56 ++- scene/main/node.h | 7 + 9 files changed, 631 insertions(+), 117 deletions(-) diff --git a/doc/classes/Node.xml b/doc/classes/Node.xml index 1e12d619e22f..cbf6d048ebbd 100644 --- a/doc/classes/Node.xml +++ b/doc/classes/Node.xml @@ -1068,6 +1068,11 @@ Emitted when the node's editor description field changed. + + + Emitted when an attribute of the node that is relevant to the editor is changed. Only emitted in the editor. + + Emitted when the node is considered ready, after [method _ready] is called. diff --git a/editor/connections_dialog.cpp b/editor/connections_dialog.cpp index d76c324be05c..eb2ecc44d5d9 100644 --- a/editor/connections_dialog.cpp +++ b/editor/connections_dialog.cpp @@ -735,6 +735,7 @@ ConnectDialog::ConnectDialog() { from_signal->set_editable(false); tree = memnew(SceneTreeEditor(false)); + tree->set_update_when_invisible(false); tree->set_connecting_signal(true); tree->set_show_enabled_subscene(true); tree->set_v_size_flags(Control::SIZE_FILL | Control::SIZE_EXPAND); diff --git a/editor/gui/scene_tree_editor.cpp b/editor/gui/scene_tree_editor.cpp index fc3fb55d45c4..d802e24496d7 100644 --- a/editor/gui/scene_tree_editor.cpp +++ b/editor/gui/scene_tree_editor.cpp @@ -61,7 +61,7 @@ void SceneTreeEditor::_cell_button_pressed(Object *p_item, int p_column, int p_i } if (connect_to_script_mode) { - return; //don't do anything in this mode + return; // Don't do anything in this mode. } TreeItem *item = Object::cast_to(p_item); @@ -152,7 +152,8 @@ void SceneTreeEditor::_cell_button_pressed(Object *p_item, int p_column, int p_i const String line = all_warnings.substr(start, end - start); lines.append(line); } - all_warnings = String("\n").join(lines).indent(" ").replace(U" •", U"\n•").substr(2); // We don't want the first two newlines. + // We don't want the first two newlines. + all_warnings = String("\n").join(lines).indent(" ").replace(U" •", U"\n•").substr(2); warning->set_text(all_warnings); warning->popup_centered(); @@ -217,55 +218,175 @@ void SceneTreeEditor::_toggle_visible(Node *p_node) { } } -void SceneTreeEditor::_add_nodes(Node *p_node, TreeItem *p_parent) { +void SceneTreeEditor::_update_node_path(Node *p_node, bool p_recursive) { if (!p_node) { return; } - // only owned nodes are editable, since nodes can create their own (manually owned) child nodes, + HashMap::Iterator I = node_cache.get(p_node); + if (!I) { + return; + } + + I->value.item->set_metadata(0, p_node->get_path()); + + if (!p_recursive) { + return; + } + + int cc = p_node->get_child_count(false); + for (int i = 0; i < cc; i++) { + Node *c = p_node->get_child(i, false); + _update_node_path(c, p_recursive); + } +} + +void SceneTreeEditor::_update_node_subtree(Node *p_node, TreeItem *p_parent, bool force) { + if (!p_node) { + return; + } + + // Only owned nodes are editable, since nodes can create their own (manually owned) child nodes, // which the editor needs not to know about. bool part_of_subscene = false; + HashMap::Iterator I = node_cache.get(p_node); if (!display_foreign && p_node->get_owner() != get_scene_node() && p_node != get_scene_node()) { if ((show_enabled_subscene || can_open_instance) && p_node->get_owner() && (get_scene_node()->is_editable_instance(p_node->get_owner()))) { part_of_subscene = true; - //allow + // Allow. } else { + if (I) { + // Stale node, remove recursively. + node_cache.remove(p_node, true); + } return; } } else { part_of_subscene = p_node != get_scene_node() && get_scene_node()->get_scene_inherited_state().is_valid() && get_scene_node()->get_scene_inherited_state()->find_node_by_path(get_scene_node()->get_path_to(p_node)) >= 0; } - TreeItem *item = tree->create_item(p_parent); + TreeItem *item; + + bool is_new = false; + + if (I) { + item = I->value.item; + } else { + // Check to see if there is a root node for us to reuse. + if (!p_parent) { + item = tree->get_root(); + if (!item) { + item = tree->create_item(nullptr); + } + } else { + item = tree->create_item(p_parent, p_node->get_index(false)); + } + + I = node_cache.add(p_node, item); + is_new = true; + } + + if (item->get_parent() != p_parent) { + p_parent->add_child(item); + _move_node_item(p_parent, I); + } + + bool dirty = force || I->value.dirty; + + if (I->value.has_moved_children) { + _move_node_children(I); + } + + if (dirty) { + _update_node(p_node, item, part_of_subscene); + I->value.dirty = false; + I->value.can_process = p_node->can_process(); + + // Force update all our children if we are new or if we were forced to update. + bool force_update_children = force || is_new; + // Update all our children. + for (int i = 0; i < p_node->get_child_count(false); i++) { + _update_node_subtree(p_node->get_child(i, false), item, force_update_children); + } + } - item->set_text(0, p_node->get_name()); - item->set_text_overrun_behavior(0, TextServer::OVERRUN_NO_TRIMMING); + if (valid_types.size()) { + bool valid = false; + for (const StringName &E : valid_types) { + if (p_node->is_class(E) || + EditorNode::get_singleton()->is_object_of_custom_type(p_node, E)) { + valid = true; + break; + } else { + Ref