From 31c87b72326d8a31b58948504e45c9322e723a03 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 --- editor/gui/scene_tree_editor.cpp | 242 +++++++++++++++++++++---------- editor/gui/scene_tree_editor.h | 15 +- scene/gui/tree.cpp | 10 ++ scene/gui/tree.h | 1 + 4 files changed, 194 insertions(+), 74 deletions(-) diff --git a/editor/gui/scene_tree_editor.cpp b/editor/gui/scene_tree_editor.cpp index e89912d5bc68..98fce1a4c320 100644 --- a/editor/gui/scene_tree_editor.cpp +++ b/editor/gui/scene_tree_editor.cpp @@ -217,7 +217,7 @@ void SceneTreeEditor::_toggle_visible(Node *p_node) { } } -void SceneTreeEditor::_add_nodes(Node *p_node, TreeItem *p_parent) { +void SceneTreeEditor::_update_nodes(Node *p_node, TreeItem *p_parent) { if (!p_node) { return; } @@ -238,34 +238,124 @@ void SceneTreeEditor::_add_nodes(Node *p_node, TreeItem *p_parent) { 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; + int node_index = p_node->get_index(false); + HashMap::Iterator I = node_cache.find(p_node); - item->set_text(0, p_node->get_name()); - item->set_text_overrun_behavior(0, TextServer::OVERRUN_NO_TRIMMING); - if (can_rename && !part_of_subscene) { - item->set_editable(0, true); + if (I) { + item = I->value.item; + if (marked.has(p_node)) { + if (!I->value.marked) { + I->value.marked = true; + I->value.dirty = true; + } + } else { + if (I->value.marked) { + I->value.marked = false; + I->value.dirty = true; + } + } + } else { + // We don't set the node_index cache here. As undo/redo might've just recreated this. + item = tree->create_item(p_parent); + CachedNode cached_node; + cached_node.item = item; + I = node_cache.insert(p_node, cached_node); + } + + // Do a quick check to see if we are where we expect to be. + // We can't only do this when we're dirty as moving one node will necessarily move our siblings. + if (p_parent && I->value.index != node_index) { + if (node_index < p_parent->get_child_count()) { + TreeItem *new_neigbor = p_parent->get_child(node_index); + + if (new_neigbor != item) { + if (node_index == p_parent->get_child_count()) { + item->move_after(new_neigbor); + } else { + item->move_before(new_neigbor); + } + } + } + + I->value.index = node_index; } + // Selection might have changed. item->set_selectable(0, true); + _set_item_custom_color(item, Color(0, 0, 0, 0)); + item->clear_custom_color(0); + + if (I->value.dirty) { + I->value.dirty = false; + _update_node(p_node, item, part_of_subscene); + } else { + // A parent might have moved/renamed. + item->set_metadata(0, p_node->get_path()); + } + + for (int i = 0; i < p_node->get_child_count(); i++) { + _update_nodes(p_node->get_child(i), item); + } + + 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