Path: blob/master/modules/objectdb_profiler/editor/data_viewers/summary_view.cpp
11325 views
/**************************************************************************/1/* summary_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 "summary_view.h"3132#include "core/os/time.h"33#include "editor/editor_node.h"34#include "scene/gui/center_container.h"35#include "scene/gui/label.h"36#include "scene/gui/panel_container.h"37#include "scene/gui/rich_text_label.h"38#include "scene/resources/style_box_flat.h"3940SnapshotSummaryView::SnapshotSummaryView() {41set_name(TTRC("Summary"));4243set_v_size_flags(SizeFlags::SIZE_EXPAND_FILL);44set_h_size_flags(SizeFlags::SIZE_EXPAND_FILL);4546MarginContainer *mc = memnew(MarginContainer);47mc->add_theme_constant_override("margin_left", 5);48mc->add_theme_constant_override("margin_right", 5);49mc->add_theme_constant_override("margin_top", 5);50mc->add_theme_constant_override("margin_bottom", 5);51mc->set_anchors_preset(LayoutPreset::PRESET_FULL_RECT);52PanelContainer *content_wrapper = memnew(PanelContainer);53content_wrapper->set_anchors_preset(LayoutPreset::PRESET_FULL_RECT);54Ref<StyleBoxFlat> content_wrapper_sbf;55content_wrapper_sbf.instantiate();56content_wrapper_sbf->set_bg_color(EditorNode::get_singleton()->get_editor_theme()->get_color("dark_color_2", "Editor"));57content_wrapper->add_theme_style_override(SceneStringName(panel), content_wrapper_sbf);58content_wrapper->add_child(mc);59add_child(content_wrapper);6061VBoxContainer *content = memnew(VBoxContainer);62mc->add_child(content);63content->set_anchors_preset(LayoutPreset::PRESET_FULL_RECT);6465PanelContainer *pc = memnew(PanelContainer);66Ref<StyleBoxFlat> sbf;67sbf.instantiate();68sbf->set_bg_color(EditorNode::get_singleton()->get_editor_theme()->get_color("dark_color_3", "Editor"));69pc->add_theme_style_override("panel", sbf);70content->add_child(pc);71pc->set_anchors_preset(LayoutPreset::PRESET_TOP_WIDE);72Label *title = memnew(Label(TTRC("ObjectDB Snapshot Summary")));73pc->add_child(title);74title->set_horizontal_alignment(HorizontalAlignment::HORIZONTAL_ALIGNMENT_CENTER);75title->set_vertical_alignment(VerticalAlignment::VERTICAL_ALIGNMENT_CENTER);7677explainer_text = memnew(CenterContainer);78explainer_text->set_v_size_flags(SizeFlags::SIZE_EXPAND_FILL);79explainer_text->set_h_size_flags(SizeFlags::SIZE_EXPAND_FILL);80content->add_child(explainer_text);81VBoxContainer *explainer_lines = memnew(VBoxContainer);82explainer_text->add_child(explainer_lines);83Label *l1 = memnew(Label(TTRC("Press 'Take ObjectDB Snapshot' to snapshot the ObjectDB.")));84Label *l2 = memnew(Label(TTRC("Memory in Godot is either owned natively by the engine or owned by the ObjectDB.")));85Label *l3 = memnew(Label(TTRC("ObjectDB Snapshots capture only memory owned by the ObjectDB.")));86l1->set_horizontal_alignment(HorizontalAlignment::HORIZONTAL_ALIGNMENT_CENTER);87l2->set_horizontal_alignment(HorizontalAlignment::HORIZONTAL_ALIGNMENT_CENTER);88l3->set_horizontal_alignment(HorizontalAlignment::HORIZONTAL_ALIGNMENT_CENTER);89explainer_lines->add_child(l1);90explainer_lines->add_child(l2);91explainer_lines->add_child(l3);9293ScrollContainer *sc = memnew(ScrollContainer);94sc->set_anchors_preset(LayoutPreset::PRESET_FULL_RECT);95sc->set_v_size_flags(SizeFlags::SIZE_EXPAND_FILL);96sc->set_h_size_flags(SizeFlags::SIZE_EXPAND_FILL);97content->add_child(sc);9899blurb_list = memnew(VBoxContainer);100sc->add_child(blurb_list);101blurb_list->set_v_size_flags(SizeFlags::SIZE_EXPAND_FILL);102blurb_list->set_h_size_flags(SizeFlags::SIZE_EXPAND_FILL);103}104105void SnapshotSummaryView::show_snapshot(GameStateSnapshot *p_data, GameStateSnapshot *p_diff_data) {106SnapshotView::show_snapshot(p_data, p_diff_data);107explainer_text->set_visible(false);108109String snapshot_a_name = diff_data == nullptr ? TTRC("Snapshot") : TTRC("Snapshot A");110String snapshot_b_name = TTRC("Snapshot B");111112_push_overview_blurb(snapshot_a_name + " " + TTRC("Overview"), snapshot_data);113if (diff_data) {114_push_overview_blurb(snapshot_b_name + " " + TTRC("Overview"), diff_data);115}116117_push_node_blurb(snapshot_a_name + " " + TTRC("Nodes"), snapshot_data);118if (diff_data) {119_push_node_blurb(snapshot_b_name + " " + TTRC("Nodes"), diff_data);120}121122_push_refcounted_blurb(snapshot_a_name + " " + TTRC("RefCounteds"), snapshot_data);123if (diff_data) {124_push_refcounted_blurb(snapshot_b_name + " " + TTRC("RefCounteds"), diff_data);125}126127_push_object_blurb(snapshot_a_name + " " + TTRC("Objects"), snapshot_data);128if (diff_data) {129_push_object_blurb(snapshot_b_name + " " + TTRC("Objects"), diff_data);130}131}132133void SnapshotSummaryView::clear_snapshot() {134// Just clear out the blurbs and leave the explainer.135for (int i = 0; i < blurb_list->get_child_count(); i++) {136blurb_list->get_child(i)->queue_free();137}138snapshot_data = nullptr;139diff_data = nullptr;140explainer_text->set_visible(true);141}142143SummaryBlurb::SummaryBlurb(const String &p_title, const String &p_rtl_content) {144add_theme_constant_override("margin_left", 2);145add_theme_constant_override("margin_right", 2);146add_theme_constant_override("margin_top", 2);147add_theme_constant_override("margin_bottom", 2);148149label = memnew(RichTextLabel);150label->add_theme_constant_override(SceneStringName(line_separation), 6);151label->set_text_direction(Control::TEXT_DIRECTION_INHERITED);152label->set_fit_content(true);153label->set_use_bbcode(true);154label->add_newline();155label->push_bold();156label->add_text(p_title);157label->pop();158label->add_newline();159label->add_newline();160label->append_text(p_rtl_content);161add_child(label);162}163164void SnapshotSummaryView::_push_overview_blurb(const String &p_title, GameStateSnapshot *p_snapshot) {165String c = "";166167c += "[ul]\n";168c += vformat(" [i]%s[/i] %s\n", TTR("Name:"), p_snapshot->name);169if (p_snapshot->snapshot_context.has("timestamp")) {170c += vformat(" [i]%s[/i] %s\n", TTR("Timestamp:"), Time::get_singleton()->get_datetime_string_from_unix_time((double)p_snapshot->snapshot_context["timestamp"]));171}172if (p_snapshot->snapshot_context.has("game_version")) {173c += vformat(" [i]%s[/i] %s\n", TTR("Game Version:"), (String)p_snapshot->snapshot_context["game_version"]);174}175if (p_snapshot->snapshot_context.has("editor_version")) {176c += vformat(" [i]%s[/i] %s\n", TTR("Editor Version:"), (String)p_snapshot->snapshot_context["editor_version"]);177}178179double bytes_to_mb = 0.000001;180if (p_snapshot->snapshot_context.has("mem_usage")) {181c += vformat(" [i]%s[/i] %s\n", TTR("Memory Used:"), String::num((double)((uint64_t)p_snapshot->snapshot_context["mem_usage"]) * bytes_to_mb, 3) + " MB");182}183if (p_snapshot->snapshot_context.has("mem_max_usage")) {184c += vformat(" [i]%s[/i] %s\n", TTR("Max Memory Used:"), String::num((double)((uint64_t)p_snapshot->snapshot_context["mem_max_usage"]) * bytes_to_mb, 3) + " MB");185}186c += vformat(" [i]%s[/i] %s\n", TTR("Total Objects:"), itos(p_snapshot->objects.size()));187188int node_count = 0;189for (const KeyValue<ObjectID, SnapshotDataObject *> &pair : p_snapshot->objects) {190if (pair.value->is_node()) {191node_count++;192}193}194c += vformat(" [i]%s[/i] %s\n", TTR("Total Nodes:"), itos(node_count));195c += "[/ul]\n";196197blurb_list->add_child(memnew(SummaryBlurb(p_title, c)));198}199200void SnapshotSummaryView::_push_node_blurb(const String &p_title, GameStateSnapshot *p_snapshot) {201LocalVector<String> nodes;202nodes.reserve(p_snapshot->objects.size());203204for (const KeyValue<ObjectID, SnapshotDataObject *> &pair : p_snapshot->objects) {205// if it's a node AND it doesn't have a parent node206if (pair.value->is_node() && !pair.value->extra_debug_data.has("node_parent") && pair.value->extra_debug_data.has("node_is_scene_root") && !pair.value->extra_debug_data["node_is_scene_root"]) {207String node_name = pair.value->extra_debug_data["node_name"];208nodes.push_back(node_name != "" ? node_name : pair.value->get_name());209}210}211212if (nodes.size() <= 1) {213return;214}215216String c = TTRC("Multiple root nodes [i](possible call to 'remove_child' without 'queue_free')[/i]\n");217c += "[ul]\n";218for (const String &node : nodes) {219c += " " + node + "\n";220}221c += "[/ul]\n";222223blurb_list->add_child(memnew(SummaryBlurb(p_title, c)));224}225226void SnapshotSummaryView::_push_refcounted_blurb(const String &p_title, GameStateSnapshot *p_snapshot) {227LocalVector<String> rcs;228rcs.reserve(p_snapshot->objects.size());229230for (const KeyValue<ObjectID, SnapshotDataObject *> &pair : p_snapshot->objects) {231if (pair.value->is_refcounted()) {232int ref_count = (uint64_t)pair.value->extra_debug_data["ref_count"];233Array ref_cycles = (Array)pair.value->extra_debug_data["ref_cycles"];234235if (ref_count == ref_cycles.size()) {236rcs.push_back(pair.value->get_name());237}238}239}240241if (rcs.is_empty()) {242return;243}244245String c = TTRC("RefCounted objects only referenced in cycles [i](cycles often indicate a memory leaks)[/i]\n");246c += "[ul]\n";247for (const String &rc : rcs) {248c += " " + rc + "\n";249}250c += "[/ul]\n";251252blurb_list->add_child(memnew(SummaryBlurb(p_title, c)));253}254255void SnapshotSummaryView::_push_object_blurb(const String &p_title, GameStateSnapshot *p_snapshot) {256LocalVector<String> objects;257objects.reserve(p_snapshot->objects.size());258259for (const KeyValue<ObjectID, SnapshotDataObject *> &pair : p_snapshot->objects) {260if (pair.value->inbound_references.is_empty() && pair.value->outbound_references.is_empty()) {261if (!pair.value->get_script().is_null()) {262// This blurb will have a lot of false positives, but we can at least suppress false positives263// from unreferenced nodes that are part of the scene tree.264if (pair.value->is_node() && (bool)pair.value->extra_debug_data["node_is_scene_root"]) {265objects.push_back(pair.value->get_name());266}267}268}269}270271if (objects.is_empty()) {272return;273}274275String c = TTRC("Scripted objects not referenced by any other objects [i](unreferenced objects may indicate a memory leak)[/i]\n");276c += "[ul]\n";277for (const String &object : objects) {278c += " " + object + "\n";279}280c += "[/ul]\n";281282blurb_list->add_child(memnew(SummaryBlurb(p_title, c)));283}284285286