Path: blob/master/modules/objectdb_profiler/editor/data_viewers/class_view.cpp
20897 views
/**************************************************************************/1/* class_view.cpp */2/**************************************************************************/3/* This file is part of: */4/* GODOT ENGINE */5/* https://godotengine.org */6/**************************************************************************/7/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */8/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */9/* */10/* Permission is hereby granted, free of charge, to any person obtaining */11/* a copy of this software and associated documentation files (the */12/* "Software"), to deal in the Software without restriction, including */13/* without limitation the rights to use, copy, modify, merge, publish, */14/* distribute, sublicense, and/or sell copies of the Software, and to */15/* permit persons to whom the Software is furnished to do so, subject to */16/* the following conditions: */17/* */18/* The above copyright notice and this permission notice shall be */19/* included in all copies or substantial portions of the Software. */20/* */21/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */22/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */23/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */24/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */25/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */26/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */27/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */28/**************************************************************************/2930#include "class_view.h"3132#include "shared_controls.h"3334#include "editor/editor_node.h"35#include "editor/themes/editor_scale.h"36#include "scene/gui/split_container.h"3738int ClassData::instance_count(GameStateSnapshot *p_snapshot) {39int count = 0;40for (const SnapshotDataObject *instance : instances) {41if (!p_snapshot || instance->snapshot == p_snapshot) {42count += 1;43}44}45return count;46}4748int ClassData::get_recursive_instance_count(HashMap<String, ClassData> &p_all_classes, GameStateSnapshot *p_snapshot) {49if (!recursive_instance_count_cache.has(p_snapshot)) {50recursive_instance_count_cache[p_snapshot] = instance_count(p_snapshot);51for (const String &child : child_classes) {52recursive_instance_count_cache[p_snapshot] += p_all_classes[child].get_recursive_instance_count(p_all_classes, p_snapshot);53}54}55return recursive_instance_count_cache[p_snapshot];56}5758SnapshotClassView::SnapshotClassView() {59set_name(TTRC("Classes"));60}6162void SnapshotClassView::show_snapshot(GameStateSnapshot *p_data, GameStateSnapshot *p_diff_data) {63SnapshotView::show_snapshot(p_data, p_diff_data);6465set_v_size_flags(SizeFlags::SIZE_EXPAND_FILL);66set_h_size_flags(SizeFlags::SIZE_EXPAND_FILL);6768HSplitContainer *classes_view = memnew(HSplitContainer);69add_child(classes_view);70classes_view->set_anchors_preset(LayoutPreset::PRESET_FULL_RECT);71classes_view->set_v_size_flags(SizeFlags::SIZE_EXPAND_FILL);72classes_view->set_h_size_flags(SizeFlags::SIZE_EXPAND_FILL);73classes_view->set_split_offset(0);7475VBoxContainer *class_list_column = memnew(VBoxContainer);76class_list_column->set_v_size_flags(SizeFlags::SIZE_EXPAND_FILL);77class_list_column->set_h_size_flags(SizeFlags::SIZE_EXPAND_FILL);78classes_view->add_child(class_list_column);7980class_tree = memnew(Tree);8182TreeSortAndFilterBar *filter_bar = memnew(TreeSortAndFilterBar(class_tree, TTRC("Filter Classes")));83filter_bar->add_sort_option(TTRC("Name"), TreeSortAndFilterBar::SortType::ALPHA_SORT, 0);8485TreeSortAndFilterBar::SortOptionIndexes default_sort;86if (!diff_data) {87default_sort = filter_bar->add_sort_option(TTRC("Count"), TreeSortAndFilterBar::SortType::NUMERIC_SORT, 1);88} else {89filter_bar->add_sort_option(TTRC("A Count"), TreeSortAndFilterBar::SortType::NUMERIC_SORT, 1);90filter_bar->add_sort_option(TTRC("B Count"), TreeSortAndFilterBar::SortType::NUMERIC_SORT, 2);91default_sort = filter_bar->add_sort_option(TTRC("Delta"), TreeSortAndFilterBar::SortType::NUMERIC_SORT, 3);92}93class_list_column->add_child(filter_bar);9495class_tree->set_select_mode(Tree::SelectMode::SELECT_ROW);96class_tree->set_custom_minimum_size(Size2(200 * EDSCALE, 0));97class_tree->set_hide_folding(false);98class_tree->set_hide_root(true);99class_tree->set_columns(diff_data ? 4 : 2);100class_tree->set_column_titles_visible(true);101class_tree->set_column_title(0, TTRC("Class"));102class_tree->set_column_expand(0, true);103class_tree->set_column_custom_minimum_width(0, 200 * EDSCALE);104class_tree->set_column_title(1, diff_data ? TTRC("A Count") : TTRC("Count"));105class_tree->set_column_expand(1, false);106class_tree->set_theme_type_variation("TreeSecondary");107108if (diff_data) {109class_tree->set_column_title_tooltip_text(1, vformat(TTR("A: %s"), snapshot_data->name));110class_tree->set_column_title_tooltip_text(2, vformat(TTR("B: %s"), diff_data->name));111class_tree->set_column_title(2, TTRC("B Count"));112class_tree->set_column_expand(2, false);113class_tree->set_column_title(3, TTRC("Delta"));114class_tree->set_column_expand(3, false);115}116117class_tree->connect(SceneStringName(item_selected), callable_mp(this, &SnapshotClassView::_class_selected));118class_tree->set_h_size_flags(SizeFlags::SIZE_EXPAND_FILL);119class_tree->set_v_size_flags(SizeFlags::SIZE_EXPAND_FILL);120class_tree->set_anchors_preset(LayoutPreset::PRESET_FULL_RECT);121class_list_column->add_child(class_tree);122123VSplitContainer *object_lists = memnew(VSplitContainer);124classes_view->add_child(object_lists);125object_lists->set_custom_minimum_size(Size2(150 * EDSCALE, 0));126object_lists->set_h_size_flags(SizeFlags::SIZE_EXPAND_FILL);127object_lists->set_v_size_flags(SizeFlags::SIZE_EXPAND_FILL);128if (!diff_data) {129object_lists->add_child(object_list = _make_object_list_tree(TTRC("Objects")));130} else {131object_lists->add_child(object_list = _make_object_list_tree(TTRC("A Objects")));132object_lists->add_child(diff_object_list = _make_object_list_tree(TTRC("B Objects")));133}134135HashMap<String, ClassData> grouped_by_class;136grouped_by_class["Object"] = ClassData("Object", "");137_add_objects_to_class_map(grouped_by_class, snapshot_data);138if (diff_data != nullptr) {139_add_objects_to_class_map(grouped_by_class, diff_data);140}141142grouped_by_class[""].tree_node = class_tree->create_item();143List<String> classes_todo;144for (const String &c : grouped_by_class[""].child_classes) {145classes_todo.push_front(c);146}147while (classes_todo.size() > 0) {148String next_class_name = classes_todo.front()->get();149classes_todo.pop_front();150ClassData &next = grouped_by_class[next_class_name];151ClassData &nexts_parent = grouped_by_class[next.parent_class_name];152next.tree_node = class_tree->create_item(nexts_parent.tree_node);153next.tree_node->set_text(0, next_class_name + " (" + String::num_int64(next.instance_count(snapshot_data)) + ")");154next.tree_node->set_auto_translate_mode(0, AUTO_TRANSLATE_MODE_DISABLED);155int a_count = next.get_recursive_instance_count(grouped_by_class, snapshot_data);156next.tree_node->set_text(1, String::num_int64(a_count));157if (diff_data) {158int b_count = next.get_recursive_instance_count(grouped_by_class, diff_data);159next.tree_node->set_text(2, String::num_int64(b_count));160next.tree_node->set_text(3, String::num_int64(a_count - b_count));161}162next.tree_node->set_metadata(0, next_class_name);163for (const String &c : next.child_classes) {164classes_todo.push_front(c);165}166}167168// Icons won't load until the frame after show_snapshot is called. Not sure why, but just defer the load.169callable_mp(this, &SnapshotClassView::_notification).call_deferred(NOTIFICATION_THEME_CHANGED);170171// Default to sort by descending count. Putting the biggest groups at the top is generally pretty interesting.172filter_bar->select_sort(default_sort.descending);173filter_bar->apply();174}175176Tree *SnapshotClassView::_make_object_list_tree(const String &p_column_name) {177Tree *list = memnew(Tree);178list->set_select_mode(Tree::SelectMode::SELECT_ROW);179list->set_hide_folding(true);180list->set_hide_root(true);181list->set_columns(1);182list->set_column_titles_visible(true);183list->set_column_title(0, p_column_name);184list->set_column_expand(0, true);185list->set_theme_type_variation("TreeSecondary");186list->connect(SceneStringName(item_selected), callable_mp(this, &SnapshotClassView::_object_selected).bind(list));187list->set_h_size_flags(SizeFlags::SIZE_EXPAND_FILL);188list->set_v_size_flags(SizeFlags::SIZE_EXPAND_FILL);189return list;190}191192void SnapshotClassView::_add_objects_to_class_map(HashMap<String, ClassData> &p_class_map, GameStateSnapshot *p_objects) {193for (const KeyValue<ObjectID, SnapshotDataObject *> &pair : p_objects->objects) {194StringName class_name = pair.value->type_name;195StringName parent_class_name = !class_name.is_empty() && ClassDB::class_exists(class_name) ? ClassDB::get_parent_class(class_name) : "";196197p_class_map[class_name].instances.push_back(pair.value);198199// Go up the tree and insert all parents/grandparents.200while (!class_name.is_empty()) {201if (!p_class_map.has(class_name)) {202p_class_map[class_name] = ClassData(class_name, parent_class_name);203}204205if (!p_class_map.has(parent_class_name)) {206// Leave our grandparent blank for now. Next iteration of the while loop will fill it in.207p_class_map[parent_class_name] = ClassData(parent_class_name, "");208}209p_class_map[class_name].parent_class_name = parent_class_name;210p_class_map[parent_class_name].child_classes.insert(class_name);211212class_name = parent_class_name;213parent_class_name = !class_name.is_empty() ? ClassDB::get_parent_class(class_name) : "";214}215}216}217218void SnapshotClassView::_object_selected(Tree *p_tree) {219GameStateSnapshot *snapshot = snapshot_data;220if (diff_data) {221Tree *other = p_tree == diff_object_list ? object_list : diff_object_list;222TreeItem *selected = other->get_selected();223if (selected) {224selected->deselect(0);225}226if (p_tree == diff_object_list) {227snapshot = diff_data;228}229}230ObjectID object_id = p_tree->get_selected()->get_metadata(0);231EditorNode::get_singleton()->push_item(static_cast<Object *>(snapshot->objects[object_id]));232}233234void SnapshotClassView::_class_selected() {235_update_lists();236}237238void SnapshotClassView::_populate_object_list(GameStateSnapshot *p_snapshot, Tree *p_list, const String &p_name_base) {239p_list->clear();240241TreeItem *selected_item = class_tree->get_selected();242if (selected_item == nullptr) {243p_list->set_column_title(0, vformat("%s (0)", TTR(p_name_base)));244return;245}246247String class_name = selected_item->get_metadata(0);248TreeItem *root = p_list->create_item();249int object_count = 0;250for (const KeyValue<ObjectID, SnapshotDataObject *> &pair : p_snapshot->objects) {251if (pair.value->type_name == class_name) {252TreeItem *item = p_list->create_item(root);253item->set_auto_translate_mode(0, AUTO_TRANSLATE_MODE_DISABLED);254item->set_text(0, pair.value->get_name());255item->set_metadata(0, pair.value->remote_object_id);256item->set_text_overrun_behavior(0, TextServer::OverrunBehavior::OVERRUN_NO_TRIMMING);257object_count++;258}259}260261p_list->set_column_title(0, vformat("%s (%d)", TTR(p_name_base), object_count));262}263264void SnapshotClassView::_update_lists() {265if (snapshot_data == nullptr) {266return;267}268269if (!diff_data) {270_populate_object_list(snapshot_data, object_list, TTRC("Objects"));271} else {272_populate_object_list(snapshot_data, object_list, TTRC("A Objects"));273_populate_object_list(diff_data, diff_object_list, TTRC("B Objects"));274}275}276277void SnapshotClassView::_notification(int p_what) {278if (p_what == NOTIFICATION_THEME_CHANGED) {279for (TreeItem *item : _get_children_recursive(class_tree)) {280item->set_icon(0, EditorNode::get_singleton()->get_class_icon(item->get_metadata(0), ""));281}282} else if (p_what == NOTIFICATION_TRANSLATION_CHANGED) {283_update_lists();284}285}286287288