objectdb_profiler_panel.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479
  1. /**************************************************************************/
  2. /* objectdb_profiler_panel.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 "objectdb_profiler_panel.h"
  31. #include "../snapshot_collector.h"
  32. #include "data_viewers/class_view.h"
  33. #include "data_viewers/node_view.h"
  34. #include "data_viewers/object_view.h"
  35. #include "data_viewers/refcounted_view.h"
  36. #include "data_viewers/summary_view.h"
  37. #include "core/config/project_settings.h"
  38. #include "core/os/time.h"
  39. #include "editor/debugger/editor_debugger_node.h"
  40. #include "editor/debugger/script_editor_debugger.h"
  41. #include "editor/docks/inspector_dock.h"
  42. #include "editor/editor_node.h"
  43. #include "editor/inspector/editor_inspector.h"
  44. #include "editor/themes/editor_scale.h"
  45. #include "scene/gui/button.h"
  46. #include "scene/gui/label.h"
  47. #include "scene/gui/option_button.h"
  48. #include "scene/gui/split_container.h"
  49. #include "scene/gui/tab_container.h"
  50. // ObjectDB snapshots are very large. In remote_debugger_peer.cpp, the max in_buf and out_buf size is 8mb.
  51. // Snapshots are typically larger than that, so we send them 6mb at a time. Leaving 2mb for other data.
  52. const int SNAPSHOT_CHUNK_SIZE = 6 << 20;
  53. void ObjectDBProfilerPanel::_request_object_snapshot() {
  54. take_snapshot->set_disabled(true);
  55. take_snapshot->set_text(TTRC("Generating Snapshot"));
  56. // Pause the game while the snapshot is taken so the state of the game isn't modified as we capture the snapshot.
  57. if (EditorDebuggerNode::get_singleton()->get_current_debugger()->is_breaked()) {
  58. requested_break_for_snapshot = false;
  59. _begin_object_snapshot();
  60. } else {
  61. awaiting_debug_break = true;
  62. requested_break_for_snapshot = true; // We only need to resume the game if we are the ones who paused it.
  63. EditorDebuggerNode::get_singleton()->debug_break();
  64. }
  65. }
  66. void ObjectDBProfilerPanel::_on_debug_breaked(bool p_reallydid, bool p_can_debug, const String &p_reason, bool p_has_stackdump) {
  67. if (p_reallydid && awaiting_debug_break) {
  68. awaiting_debug_break = false;
  69. _begin_object_snapshot();
  70. }
  71. }
  72. void ObjectDBProfilerPanel::_begin_object_snapshot() {
  73. Array args = { next_request_id++, SnapshotCollector::get_godot_version_string() };
  74. EditorDebuggerNode::get_singleton()->get_current_debugger()->send_message("snapshot:request_prepare_snapshot", args);
  75. }
  76. bool ObjectDBProfilerPanel::handle_debug_message(const String &p_message, const Array &p_data, int p_index) {
  77. if (p_message == "snapshot:snapshot_prepared") {
  78. int request_id = p_data[0];
  79. int total_size = p_data[1];
  80. partial_snapshots[request_id] = PartialSnapshot();
  81. partial_snapshots[request_id].total_size = total_size;
  82. Array args = { request_id, 0, SNAPSHOT_CHUNK_SIZE };
  83. take_snapshot->set_text(vformat(TTR("Receiving Snapshot (0/%s MiB)"), _to_mb(total_size)));
  84. EditorDebuggerNode::get_singleton()->get_current_debugger()->send_message("snapshot:request_snapshot_chunk", args);
  85. return true;
  86. }
  87. if (p_message == "snapshot:snapshot_chunk") {
  88. int request_id = p_data[0];
  89. PartialSnapshot &chunk = partial_snapshots[request_id];
  90. chunk.data.append_array(p_data[1]);
  91. take_snapshot->set_text(vformat(TTR("Receiving Snapshot (%s/%s MiB)"), _to_mb(chunk.data.size()), _to_mb(chunk.total_size)));
  92. if (chunk.data.size() != chunk.total_size) {
  93. Array args = { request_id, chunk.data.size(), chunk.data.size() + SNAPSHOT_CHUNK_SIZE };
  94. EditorDebuggerNode::get_singleton()->get_current_debugger()->send_message("snapshot:request_snapshot_chunk", args);
  95. return true;
  96. }
  97. take_snapshot->set_text(TTRC("Visualizing Snapshot"));
  98. // Wait a frame just so the button has a chance to update its text so the user knows what's going on.
  99. get_tree()->connect("process_frame", callable_mp(this, &ObjectDBProfilerPanel::receive_snapshot).bind(request_id), CONNECT_ONE_SHOT);
  100. return true;
  101. }
  102. return false;
  103. }
  104. void ObjectDBProfilerPanel::receive_snapshot(int request_id) {
  105. const Vector<uint8_t> &in_data = partial_snapshots[request_id].data;
  106. Ref<DirAccess> snapshot_dir = _get_and_create_snapshot_storage_dir();
  107. if (snapshot_dir.is_valid()) {
  108. Error err;
  109. String base_snapshot_file_name = Time::get_singleton()->get_datetime_string_from_system(false).replace_char('T', '_').replace_char(':', '-');
  110. String snapshot_file_name = base_snapshot_file_name;
  111. String current_dir = snapshot_dir->get_current_dir();
  112. String joined_dir = current_dir.path_join(snapshot_file_name) + ".odb_snapshot";
  113. for (int i = 2; FileAccess::exists(joined_dir); i++) {
  114. snapshot_file_name = base_snapshot_file_name + '_' + String::chr('0' + i);
  115. joined_dir = current_dir.path_join(snapshot_file_name) + ".odb_snapshot";
  116. }
  117. Ref<FileAccess> file = FileAccess::open(joined_dir, FileAccess::WRITE, &err);
  118. if (err == OK) {
  119. file->store_buffer(in_data);
  120. file->close(); // RAII could do this typically, but we want to read the file in _show_selected_snapshot, so we have to finalize the write before that.
  121. _add_snapshot_button(snapshot_file_name, joined_dir);
  122. snapshot_list->deselect_all();
  123. snapshot_list->set_selected(snapshot_list->get_root()->get_first_child());
  124. snapshot_list->ensure_cursor_is_visible();
  125. _show_selected_snapshot();
  126. } else {
  127. ERR_PRINT("Could not persist ObjectDB Snapshot: " + String(error_names[err]));
  128. }
  129. }
  130. partial_snapshots.erase(request_id);
  131. if (requested_break_for_snapshot) {
  132. EditorDebuggerNode::get_singleton()->debug_continue();
  133. }
  134. take_snapshot->set_disabled(false);
  135. take_snapshot->set_text("Take ObjectDB Snapshot");
  136. }
  137. Ref<DirAccess> ObjectDBProfilerPanel::_get_and_create_snapshot_storage_dir() {
  138. String profiles_dir = "user://";
  139. Ref<DirAccess> da = DirAccess::open(profiles_dir);
  140. ERR_FAIL_COND_V_MSG(da.is_null(), nullptr, vformat("Could not open 'user://' directory: '%s'.", profiles_dir));
  141. Error err = da->change_dir("objectdb_snapshots");
  142. if (err != OK) {
  143. Error err_mk = da->make_dir("objectdb_snapshots");
  144. Error err_ch = da->change_dir("objectdb_snapshots");
  145. ERR_FAIL_COND_V_MSG(err_mk != OK || err_ch != OK, nullptr, "Could not create ObjectDB Snapshots directory: " + da->get_current_dir());
  146. }
  147. return da;
  148. }
  149. TreeItem *ObjectDBProfilerPanel::_add_snapshot_button(const String &p_snapshot_file_name, const String &p_full_file_path) {
  150. TreeItem *item = snapshot_list->create_item(snapshot_list->get_root());
  151. item->set_text(0, p_snapshot_file_name);
  152. item->set_metadata(0, p_full_file_path);
  153. item->set_auto_translate_mode(0, AUTO_TRANSLATE_MODE_DISABLED);
  154. item->move_before(snapshot_list->get_root()->get_first_child());
  155. _update_diff_items();
  156. _update_enabled_diff_items();
  157. return item;
  158. }
  159. void ObjectDBProfilerPanel::_show_selected_snapshot() {
  160. if (snapshot_list->get_selected()->get_text(0) == (String)diff_button->get_selected_metadata()) {
  161. for (int i = 0; i < diff_button->get_item_count(); i++) {
  162. if (diff_button->get_item_text(i) == current_snapshot->name) {
  163. diff_button->select(i);
  164. break;
  165. }
  166. }
  167. }
  168. show_snapshot(snapshot_list->get_selected()->get_text(0), diff_button->get_selected_metadata());
  169. _update_enabled_diff_items();
  170. }
  171. void ObjectDBProfilerPanel::_on_snapshot_deselected() {
  172. snapshot_list->deselect_all();
  173. diff_button->select(0);
  174. clear_snapshot();
  175. _update_enabled_diff_items();
  176. }
  177. Ref<GameStateSnapshot> ObjectDBProfilerPanel::get_snapshot(const String &p_snapshot_file_name) {
  178. if (snapshot_cache.has(p_snapshot_file_name)) {
  179. return snapshot_cache.get(p_snapshot_file_name);
  180. }
  181. Ref<DirAccess> snapshot_dir = _get_and_create_snapshot_storage_dir();
  182. ERR_FAIL_COND_V_MSG(snapshot_dir.is_null(), nullptr, "Could not access ObjectDB Snapshot directory");
  183. String full_file_path = snapshot_dir->get_current_dir().path_join(p_snapshot_file_name) + ".odb_snapshot";
  184. Error err;
  185. Ref<FileAccess> snapshot_file = FileAccess::open(full_file_path, FileAccess::READ, &err);
  186. ERR_FAIL_COND_V_MSG(err != OK, nullptr, "Could not open ObjectDB Snapshot file: " + full_file_path);
  187. Vector<uint8_t> content = snapshot_file->get_buffer(snapshot_file->get_length()); // We want to split on newlines, so normalize them.
  188. ERR_FAIL_COND_V_MSG(content.is_empty(), nullptr, "ObjectDB Snapshot file is empty: " + full_file_path);
  189. Ref<GameStateSnapshot> snapshot = GameStateSnapshot::create_ref(p_snapshot_file_name, content);
  190. if (snapshot.is_valid()) {
  191. snapshot_cache.insert(p_snapshot_file_name, snapshot);
  192. }
  193. return snapshot;
  194. }
  195. void ObjectDBProfilerPanel::show_snapshot(const String &p_snapshot_file_name, const String &p_snapshot_diff_file_name) {
  196. clear_snapshot(false);
  197. current_snapshot = get_snapshot(p_snapshot_file_name);
  198. if (!p_snapshot_diff_file_name.is_empty()) {
  199. diff_snapshot = get_snapshot(p_snapshot_diff_file_name);
  200. }
  201. _update_view_tabs();
  202. _view_tab_changed(view_tabs->get_current_tab());
  203. }
  204. void ObjectDBProfilerPanel::_view_tab_changed(int p_tab_idx) {
  205. // Populating tabs only on tab changed because we're handling a lot of data,
  206. // and the editor freezes for a while if we try to populate every tab at once.
  207. SnapshotView *view = cast_to<SnapshotView>(view_tabs->get_current_tab_control());
  208. GameStateSnapshot *snapshot = current_snapshot.ptr();
  209. GameStateSnapshot *diff = diff_snapshot.ptr();
  210. if (snapshot != nullptr && !view->is_showing_snapshot(snapshot, diff)) {
  211. view->show_snapshot(snapshot, diff);
  212. }
  213. }
  214. void ObjectDBProfilerPanel::clear_snapshot(bool p_update_view_tabs) {
  215. for (SnapshotView *view : views) {
  216. view->clear_snapshot();
  217. }
  218. const Object *edited_object = InspectorDock::get_inspector_singleton()->get_edited_object();
  219. if (Object::cast_to<SnapshotDataObject>(edited_object)) {
  220. EditorNode::get_singleton()->push_item(nullptr);
  221. }
  222. current_snapshot.unref();
  223. diff_snapshot.unref();
  224. if (p_update_view_tabs) {
  225. _update_view_tabs();
  226. }
  227. }
  228. void ObjectDBProfilerPanel::set_enabled(bool p_enabled) {
  229. take_snapshot->set_text(TTRC("Take ObjectDB Snapshot"));
  230. take_snapshot->set_disabled(!p_enabled);
  231. }
  232. void ObjectDBProfilerPanel::_snapshot_rmb(const Vector2 &p_pos, MouseButton p_button) {
  233. if (p_button != MouseButton::RIGHT) {
  234. return;
  235. }
  236. rmb_menu->clear(false);
  237. rmb_menu->add_icon_item(get_editor_theme_icon(SNAME("Rename")), TTRC("Rename"), OdbProfilerMenuOptions::ODB_MENU_RENAME);
  238. rmb_menu->add_icon_item(get_editor_theme_icon(SNAME("Folder")), TTRC("Show in File Manager"), OdbProfilerMenuOptions::ODB_MENU_SHOW_IN_FOLDER);
  239. rmb_menu->add_icon_item(get_editor_theme_icon(SNAME("Remove")), TTRC("Delete"), OdbProfilerMenuOptions::ODB_MENU_DELETE);
  240. rmb_menu->set_position(snapshot_list->get_screen_position() + p_pos);
  241. rmb_menu->reset_size();
  242. rmb_menu->popup();
  243. }
  244. void ObjectDBProfilerPanel::_rmb_menu_pressed(int p_tool, bool p_confirm_override) {
  245. String file_path = snapshot_list->get_selected()->get_metadata(0);
  246. String global_path = ProjectSettings::get_singleton()->globalize_path(file_path);
  247. switch (rmb_menu->get_item_id(p_tool)) {
  248. case OdbProfilerMenuOptions::ODB_MENU_SHOW_IN_FOLDER: {
  249. OS::get_singleton()->shell_show_in_file_manager(global_path, true);
  250. break;
  251. }
  252. case OdbProfilerMenuOptions::ODB_MENU_DELETE: {
  253. DirAccess::remove_file_or_error(global_path);
  254. snapshot_list->get_root()->remove_child(snapshot_list->get_selected());
  255. if (snapshot_list->get_root()->get_child_count() > 0) {
  256. snapshot_list->set_selected(snapshot_list->get_root()->get_first_child());
  257. } else {
  258. // If we deleted the last snapshot, jump back to the summary tab and clear everything out.
  259. clear_snapshot();
  260. }
  261. _update_diff_items();
  262. break;
  263. }
  264. case OdbProfilerMenuOptions::ODB_MENU_RENAME: {
  265. snapshot_list->edit_selected(true);
  266. break;
  267. }
  268. }
  269. }
  270. void ObjectDBProfilerPanel::_edit_snapshot_name() {
  271. String new_snapshot_name = snapshot_list->get_selected()->get_text(0);
  272. String full_file_with_path = snapshot_list->get_selected()->get_metadata(0);
  273. Vector<String> full_path_parts = full_file_with_path.rsplit("/", false, 1);
  274. String full_file_path = full_path_parts[0];
  275. String file_name = full_path_parts[1];
  276. String old_snapshot_name = file_name.split(".")[0];
  277. String new_full_file_path = full_file_path.path_join(new_snapshot_name) + ".odb_snapshot";
  278. bool name_taken = false;
  279. for (int i = 0; i < snapshot_list->get_root()->get_child_count(); i++) {
  280. TreeItem *item = snapshot_list->get_root()->get_child(i);
  281. if (item != snapshot_list->get_selected()) {
  282. if (item->get_text(0) == new_snapshot_name) {
  283. name_taken = true;
  284. break;
  285. }
  286. }
  287. }
  288. if (name_taken || new_snapshot_name.contains_char(':') || new_snapshot_name.contains_char('\\') || new_snapshot_name.contains_char('/') || new_snapshot_name.begins_with(".") || new_snapshot_name.is_empty()) {
  289. EditorNode::get_singleton()->show_warning(TTRC("Invalid snapshot name."));
  290. snapshot_list->get_selected()->set_text(0, old_snapshot_name);
  291. return;
  292. }
  293. Error err = DirAccess::rename_absolute(full_file_with_path, new_full_file_path);
  294. if (err != OK) {
  295. EditorNode::get_singleton()->show_warning(TTRC("Snapshot rename failed"));
  296. snapshot_list->get_selected()->set_text(0, old_snapshot_name);
  297. } else {
  298. snapshot_list->get_selected()->set_metadata(0, new_full_file_path);
  299. }
  300. _update_diff_items();
  301. _show_selected_snapshot();
  302. }
  303. ObjectDBProfilerPanel::ObjectDBProfilerPanel() {
  304. set_name(TTRC("ObjectDB Profiler"));
  305. snapshot_cache = LRUCache<String, Ref<GameStateSnapshot>>(SNAPSHOT_CACHE_MAX_SIZE);
  306. EditorDebuggerNode::get_singleton()->get_current_debugger()->connect("breaked", callable_mp(this, &ObjectDBProfilerPanel::_on_debug_breaked));
  307. HSplitContainer *root_container = memnew(HSplitContainer);
  308. root_container->set_anchors_preset(Control::LayoutPreset::PRESET_FULL_RECT);
  309. root_container->set_v_size_flags(Control::SizeFlags::SIZE_EXPAND_FILL);
  310. root_container->set_h_size_flags(Control::SizeFlags::SIZE_EXPAND_FILL);
  311. root_container->set_split_offset(300 * EDSCALE);
  312. add_child(root_container);
  313. VBoxContainer *snapshot_column = memnew(VBoxContainer);
  314. root_container->add_child(snapshot_column);
  315. take_snapshot = memnew(Button(TTRC("Take ObjectDB Snapshot")));
  316. snapshot_column->add_child(take_snapshot);
  317. take_snapshot->connect(SceneStringName(pressed), callable_mp(this, &ObjectDBProfilerPanel::_request_object_snapshot));
  318. snapshot_list = memnew(Tree);
  319. snapshot_list->create_item();
  320. snapshot_list->set_hide_folding(true);
  321. snapshot_column->add_child(snapshot_list);
  322. snapshot_list->set_select_mode(Tree::SelectMode::SELECT_ROW);
  323. snapshot_list->set_hide_root(true);
  324. snapshot_list->set_columns(1);
  325. snapshot_list->set_column_titles_visible(true);
  326. snapshot_list->set_column_title(0, "Snapshots");
  327. snapshot_list->set_column_expand(0, true);
  328. snapshot_list->set_column_clip_content(0, true);
  329. snapshot_list->connect(SceneStringName(item_selected), callable_mp(this, &ObjectDBProfilerPanel::_show_selected_snapshot));
  330. snapshot_list->connect("nothing_selected", callable_mp(this, &ObjectDBProfilerPanel::_on_snapshot_deselected));
  331. snapshot_list->connect("item_edited", callable_mp(this, &ObjectDBProfilerPanel::_edit_snapshot_name));
  332. snapshot_list->set_h_size_flags(SizeFlags::SIZE_EXPAND_FILL);
  333. snapshot_list->set_v_size_flags(SizeFlags::SIZE_EXPAND_FILL);
  334. snapshot_list->set_anchors_preset(LayoutPreset::PRESET_FULL_RECT);
  335. snapshot_list->set_theme_type_variation("TreeSecondary");
  336. snapshot_list->set_allow_rmb_select(true);
  337. snapshot_list->connect("item_mouse_selected", callable_mp(this, &ObjectDBProfilerPanel::_snapshot_rmb));
  338. rmb_menu = memnew(PopupMenu);
  339. add_child(rmb_menu);
  340. rmb_menu->connect(SceneStringName(id_pressed), callable_mp(this, &ObjectDBProfilerPanel::_rmb_menu_pressed).bind(false));
  341. HBoxContainer *diff_button_and_label = memnew(HBoxContainer);
  342. diff_button_and_label->set_h_size_flags(SizeFlags::SIZE_EXPAND_FILL);
  343. snapshot_column->add_child(diff_button_and_label);
  344. Label *diff_against = memnew(Label(TTRC("Diff Against:")));
  345. diff_button_and_label->add_child(diff_against);
  346. diff_button = memnew(OptionButton);
  347. diff_button->set_h_size_flags(SizeFlags::SIZE_EXPAND_FILL);
  348. diff_button->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
  349. diff_button->connect(SceneStringName(item_selected), callable_mp(this, &ObjectDBProfilerPanel::_show_selected_snapshot).unbind(1));
  350. diff_button_and_label->add_child(diff_button);
  351. // Tabs of various views right for each snapshot.
  352. view_tabs = memnew(TabContainer);
  353. view_tabs->set_theme_type_variation("TabContainerInner");
  354. root_container->add_child(view_tabs);
  355. view_tabs->set_custom_minimum_size(Size2(300 * EDSCALE, 0));
  356. view_tabs->set_v_size_flags(SizeFlags::SIZE_EXPAND_FILL);
  357. view_tabs->connect("tab_changed", callable_mp(this, &ObjectDBProfilerPanel::_view_tab_changed));
  358. add_view(memnew(SnapshotSummaryView));
  359. add_view(memnew(SnapshotClassView));
  360. add_view(memnew(SnapshotObjectView));
  361. add_view(memnew(SnapshotNodeView));
  362. add_view(memnew(SnapshotRefCountedView));
  363. set_enabled(false);
  364. // Load all the snapshot names from disk.
  365. Ref<DirAccess> snapshot_dir = _get_and_create_snapshot_storage_dir();
  366. if (snapshot_dir.is_valid()) {
  367. for (const String &file_name : snapshot_dir->get_files()) {
  368. Vector<String> name_parts = file_name.split(".");
  369. ERR_CONTINUE_MSG(name_parts.size() != 2 || name_parts[1] != "odb_snapshot", "ObjectDB snapshot file did not have .odb_snapshot extension. Skipping: " + file_name);
  370. _add_snapshot_button(name_parts[0], snapshot_dir->get_current_dir().path_join(file_name));
  371. }
  372. }
  373. }
  374. void ObjectDBProfilerPanel::add_view(SnapshotView *p_to_add) {
  375. views.push_back(p_to_add);
  376. view_tabs->add_child(p_to_add);
  377. _update_view_tabs();
  378. }
  379. void ObjectDBProfilerPanel::_update_view_tabs() {
  380. bool has_snapshot = current_snapshot.is_valid();
  381. for (int i = 1; i < view_tabs->get_tab_count(); i++) {
  382. view_tabs->set_tab_disabled(i, !has_snapshot);
  383. }
  384. if (!has_snapshot) {
  385. view_tabs->set_current_tab(0);
  386. }
  387. }
  388. void ObjectDBProfilerPanel::_update_diff_items() {
  389. diff_button->clear();
  390. diff_button->add_item(TTRC("None"), 0);
  391. diff_button->set_item_metadata(0, String());
  392. diff_button->set_item_auto_translate_mode(0, Node::AUTO_TRANSLATE_MODE_ALWAYS);
  393. for (int i = 0; i < snapshot_list->get_root()->get_child_count(); i++) {
  394. String name = snapshot_list->get_root()->get_child(i)->get_text(0);
  395. diff_button->add_item(name);
  396. diff_button->set_item_metadata(i + 1, name);
  397. }
  398. }
  399. void ObjectDBProfilerPanel::_update_enabled_diff_items() {
  400. TreeItem *selected_snapshot = snapshot_list->get_selected();
  401. if (selected_snapshot == nullptr) {
  402. diff_button->set_disabled(true);
  403. return;
  404. }
  405. diff_button->set_disabled(false);
  406. String snapshot_name = selected_snapshot->get_text(0);
  407. for (int i = 0; i < diff_button->get_item_count(); i++) {
  408. diff_button->set_item_disabled(i, diff_button->get_item_text(i) == snapshot_name);
  409. }
  410. }
  411. String ObjectDBProfilerPanel::_to_mb(int p_x) {
  412. return String::num((double)p_x / (double)(1 << 20), 2);
  413. }