From e68e0236e97f5f04be9e0aadf77b5041c07a9db0 Mon Sep 17 00:00:00 2001 From: monxa <84077629+monxa@users.noreply.github.com> Date: Sun, 29 Sep 2024 14:50:07 +0200 Subject: [PATCH] Improve performance for TreeSearch (#1) * Improve TreeSearch performance. Experimental, hence this is on a different branch. This commit vastly improves performance by not updating the tree for search mask changes. Relates to: https://github.com/limbonaut/limboai/pull/229 * Fix SearchTree overdraw after performance optimization * Manage Performance optimizations: TreeSearch no. 2 - Carefully manage callable_cache - Only clear filter when previously filtered - Reintroduce sorting for ordered_tree_items This commit addresses performance issues in TreeSearch and fixes a critical bug where ordered_tree_items was not being sorted. The bug was introduced during a merge with the main feature branch. * Use queue_redraw as much as possible for Tree updates. * Fix TreeSearch after performance considerations --- editor/task_tree.cpp | 4 +- editor/tree_search.cpp | 131 +++++++++++++++++++++++++++-------------- editor/tree_search.h | 11 +++- 3 files changed, 100 insertions(+), 46 deletions(-) diff --git a/editor/task_tree.cpp b/editor/task_tree.cpp index 7a4c5bc..a976ca0 100644 --- a/editor/task_tree.cpp +++ b/editor/task_tree.cpp @@ -538,8 +538,8 @@ void TaskTree::_notification(int p_what) { tree->connect("multi_selected", callable_mp(this, &TaskTree::_on_item_selected).unbind(3), CONNECT_DEFERRED); tree->connect("item_activated", callable_mp(this, &TaskTree::_on_item_activated)); tree->connect("item_collapsed", callable_mp(this, &TaskTree::_on_item_collapsed)); - tree_search_panel->connect("update_requested", callable_mp(this, &TaskTree::_update_tree)); - tree_search_panel->connect("visibility_changed", callable_mp(this, &TaskTree::_update_tree)); + tree_search_panel->connect("update_requested", callable_mp(tree_search.ptr(), &TreeSearch::update_search).bind(tree)); + tree_search_panel->connect("visibility_changed", callable_mp(tree_search.ptr(), &TreeSearch::update_search).bind(tree)); } break; case NOTIFICATION_THEME_CHANGED: { _do_update_theme_item_cache(); diff --git a/editor/tree_search.cpp b/editor/tree_search.cpp index 3fafd38..e0494d1 100644 --- a/editor/tree_search.cpp +++ b/editor/tree_search.cpp @@ -41,6 +41,20 @@ /* ------- TreeSearch ------- */ +void TreeSearch::_clean_callable_cache() { + ERR_FAIL_COND(!tree_reference); + HashMap new_callable_cache; + new_callable_cache.reserve(callable_cache.size()); // Efficiency + + for (int i = 0; i < ordered_tree_items.size(); i++) { + TreeItem *cur_item = ordered_tree_items[i]; + if (callable_cache.has(cur_item)) { + new_callable_cache[cur_item] = callable_cache[cur_item]; + } + } + callable_cache = new_callable_cache; +} + void TreeSearch::_filter_tree(const String &p_search_mask) { if (matching_entries.size() == 0) { return; @@ -63,42 +77,66 @@ void TreeSearch::_filter_tree(const String &p_search_mask) { } } -void TreeSearch::_highlight_tree(const String &p_search_mask) { - callable_cache.clear(); - for (int i = 0; i < ordered_tree_items.size(); i++) { - TreeItem *entry = ordered_tree_items[i]; +// makes all tree items visible. +void TreeSearch::_clear_filter() { + ERR_FAIL_COND(!tree_reference); + Vector items = { tree_reference->get_root() }; + for (int idx = 0; idx < items.size(); idx++) { + TreeItem *cur_item = items[idx]; + cur_item->set_visible(true); - int num_m = number_matches.has(entry) ? number_matches.get(entry) : 0; - if (num_m == 0) { - continue; + for (int i = 0; i < cur_item->get_child_count(); i++) { + items.push_back(cur_item->get_child(i)); } - - // make sure to also call any draw method already defined. - Callable parent_draw_method; - if (entry->get_cell_mode(0) == TreeItem::CELL_MODE_CUSTOM) { - parent_draw_method = entry->get_custom_draw_callback(0); - } - - Callable draw_callback = callable_mp(this, &TreeSearch::_draw_highlight_item).bind(parent_draw_method); - - // -- this is necessary because of the modularity of this implementation - // cache render properties of entry - String cached_text = entry->get_text(0); - Ref cached_icon = entry->get_icon(0); - int cached_max_width = entry->get_icon_max_width(0); - callable_cache[entry] = draw_callback; - - // this removes render properties in entry - entry->set_custom_draw_callback(0, draw_callback); - entry->set_cell_mode(0, TreeItem::CELL_MODE_CUSTOM); - - // restore render properties - entry->set_text(0, cached_text); - entry->set_icon(0, cached_icon); - entry->set_icon_max_width(0, cached_max_width); } } +void TreeSearch::_highlight_tree() { + ERR_FAIL_COND(!tree_reference); + for (int i = 0; i < matching_entries.size(); i++) { + TreeItem *tree_item = matching_entries[i]; + _highlight_tree_item(tree_item); + } + tree_reference->queue_redraw(); +} + +void TreeSearch::_highlight_tree_item(TreeItem *p_tree_item) { + int num_m = number_matches.has(p_tree_item) ? number_matches.get(p_tree_item) : 0; + + if (num_m == 0) { + return; + } + + // make sure to also call any draw method already defined. + Callable parent_draw_method; + if (p_tree_item->get_cell_mode(0) == TreeItem::CELL_MODE_CUSTOM) { + parent_draw_method = p_tree_item->get_custom_draw_callback(0); + } + + // if the cached draw method is already applied, do nothing. + if (callable_cache.has(p_tree_item) && parent_draw_method == callable_cache.get(p_tree_item)){ + return; + } + + Callable draw_callback = callable_mp(this, &TreeSearch::_draw_highlight_item).bind(parent_draw_method); + + // -- this is necessary because of the modularity of this implementation + // cache render properties of entry + String cached_text = p_tree_item->get_text(0); + Ref cached_icon = p_tree_item->get_icon(0); + int cached_max_width = p_tree_item->get_icon_max_width(0); + callable_cache[p_tree_item] = draw_callback; + + // this removes render properties in entry + p_tree_item->set_custom_draw_callback(0, draw_callback); + p_tree_item->set_cell_mode(0, TreeItem::CELL_MODE_CUSTOM); + + // restore render properties + p_tree_item->set_text(0, cached_text); + p_tree_item->set_icon(0, cached_icon); + p_tree_item->set_icon_max_width(0, cached_max_width); +} + // custom draw callback for highlighting (bind the parent_drw_method to this) void TreeSearch::_draw_highlight_item(TreeItem *p_tree_item, Rect2 p_rect, Callable p_parent_draw_method) { if (!p_tree_item) { @@ -143,7 +181,7 @@ void TreeSearch::_draw_highlight_item(TreeItem *p_tree_item, Rect2 p_rect, Calla Vector2 rect_offset = Vector2(substring_before_size.x, 0); rect_offset.x += p_tree_item->get_icon_max_width(0); - rect_offset.x += (h_sep + 4. * EDSCALE); // TODO: Find better way to determine texts x-offset + rect_offset.x += (h_sep + 4. * EDSCALE); rect_offset.y = (p_rect.size.y - substring_match_size.y) / 2; // center box vertically draw_rect.position += rect_offset - PADDING / 2; @@ -354,24 +392,26 @@ void TreeSearch::notify_item_edited(TreeItem *item) { if (item->get_cell_mode(0) != TreeItem::CELL_MODE_CUSTOM) { return; } - - if (!callable_cache.has(item) || item->get_custom_draw_callback(0) == callable_cache.get(item)) { - return; - } - - item->set_custom_draw_callback(0, callable_cache.get(item)); + _highlight_tree_item(item); } // Call this as a post-processing step for the already constructed tree. void TreeSearch::update_search(Tree *p_tree) { ERR_FAIL_COND(!search_panel || !p_tree); - // ignore if panel not visible or no search string is given. + tree_reference = p_tree; + if (!search_panel->is_visible() || search_panel->get_text().length() == 0) { + // clear and redraw if search was active recently. + if (was_searched_recently) { + _clear_filter(); + matching_entries.clear(); + was_searched_recently = false; + p_tree->queue_redraw(); + } return; } - - tree_reference = p_tree; + was_searched_recently = true; String search_mask = search_panel->get_text(); TreeSearchMode search_mode = search_panel->get_search_mode(); @@ -380,11 +420,16 @@ void TreeSearch::update_search(Tree *p_tree) { _update_matching_entries(search_mask); _update_number_matches(); - _highlight_tree(search_mask); - + _clear_filter(); + _highlight_tree(); if (search_mode == TreeSearchMode::FILTER) { _filter_tree(search_mask); + was_filtered_recently = true; + } else if (was_filtered_recently) { + _clear_filter(); + was_filtered_recently = false; } + _clean_callable_cache(); } TreeSearch::TreeSearch(TreeSearchPanel *p_search_panel) { diff --git a/editor/tree_search.h b/editor/tree_search.h index 3c1174e..1dbb2d2 100644 --- a/editor/tree_search.h +++ b/editor/tree_search.h @@ -53,14 +53,23 @@ private: // For TaskTree: These are updated when the tree is updated through TaskTree::_create_tree. Tree *tree_reference; + Vector ordered_tree_items; Vector matching_entries; HashMap number_matches; HashMap callable_cache; + bool was_searched_recently = false; // Performance + bool was_filtered_recently = false; // Performance + + void _clean_callable_cache(); + // Update_search() calls these void _filter_tree(const String &p_search_mask); - void _highlight_tree(const String &p_search_mask); + void _clear_filter(); + + void _highlight_tree(); + void _highlight_tree_item(TreeItem *p_tree_item); // Custom draw-Callback (bind inherited Callable). void _draw_highlight_item(TreeItem *p_tree_item, Rect2 p_rect, Callable p_parent_draw_method);