refcounted_view.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  1. /**************************************************************************/
  2. /* refcounted_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 "refcounted_view.h"
  31. #include "editor/editor_node.h"
  32. #include "editor/themes/editor_scale.h"
  33. #include "scene/gui/rich_text_label.h"
  34. #include "scene/gui/split_container.h"
  35. SnapshotRefCountedView::SnapshotRefCountedView() {
  36. set_name(TTRC("RefCounted"));
  37. }
  38. void SnapshotRefCountedView::show_snapshot(GameStateSnapshot *p_data, GameStateSnapshot *p_diff_data) {
  39. SnapshotView::show_snapshot(p_data, p_diff_data);
  40. item_data_map.clear();
  41. data_item_map.clear();
  42. set_v_size_flags(SizeFlags::SIZE_EXPAND_FILL);
  43. set_h_size_flags(SizeFlags::SIZE_EXPAND_FILL);
  44. refs_view = memnew(HSplitContainer);
  45. add_child(refs_view);
  46. refs_view->set_anchors_preset(LayoutPreset::PRESET_FULL_RECT);
  47. VBoxContainer *refs_column = memnew(VBoxContainer);
  48. refs_column->set_anchors_preset(LayoutPreset::PRESET_FULL_RECT);
  49. refs_view->add_child(refs_column);
  50. // Tree of Refs.
  51. refs_list = memnew(Tree);
  52. filter_bar = memnew(TreeSortAndFilterBar(refs_list, TTRC("Filter RefCounteds")));
  53. refs_column->add_child(filter_bar);
  54. int offset = diff_data ? 1 : 0;
  55. if (diff_data) {
  56. filter_bar->add_sort_option(TTRC("Snapshot"), TreeSortAndFilterBar::SortType::ALPHA_SORT, 0);
  57. }
  58. filter_bar->add_sort_option(TTRC("Class"), TreeSortAndFilterBar::SortType::ALPHA_SORT, offset + 0);
  59. filter_bar->add_sort_option(TTRC("Name"), TreeSortAndFilterBar::SortType::ALPHA_SORT, offset + 1);
  60. TreeSortAndFilterBar::SortOptionIndexes default_sort = filter_bar->add_sort_option(
  61. TTRC("Native Refs"),
  62. TreeSortAndFilterBar::SortType::NUMERIC_SORT,
  63. offset + 2);
  64. filter_bar->add_sort_option(TTRC("ObjectDB Refs"), TreeSortAndFilterBar::SortType::NUMERIC_SORT, offset + 3);
  65. filter_bar->add_sort_option(TTRC("Total Refs"), TreeSortAndFilterBar::SortType::NUMERIC_SORT, offset + 4);
  66. filter_bar->add_sort_option(TTRC("ObjectDB Cycles"), TreeSortAndFilterBar::SortType::NUMERIC_SORT, offset + 5);
  67. refs_list->set_select_mode(Tree::SelectMode::SELECT_ROW);
  68. refs_list->set_custom_minimum_size(Size2(200, 0) * EDSCALE);
  69. refs_list->set_hide_folding(false);
  70. refs_column->add_child(refs_list);
  71. refs_list->set_hide_root(true);
  72. refs_list->set_columns(diff_data ? 7 : 6);
  73. refs_list->set_column_titles_visible(true);
  74. refs_list->set_theme_type_variation("TreeSecondary");
  75. if (diff_data) {
  76. refs_list->set_column_title(0, TTRC("Snapshot"));
  77. refs_list->set_column_expand(0, false);
  78. refs_list->set_column_title_tooltip_text(0, "A: " + snapshot_data->name + ", B: " + diff_data->name);
  79. }
  80. refs_list->set_column_title(offset + 0, TTRC("Class"));
  81. refs_list->set_column_expand(offset + 0, true);
  82. refs_list->set_column_title_tooltip_text(offset + 0, TTRC("Object's class"));
  83. refs_list->set_column_title(offset + 1, TTRC("Name"));
  84. refs_list->set_column_expand(offset + 1, true);
  85. refs_list->set_column_expand_ratio(offset + 1, 2);
  86. refs_list->set_column_title_tooltip_text(offset + 1, TTRC("Object's name"));
  87. refs_list->set_column_title(offset + 2, TTRC("Native Refs"));
  88. refs_list->set_column_expand(offset + 2, false);
  89. refs_list->set_column_title_tooltip_text(offset + 2, TTRC("References not owned by the ObjectDB"));
  90. refs_list->set_column_title(offset + 3, TTRC("ObjectDB Refs"));
  91. refs_list->set_column_expand(offset + 3, false);
  92. refs_list->set_column_title_tooltip_text(offset + 3, TTRC("References owned by the ObjectDB"));
  93. refs_list->set_column_title(offset + 4, TTRC("Total Refs"));
  94. refs_list->set_column_expand(offset + 4, false);
  95. refs_list->set_column_title_tooltip_text(offset + 4, TTRC("ObjectDB References + Native References"));
  96. refs_list->set_column_title(offset + 5, TTRC("ObjectDB Cycles"));
  97. refs_list->set_column_expand(offset + 5, false);
  98. refs_list->set_column_title_tooltip_text(offset + 5, TTRC("Cycles detected in the ObjectDB"));
  99. refs_list->connect(SceneStringName(item_selected), callable_mp(this, &SnapshotRefCountedView::_refcounted_selected));
  100. refs_list->set_h_size_flags(SizeFlags::SIZE_EXPAND_FILL);
  101. refs_list->set_v_size_flags(SizeFlags::SIZE_EXPAND_FILL);
  102. // View of the selected refcounted.
  103. ref_details = memnew(VBoxContainer);
  104. ref_details->set_custom_minimum_size(Size2(200, 0) * EDSCALE);
  105. refs_view->add_child(ref_details);
  106. ref_details->set_h_size_flags(SizeFlags::SIZE_EXPAND_FILL);
  107. ref_details->set_v_size_flags(SizeFlags::SIZE_EXPAND_FILL);
  108. refs_list->create_item();
  109. _insert_data(snapshot_data, TTRC("A"));
  110. if (diff_data) {
  111. _insert_data(diff_data, TTRC("B"));
  112. }
  113. // Push the split as far right as possible.
  114. filter_bar->select_sort(default_sort.descending);
  115. filter_bar->apply();
  116. refs_list->set_selected(refs_list->get_root()->get_first_child());
  117. callable_mp(this, &SnapshotRefCountedView::_set_split_to_center).call_deferred();
  118. }
  119. void SnapshotRefCountedView::_set_split_to_center() {
  120. refs_view->set_split_offset(refs_view->get_size().x * 0.5);
  121. }
  122. void SnapshotRefCountedView::_insert_data(GameStateSnapshot *p_snapshot, const String &p_name) {
  123. for (const KeyValue<ObjectID, SnapshotDataObject *> &pair : p_snapshot->objects) {
  124. if (!pair.value->is_refcounted()) {
  125. continue;
  126. }
  127. TreeItem *item = refs_list->create_item(refs_list->get_root());
  128. item_data_map[item] = pair.value;
  129. data_item_map[pair.value] = item;
  130. int total_refs = pair.value->extra_debug_data.has("ref_count") ? (uint64_t)pair.value->extra_debug_data["ref_count"] : 0;
  131. int objectdb_refs = pair.value->get_unique_inbound_references().size();
  132. int native_refs = total_refs - objectdb_refs;
  133. Array ref_cycles = (Array)pair.value->extra_debug_data["ref_cycles"];
  134. int offset = 0;
  135. if (diff_data) {
  136. item->set_text(0, p_name);
  137. item->set_tooltip_text(0, p_snapshot->name);
  138. item->set_auto_translate_mode(0, AUTO_TRANSLATE_MODE_DISABLED);
  139. offset = 1;
  140. }
  141. item->set_text(offset + 0, pair.value->type_name);
  142. item->set_auto_translate_mode(offset + 0, AUTO_TRANSLATE_MODE_DISABLED);
  143. item->set_text(offset + 1, pair.value->get_name());
  144. item->set_auto_translate_mode(offset + 1, AUTO_TRANSLATE_MODE_DISABLED);
  145. item->set_text(offset + 2, String::num_uint64(native_refs));
  146. item->set_text(offset + 3, String::num_uint64(objectdb_refs));
  147. item->set_text(offset + 4, String::num_uint64(total_refs));
  148. item->set_text(offset + 5, String::num_uint64(ref_cycles.size())); // Compute cycles and attach it to refcounted object.
  149. if (total_refs == ref_cycles.size()) {
  150. // Often, references are held by the engine so we can't know if we're stuck in a cycle or not
  151. // But if the full cycle is visible in the ObjectDB,
  152. // tell the user by highlighting the cells in red.
  153. item->set_custom_bg_color(offset + 5, Color(1, 0, 0, 0.1));
  154. }
  155. }
  156. }
  157. void SnapshotRefCountedView::_refcounted_selected() {
  158. for (int i = 0; i < ref_details->get_child_count(); i++) {
  159. ref_details->get_child(i)->queue_free();
  160. }
  161. SnapshotDataObject *d = item_data_map[refs_list->get_selected()];
  162. EditorNode::get_singleton()->push_item(static_cast<Object *>(d));
  163. DarkPanelContainer *refcounted_panel = memnew(DarkPanelContainer);
  164. VBoxContainer *refcounted_panel_content = memnew(VBoxContainer);
  165. refcounted_panel_content->set_v_size_flags(SizeFlags::SIZE_EXPAND_FILL);
  166. refcounted_panel_content->set_h_size_flags(SizeFlags::SIZE_EXPAND_FILL);
  167. ref_details->add_child(refcounted_panel);
  168. refcounted_panel->add_child(refcounted_panel_content);
  169. refcounted_panel_content->add_child(memnew(SpanningHeader(d->get_name())));
  170. ScrollContainer *properties_scroll = memnew(ScrollContainer);
  171. properties_scroll->set_horizontal_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED);
  172. properties_scroll->set_vertical_scroll_mode(ScrollContainer::SCROLL_MODE_AUTO);
  173. properties_scroll->set_v_size_flags(SizeFlags::SIZE_EXPAND_FILL);
  174. properties_scroll->set_h_size_flags(SizeFlags::SIZE_EXPAND_FILL);
  175. refcounted_panel_content->add_child(properties_scroll);
  176. VBoxContainer *properties_container = memnew(VBoxContainer);
  177. properties_container->set_h_size_flags(SizeFlags::SIZE_EXPAND_FILL);
  178. properties_scroll->add_child(properties_container);
  179. properties_container->add_theme_constant_override("separation", 5);
  180. properties_container->add_theme_constant_override("margin_left", 2);
  181. properties_container->add_theme_constant_override("margin_right", 2);
  182. properties_container->add_theme_constant_override("margin_top", 2);
  183. properties_container->add_theme_constant_override("margin_bottom", 2);
  184. int total_refs = d->extra_debug_data.has("ref_count") ? (uint64_t)d->extra_debug_data["ref_count"] : 0;
  185. int objectdb_refs = d->get_unique_inbound_references().size();
  186. int native_refs = total_refs - objectdb_refs;
  187. Array ref_cycles = (Array)d->extra_debug_data["ref_cycles"];
  188. String count_str = "[ul]\n";
  189. count_str += vformat(TTR("Native References: %d"), native_refs) + "\n";
  190. count_str += vformat(TTR("ObjectDB References: %d"), objectdb_refs) + "\n";
  191. count_str += vformat(TTR("Total References: %d"), total_refs) + "\n";
  192. count_str += vformat(TTR("ObjectDB Cycles: %d"), ref_cycles.size()) + "\n";
  193. count_str += "[/ul]\n";
  194. RichTextLabel *counts = memnew(RichTextLabel(count_str));
  195. counts->set_use_bbcode(true);
  196. counts->set_fit_content(true);
  197. counts->add_theme_constant_override("line_separation", 6);
  198. properties_container->add_child(counts);
  199. if (d->inbound_references.size() > 0) {
  200. RichTextLabel *inbound_lbl = memnew(RichTextLabel(TTRC("[center]ObjectDB References[center]")));
  201. inbound_lbl->set_fit_content(true);
  202. inbound_lbl->set_use_bbcode(true);
  203. properties_container->add_child(inbound_lbl);
  204. Tree *inbound_tree = memnew(Tree);
  205. inbound_tree->set_hide_folding(true);
  206. properties_container->add_child(inbound_tree);
  207. inbound_tree->set_select_mode(Tree::SelectMode::SELECT_ROW);
  208. inbound_tree->set_hide_root(true);
  209. inbound_tree->set_columns(3);
  210. inbound_tree->set_column_titles_visible(true);
  211. inbound_tree->set_column_title(0, TTRC("Source"));
  212. inbound_tree->set_column_expand(0, true);
  213. inbound_tree->set_column_clip_content(0, false);
  214. inbound_tree->set_column_title_tooltip_text(0, TTRC("Other object referencing this object"));
  215. inbound_tree->set_column_title(1, TTRC("Property"));
  216. inbound_tree->set_column_expand(1, true);
  217. inbound_tree->set_column_clip_content(1, true);
  218. inbound_tree->set_column_title_tooltip_text(1, TTRC("Property of other object referencing this object"));
  219. inbound_tree->set_column_title(2, TTRC("Duplicate?"));
  220. inbound_tree->set_column_expand(2, false);
  221. inbound_tree->set_column_title_tooltip_text(2, TTRC("Was the same reference returned by multiple getters on the source object?"));
  222. inbound_tree->set_h_size_flags(SizeFlags::SIZE_EXPAND_FILL);
  223. inbound_tree->set_v_size_flags(SizeFlags::SIZE_EXPAND_FILL);
  224. inbound_tree->set_v_scroll_enabled(false);
  225. inbound_tree->connect(SceneStringName(item_selected), callable_mp(this, &SnapshotRefCountedView::_ref_selected).bind(inbound_tree));
  226. // The same reference can exist as multiple properties of an object (for example, gdscript `@export` properties exist twice).
  227. // We flag for the user if a property is exposed multiple times so it's clearer why there are more references in the list
  228. // than the ObjectDB References count would suggest.
  229. HashMap<ObjectID, int> property_repeat_count;
  230. for (const KeyValue<String, ObjectID> &ob : d->inbound_references) {
  231. if (!property_repeat_count.has(ob.value)) {
  232. property_repeat_count.insert(ob.value, 0);
  233. }
  234. property_repeat_count[ob.value]++;
  235. }
  236. TreeItem *root = inbound_tree->create_item();
  237. for (const KeyValue<String, ObjectID> &ob : d->inbound_references) {
  238. TreeItem *i = inbound_tree->create_item(root);
  239. SnapshotDataObject *target = d->snapshot->objects[ob.value];
  240. i->set_text(0, target->get_name());
  241. i->set_auto_translate_mode(0, AUTO_TRANSLATE_MODE_DISABLED);
  242. i->set_text(1, ob.key);
  243. i->set_auto_translate_mode(1, AUTO_TRANSLATE_MODE_DISABLED);
  244. i->set_text(2, property_repeat_count[ob.value] > 1 ? TTRC("Yes") : TTRC("No"));
  245. reference_item_map[i] = data_item_map[target];
  246. }
  247. }
  248. if (ref_cycles.size() > 0) {
  249. properties_container->add_child(memnew(SpanningHeader(TTRC("ObjectDB Cycles"))));
  250. Tree *cycles_tree = memnew(Tree);
  251. cycles_tree->set_hide_folding(true);
  252. properties_container->add_child(cycles_tree);
  253. cycles_tree->set_select_mode(Tree::SelectMode::SELECT_ROW);
  254. cycles_tree->set_hide_root(true);
  255. cycles_tree->set_columns(1);
  256. cycles_tree->set_column_titles_visible(false);
  257. cycles_tree->set_column_expand(0, true);
  258. cycles_tree->set_column_clip_content(0, false);
  259. cycles_tree->set_h_size_flags(SizeFlags::SIZE_EXPAND_FILL);
  260. cycles_tree->set_v_size_flags(SizeFlags::SIZE_EXPAND_FILL);
  261. cycles_tree->set_v_scroll_enabled(false);
  262. TreeItem *root = cycles_tree->create_item();
  263. for (const Variant &cycle : ref_cycles) {
  264. TreeItem *i = cycles_tree->create_item(root);
  265. i->set_text(0, cycle);
  266. i->set_text_overrun_behavior(0, TextServer::OverrunBehavior::OVERRUN_NO_TRIMMING);
  267. }
  268. }
  269. }
  270. void SnapshotRefCountedView::_ref_selected(Tree *p_source_tree) {
  271. TreeItem *target = reference_item_map[p_source_tree->get_selected()];
  272. if (target) {
  273. if (!target->is_visible()) {
  274. // Clear the filter if we can't see the node we just chose.
  275. filter_bar->clear_filter();
  276. }
  277. target->get_tree()->deselect_all();
  278. target->get_tree()->set_selected(target);
  279. target->get_tree()->ensure_cursor_is_visible();
  280. }
  281. }