summary_view.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. /**************************************************************************/
  2. /* summary_view.cpp */
  3. /**************************************************************************/
  4. /* This file is part of: */
  5. /* GODOT ENGINE */
  6. /* https://godotengine.org */
  7. /**************************************************************************/
  8. /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
  9. /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
  10. /* */
  11. /* Permission is hereby granted, free of charge, to any person obtaining */
  12. /* a copy of this software and associated documentation files (the */
  13. /* "Software"), to deal in the Software without restriction, including */
  14. /* without limitation the rights to use, copy, modify, merge, publish, */
  15. /* distribute, sublicense, and/or sell copies of the Software, and to */
  16. /* permit persons to whom the Software is furnished to do so, subject to */
  17. /* the following conditions: */
  18. /* */
  19. /* The above copyright notice and this permission notice shall be */
  20. /* included in all copies or substantial portions of the Software. */
  21. /* */
  22. /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
  23. /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
  24. /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
  25. /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
  26. /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
  27. /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
  28. /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
  29. /**************************************************************************/
  30. #include "summary_view.h"
  31. #include "core/os/time.h"
  32. #include "editor/editor_node.h"
  33. #include "editor/editor_string_names.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. SnapshotSummaryView::SnapshotSummaryView() {
  39. set_name(TTRC("Summary"));
  40. set_v_size_flags(SizeFlags::SIZE_EXPAND_FILL);
  41. set_h_size_flags(SizeFlags::SIZE_EXPAND_FILL);
  42. content_wrapper = memnew(PanelContainer);
  43. content_wrapper->set_anchors_preset(LayoutPreset::PRESET_FULL_RECT);
  44. add_child(content_wrapper);
  45. VBoxContainer *content = memnew(VBoxContainer);
  46. content_wrapper->add_child(content);
  47. content->set_anchors_preset(LayoutPreset::PRESET_FULL_RECT);
  48. title = memnew(Label(TTRC("ObjectDB Snapshot Summary")));
  49. content->add_child(title);
  50. title->set_horizontal_alignment(HorizontalAlignment::HORIZONTAL_ALIGNMENT_CENTER);
  51. title->set_vertical_alignment(VerticalAlignment::VERTICAL_ALIGNMENT_CENTER);
  52. title->set_anchors_preset(LayoutPreset::PRESET_TOP_WIDE);
  53. explainer_text = memnew(CenterContainer);
  54. explainer_text->set_v_size_flags(SizeFlags::SIZE_EXPAND_FILL);
  55. explainer_text->set_h_size_flags(SizeFlags::SIZE_EXPAND_FILL);
  56. content->add_child(explainer_text);
  57. VBoxContainer *explainer_lines = memnew(VBoxContainer);
  58. explainer_text->add_child(explainer_lines);
  59. Label *l1 = memnew(Label(TTRC("Press 'Take ObjectDB Snapshot' to snapshot the ObjectDB.")));
  60. Label *l2 = memnew(Label(TTRC("Memory in Godot is either owned natively by the engine or owned by the ObjectDB.")));
  61. Label *l3 = memnew(Label(TTRC("ObjectDB Snapshots capture only memory owned by the ObjectDB.")));
  62. l1->set_horizontal_alignment(HorizontalAlignment::HORIZONTAL_ALIGNMENT_CENTER);
  63. l2->set_horizontal_alignment(HorizontalAlignment::HORIZONTAL_ALIGNMENT_CENTER);
  64. l3->set_horizontal_alignment(HorizontalAlignment::HORIZONTAL_ALIGNMENT_CENTER);
  65. explainer_lines->add_child(l1);
  66. explainer_lines->add_child(l2);
  67. explainer_lines->add_child(l3);
  68. ScrollContainer *sc = memnew(ScrollContainer);
  69. sc->set_anchors_preset(LayoutPreset::PRESET_FULL_RECT);
  70. sc->set_v_size_flags(SizeFlags::SIZE_EXPAND_FILL);
  71. sc->set_h_size_flags(SizeFlags::SIZE_EXPAND_FILL);
  72. content->add_child(sc);
  73. blurb_list = memnew(VBoxContainer);
  74. sc->add_child(blurb_list);
  75. blurb_list->set_v_size_flags(SizeFlags::SIZE_EXPAND_FILL);
  76. blurb_list->set_h_size_flags(SizeFlags::SIZE_EXPAND_FILL);
  77. }
  78. void SnapshotSummaryView::_notification(int p_what) {
  79. if (p_what == NOTIFICATION_THEME_CHANGED) {
  80. content_wrapper->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("ObjectDBContentWrapper"), EditorStringName(EditorStyles)));
  81. title->add_theme_style_override(SNAME("normal"), get_theme_stylebox(SNAME("ObjectDBTitle"), EditorStringName(EditorStyles)));
  82. }
  83. }
  84. void SnapshotSummaryView::show_snapshot(GameStateSnapshot *p_data, GameStateSnapshot *p_diff_data) {
  85. SnapshotView::show_snapshot(p_data, p_diff_data);
  86. explainer_text->set_visible(false);
  87. String snapshot_a_name = diff_data == nullptr ? TTR("Snapshot") : TTR("Snapshot A");
  88. String snapshot_b_name = TTR("Snapshot B");
  89. _push_overview_blurb(snapshot_a_name + " " + TTR("Overview"), snapshot_data);
  90. if (diff_data) {
  91. _push_overview_blurb(snapshot_b_name + " " + TTR("Overview"), diff_data);
  92. }
  93. _push_node_blurb(snapshot_a_name + " " + TTR("Nodes"), snapshot_data);
  94. if (diff_data) {
  95. _push_node_blurb(snapshot_b_name + " " + TTR("Nodes"), diff_data);
  96. }
  97. _push_refcounted_blurb(snapshot_a_name + " " + TTR("RefCounteds"), snapshot_data);
  98. if (diff_data) {
  99. _push_refcounted_blurb(snapshot_b_name + " " + TTR("RefCounteds"), diff_data);
  100. }
  101. _push_object_blurb(snapshot_a_name + " " + TTR("Objects"), snapshot_data);
  102. if (diff_data) {
  103. _push_object_blurb(snapshot_b_name + " " + TTR("Objects"), diff_data);
  104. }
  105. }
  106. void SnapshotSummaryView::clear_snapshot() {
  107. // Just clear out the blurbs and leave the explainer.
  108. for (int i = 0; i < blurb_list->get_child_count(); i++) {
  109. blurb_list->get_child(i)->queue_free();
  110. }
  111. snapshot_data = nullptr;
  112. diff_data = nullptr;
  113. explainer_text->set_visible(true);
  114. }
  115. SummaryBlurb::SummaryBlurb(const String &p_title, const String &p_rtl_content) {
  116. add_theme_constant_override("margin_left", 2);
  117. add_theme_constant_override("margin_right", 2);
  118. add_theme_constant_override("margin_top", 2);
  119. add_theme_constant_override("margin_bottom", 2);
  120. label = memnew(RichTextLabel);
  121. label->add_theme_constant_override(SceneStringName(line_separation), 6);
  122. label->set_text_direction(Control::TEXT_DIRECTION_INHERITED);
  123. label->set_fit_content(true);
  124. label->set_use_bbcode(true);
  125. label->add_newline();
  126. label->push_bold();
  127. label->add_text(p_title);
  128. label->pop();
  129. label->add_newline();
  130. label->add_newline();
  131. label->append_text(p_rtl_content);
  132. add_child(label);
  133. }
  134. void SnapshotSummaryView::_push_overview_blurb(const String &p_title, GameStateSnapshot *p_snapshot) {
  135. String c = "";
  136. c += "[ul]\n";
  137. c += vformat(" [i]%s[/i] %s\n", TTR("Name:"), p_snapshot->name);
  138. if (p_snapshot->snapshot_context.has("timestamp")) {
  139. c += vformat(" [i]%s[/i] %s\n", TTR("Timestamp:"), Time::get_singleton()->get_datetime_string_from_unix_time((double)p_snapshot->snapshot_context["timestamp"]));
  140. }
  141. if (p_snapshot->snapshot_context.has("game_version")) {
  142. c += vformat(" [i]%s[/i] %s\n", TTR("Game Version:"), (String)p_snapshot->snapshot_context["game_version"]);
  143. }
  144. if (p_snapshot->snapshot_context.has("editor_version")) {
  145. c += vformat(" [i]%s[/i] %s\n", TTR("Editor Version:"), (String)p_snapshot->snapshot_context["editor_version"]);
  146. }
  147. double bytes_to_mb = 0.000001;
  148. if (p_snapshot->snapshot_context.has("mem_usage")) {
  149. c += 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");
  150. }
  151. if (p_snapshot->snapshot_context.has("mem_max_usage")) {
  152. c += 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");
  153. }
  154. c += vformat(" [i]%s[/i] %s\n", TTR("Total Objects:"), itos(p_snapshot->objects.size()));
  155. int node_count = 0;
  156. for (const KeyValue<ObjectID, SnapshotDataObject *> &pair : p_snapshot->objects) {
  157. if (pair.value->is_node()) {
  158. node_count++;
  159. }
  160. }
  161. c += vformat(" [i]%s[/i] %s\n", TTR("Total Nodes:"), itos(node_count));
  162. c += "[/ul]\n";
  163. blurb_list->add_child(memnew(SummaryBlurb(p_title, c)));
  164. }
  165. void SnapshotSummaryView::_push_node_blurb(const String &p_title, GameStateSnapshot *p_snapshot) {
  166. LocalVector<String> nodes;
  167. nodes.reserve(p_snapshot->objects.size());
  168. for (const KeyValue<ObjectID, SnapshotDataObject *> &pair : p_snapshot->objects) {
  169. // if it's a node AND it doesn't have a parent node
  170. if (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"]) {
  171. String node_name = pair.value->extra_debug_data["node_name"];
  172. nodes.push_back(node_name != "" ? node_name : pair.value->get_name());
  173. }
  174. }
  175. if (nodes.size() <= 1) {
  176. return;
  177. }
  178. String c = TTR("Multiple root nodes [i](possible call to 'remove_child' without 'queue_free')[/i]") + "\n";
  179. c += "[ul]\n";
  180. for (const String &node : nodes) {
  181. c += " " + node + "\n";
  182. }
  183. c += "[/ul]\n";
  184. blurb_list->add_child(memnew(SummaryBlurb(p_title, c)));
  185. }
  186. void SnapshotSummaryView::_push_refcounted_blurb(const String &p_title, GameStateSnapshot *p_snapshot) {
  187. LocalVector<String> rcs;
  188. rcs.reserve(p_snapshot->objects.size());
  189. for (const KeyValue<ObjectID, SnapshotDataObject *> &pair : p_snapshot->objects) {
  190. if (pair.value->is_refcounted()) {
  191. int ref_count = (uint64_t)pair.value->extra_debug_data["ref_count"];
  192. Array ref_cycles = (Array)pair.value->extra_debug_data["ref_cycles"];
  193. if (ref_count == ref_cycles.size()) {
  194. rcs.push_back(pair.value->get_name());
  195. }
  196. }
  197. }
  198. if (rcs.is_empty()) {
  199. return;
  200. }
  201. String c = TTR("RefCounted objects only referenced in cycles [i](cycles often indicate a memory leaks)[/i]") + "\n";
  202. c += "[ul]\n";
  203. for (const String &rc : rcs) {
  204. c += " " + rc + "\n";
  205. }
  206. c += "[/ul]\n";
  207. blurb_list->add_child(memnew(SummaryBlurb(p_title, c)));
  208. }
  209. void SnapshotSummaryView::_push_object_blurb(const String &p_title, GameStateSnapshot *p_snapshot) {
  210. LocalVector<String> objects;
  211. objects.reserve(p_snapshot->objects.size());
  212. for (const KeyValue<ObjectID, SnapshotDataObject *> &pair : p_snapshot->objects) {
  213. if (pair.value->inbound_references.is_empty() && pair.value->outbound_references.is_empty()) {
  214. if (!pair.value->get_script().is_null()) {
  215. // This blurb will have a lot of false positives, but we can at least suppress false positives
  216. // from unreferenced nodes that are part of the scene tree.
  217. if (pair.value->is_node() && (bool)pair.value->extra_debug_data["node_is_scene_root"]) {
  218. objects.push_back(pair.value->get_name());
  219. }
  220. }
  221. }
  222. }
  223. if (objects.is_empty()) {
  224. return;
  225. }
  226. String c = TTR("Scripted objects not referenced by any other objects [i](unreferenced objects may indicate a memory leak)[/i]") + "\n";
  227. c += "[ul]\n";
  228. for (const String &object : objects) {
  229. c += " " + object + "\n";
  230. }
  231. c += "[/ul]\n";
  232. blurb_list->add_child(memnew(SummaryBlurb(p_title, c)));
  233. }