editor_network_profiler.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419
  1. /**************************************************************************/
  2. /* editor_network_profiler.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 "editor_network_profiler.h"
  31. #include "core/os/os.h"
  32. #include "editor/editor_settings.h"
  33. #include "editor/editor_string_names.h"
  34. #include "editor/themes/editor_scale.h"
  35. #include "scene/gui/check_box.h"
  36. void EditorNetworkProfiler::_bind_methods() {
  37. ADD_SIGNAL(MethodInfo("enable_profiling", PropertyInfo(Variant::BOOL, "enable")));
  38. ADD_SIGNAL(MethodInfo("open_request", PropertyInfo(Variant::STRING, "path")));
  39. }
  40. void EditorNetworkProfiler::_notification(int p_what) {
  41. switch (p_what) {
  42. case NOTIFICATION_THEME_CHANGED: {
  43. if (activate->is_pressed()) {
  44. activate->set_button_icon(theme_cache.stop_icon);
  45. } else {
  46. activate->set_button_icon(theme_cache.play_icon);
  47. }
  48. clear_button->set_button_icon(theme_cache.clear_icon);
  49. incoming_bandwidth_text->set_right_icon(theme_cache.incoming_bandwidth_icon);
  50. outgoing_bandwidth_text->set_right_icon(theme_cache.outgoing_bandwidth_icon);
  51. // This needs to be done here to set the faded color when the profiler is first opened
  52. incoming_bandwidth_text->add_theme_color_override("font_uneditable_color", theme_cache.incoming_bandwidth_color * Color(1, 1, 1, 0.5));
  53. outgoing_bandwidth_text->add_theme_color_override("font_uneditable_color", theme_cache.outgoing_bandwidth_color * Color(1, 1, 1, 0.5));
  54. } break;
  55. }
  56. }
  57. void EditorNetworkProfiler::_update_theme_item_cache() {
  58. VBoxContainer::_update_theme_item_cache();
  59. theme_cache.node_icon = get_theme_icon(SNAME("Node"), EditorStringName(EditorIcons));
  60. theme_cache.stop_icon = get_theme_icon(SNAME("Stop"), EditorStringName(EditorIcons));
  61. theme_cache.play_icon = get_theme_icon(SNAME("Play"), EditorStringName(EditorIcons));
  62. theme_cache.clear_icon = get_theme_icon(SNAME("Clear"), EditorStringName(EditorIcons));
  63. theme_cache.multiplayer_synchronizer_icon = get_theme_icon("MultiplayerSynchronizer", EditorStringName(EditorIcons));
  64. theme_cache.instance_options_icon = get_theme_icon(SNAME("InstanceOptions"), EditorStringName(EditorIcons));
  65. theme_cache.incoming_bandwidth_icon = get_theme_icon(SNAME("ArrowDown"), EditorStringName(EditorIcons));
  66. theme_cache.outgoing_bandwidth_icon = get_theme_icon(SNAME("ArrowUp"), EditorStringName(EditorIcons));
  67. theme_cache.incoming_bandwidth_color = get_theme_color(SceneStringName(font_color), EditorStringName(Editor));
  68. theme_cache.outgoing_bandwidth_color = get_theme_color(SceneStringName(font_color), EditorStringName(Editor));
  69. }
  70. void EditorNetworkProfiler::_refresh() {
  71. if (!dirty) {
  72. return;
  73. }
  74. dirty = false;
  75. refresh_rpc_data();
  76. refresh_replication_data();
  77. }
  78. void EditorNetworkProfiler::refresh_rpc_data() {
  79. counters_display->clear();
  80. TreeItem *root = counters_display->create_item();
  81. int cols = counters_display->get_columns();
  82. for (const KeyValue<ObjectID, RPCNodeInfo> &E : rpc_data) {
  83. TreeItem *node = counters_display->create_item(root);
  84. for (int j = 0; j < cols; ++j) {
  85. node->set_text_alignment(j, j > 0 ? HORIZONTAL_ALIGNMENT_RIGHT : HORIZONTAL_ALIGNMENT_LEFT);
  86. }
  87. node->set_text(0, E.value.node_path);
  88. node->set_text(1, E.value.incoming_rpc == 0 ? "-" : vformat(TTR("%d (%s)"), E.value.incoming_rpc, String::humanize_size(E.value.incoming_size)));
  89. node->set_text(2, E.value.outgoing_rpc == 0 ? "-" : vformat(TTR("%d (%s)"), E.value.outgoing_rpc, String::humanize_size(E.value.outgoing_size)));
  90. }
  91. }
  92. void EditorNetworkProfiler::refresh_replication_data() {
  93. replication_display->clear();
  94. TreeItem *root = replication_display->create_item();
  95. for (const KeyValue<ObjectID, SyncInfo> &E : sync_data) {
  96. // Ensure the nodes have at least a temporary cache.
  97. ObjectID ids[3] = { E.value.synchronizer, E.value.config, E.value.root_node };
  98. for (uint32_t i = 0; i < 3; i++) {
  99. const ObjectID &id = ids[i];
  100. if (!node_data.has(id)) {
  101. missing_node_data.insert(id);
  102. node_data[id] = NodeInfo(id);
  103. }
  104. }
  105. TreeItem *node = replication_display->create_item(root);
  106. const NodeInfo &root_info = node_data[E.value.root_node];
  107. const NodeInfo &sync_info = node_data[E.value.synchronizer];
  108. const NodeInfo &cfg_info = node_data[E.value.config];
  109. node->set_text(0, root_info.path.get_file());
  110. node->set_icon(0, has_theme_icon(root_info.type, EditorStringName(EditorIcons)) ? get_theme_icon(root_info.type, EditorStringName(EditorIcons)) : theme_cache.node_icon);
  111. node->set_tooltip_text(0, root_info.path);
  112. node->set_text(1, sync_info.path.get_file());
  113. node->set_icon(1, theme_cache.multiplayer_synchronizer_icon);
  114. node->set_tooltip_text(1, sync_info.path);
  115. int cfg_idx = cfg_info.path.find("::");
  116. if (cfg_info.path.begins_with("res://") && ResourceLoader::exists(cfg_info.path) && cfg_idx > 0) {
  117. String res_idstr = cfg_info.path.substr(cfg_idx + 2).replace("SceneReplicationConfig_", "");
  118. String scene_path = cfg_info.path.substr(0, cfg_idx);
  119. node->set_text(2, vformat("%s (%s)", res_idstr, scene_path.get_file()));
  120. node->add_button(2, theme_cache.instance_options_icon);
  121. node->set_tooltip_text(2, cfg_info.path);
  122. node->set_metadata(2, scene_path);
  123. } else {
  124. node->set_text(2, cfg_info.path);
  125. node->set_metadata(2, "");
  126. }
  127. node->set_text(3, vformat("%d - %d", E.value.incoming_syncs, E.value.outgoing_syncs));
  128. node->set_text(4, vformat("%d - %d", E.value.incoming_size, E.value.outgoing_size));
  129. }
  130. }
  131. Array EditorNetworkProfiler::pop_missing_node_data() {
  132. Array out;
  133. for (const ObjectID &id : missing_node_data) {
  134. out.push_back(id);
  135. }
  136. missing_node_data.clear();
  137. return out;
  138. }
  139. void EditorNetworkProfiler::add_node_data(const NodeInfo &p_info) {
  140. ERR_FAIL_COND(!node_data.has(p_info.id));
  141. node_data[p_info.id] = p_info;
  142. dirty = true;
  143. }
  144. void EditorNetworkProfiler::_activate_pressed() {
  145. _update_button_text();
  146. if (activate->is_pressed()) {
  147. refresh_timer->start();
  148. } else {
  149. refresh_timer->stop();
  150. }
  151. emit_signal(SNAME("enable_profiling"), activate->is_pressed());
  152. }
  153. void EditorNetworkProfiler::_update_button_text() {
  154. if (activate->is_pressed()) {
  155. activate->set_button_icon(theme_cache.stop_icon);
  156. activate->set_text(TTR("Stop"));
  157. } else {
  158. activate->set_button_icon(theme_cache.play_icon);
  159. activate->set_text(TTR("Start"));
  160. }
  161. }
  162. void EditorNetworkProfiler::started() {
  163. _clear_pressed();
  164. activate->set_disabled(false);
  165. if (EditorSettings::get_singleton()->get_project_metadata("debug_options", "autostart_network_profiler", false)) {
  166. set_profiling(true);
  167. refresh_timer->start();
  168. }
  169. }
  170. void EditorNetworkProfiler::stopped() {
  171. activate->set_disabled(true);
  172. set_profiling(false);
  173. refresh_timer->stop();
  174. }
  175. void EditorNetworkProfiler::set_profiling(bool p_pressed) {
  176. activate->set_pressed(p_pressed);
  177. _update_button_text();
  178. emit_signal(SNAME("enable_profiling"), activate->is_pressed());
  179. }
  180. void EditorNetworkProfiler::_clear_pressed() {
  181. rpc_data.clear();
  182. sync_data.clear();
  183. node_data.clear();
  184. missing_node_data.clear();
  185. set_bandwidth(0, 0);
  186. refresh_rpc_data();
  187. refresh_replication_data();
  188. clear_button->set_disabled(true);
  189. }
  190. void EditorNetworkProfiler::_autostart_toggled(bool p_toggled_on) {
  191. EditorSettings::get_singleton()->set_project_metadata("debug_options", "autostart_network_profiler", p_toggled_on);
  192. }
  193. void EditorNetworkProfiler::_replication_button_clicked(TreeItem *p_item, int p_column, int p_idx, MouseButton p_button) {
  194. if (!p_item) {
  195. return;
  196. }
  197. String meta = p_item->get_metadata(p_column);
  198. if (meta.size() && ResourceLoader::exists(meta)) {
  199. emit_signal("open_request", meta);
  200. }
  201. }
  202. void EditorNetworkProfiler::add_rpc_frame_data(const RPCNodeInfo &p_frame) {
  203. if (clear_button->is_disabled()) {
  204. clear_button->set_disabled(false);
  205. }
  206. dirty = true;
  207. if (!rpc_data.has(p_frame.node)) {
  208. rpc_data.insert(p_frame.node, p_frame);
  209. } else {
  210. rpc_data[p_frame.node].incoming_rpc += p_frame.incoming_rpc;
  211. rpc_data[p_frame.node].outgoing_rpc += p_frame.outgoing_rpc;
  212. }
  213. if (p_frame.incoming_rpc) {
  214. rpc_data[p_frame.node].incoming_size = p_frame.incoming_size / p_frame.incoming_rpc;
  215. }
  216. if (p_frame.outgoing_rpc) {
  217. rpc_data[p_frame.node].outgoing_size = p_frame.outgoing_size / p_frame.outgoing_rpc;
  218. }
  219. }
  220. void EditorNetworkProfiler::add_sync_frame_data(const SyncInfo &p_frame) {
  221. if (clear_button->is_disabled()) {
  222. clear_button->set_disabled(false);
  223. }
  224. dirty = true;
  225. if (!sync_data.has(p_frame.synchronizer)) {
  226. sync_data[p_frame.synchronizer] = p_frame;
  227. } else {
  228. sync_data[p_frame.synchronizer].incoming_syncs += p_frame.incoming_syncs;
  229. sync_data[p_frame.synchronizer].outgoing_syncs += p_frame.outgoing_syncs;
  230. }
  231. SyncInfo &info = sync_data[p_frame.synchronizer];
  232. if (p_frame.incoming_syncs) {
  233. info.incoming_size = p_frame.incoming_size / p_frame.incoming_syncs;
  234. }
  235. if (p_frame.outgoing_syncs) {
  236. info.outgoing_size = p_frame.outgoing_size / p_frame.outgoing_syncs;
  237. }
  238. }
  239. void EditorNetworkProfiler::set_bandwidth(int p_incoming, int p_outgoing) {
  240. incoming_bandwidth_text->set_text(vformat(TTR("%s/s"), String::humanize_size(p_incoming)));
  241. outgoing_bandwidth_text->set_text(vformat(TTR("%s/s"), String::humanize_size(p_outgoing)));
  242. // Make labels more prominent when the bandwidth is greater than 0 to attract user attention
  243. incoming_bandwidth_text->add_theme_color_override(
  244. "font_uneditable_color",
  245. theme_cache.incoming_bandwidth_color * Color(1, 1, 1, p_incoming > 0 ? 1 : 0.5));
  246. outgoing_bandwidth_text->add_theme_color_override(
  247. "font_uneditable_color",
  248. theme_cache.outgoing_bandwidth_color * Color(1, 1, 1, p_outgoing > 0 ? 1 : 0.5));
  249. }
  250. bool EditorNetworkProfiler::is_profiling() {
  251. return activate->is_pressed();
  252. }
  253. EditorNetworkProfiler::EditorNetworkProfiler() {
  254. HBoxContainer *hb = memnew(HBoxContainer);
  255. hb->add_theme_constant_override("separation", 8 * EDSCALE);
  256. add_child(hb);
  257. activate = memnew(Button);
  258. activate->set_toggle_mode(true);
  259. activate->set_text(TTR("Start"));
  260. activate->set_disabled(true);
  261. activate->connect(SceneStringName(pressed), callable_mp(this, &EditorNetworkProfiler::_activate_pressed));
  262. hb->add_child(activate);
  263. clear_button = memnew(Button);
  264. clear_button->set_text(TTR("Clear"));
  265. clear_button->set_disabled(true);
  266. clear_button->connect(SceneStringName(pressed), callable_mp(this, &EditorNetworkProfiler::_clear_pressed));
  267. hb->add_child(clear_button);
  268. CheckBox *autostart_checkbox = memnew(CheckBox);
  269. autostart_checkbox->set_text(TTR("Autostart"));
  270. autostart_checkbox->set_pressed(EditorSettings::get_singleton()->get_project_metadata("debug_options", "autostart_network_profiler", false));
  271. autostart_checkbox->connect(SceneStringName(toggled), callable_mp(this, &EditorNetworkProfiler::_autostart_toggled));
  272. hb->add_child(autostart_checkbox);
  273. hb->add_spacer();
  274. Label *lb = memnew(Label);
  275. // TRANSLATORS: This is the label for the network profiler's incoming bandwidth.
  276. lb->set_text(TTR("Down", "Network"));
  277. hb->add_child(lb);
  278. incoming_bandwidth_text = memnew(LineEdit);
  279. incoming_bandwidth_text->set_editable(false);
  280. incoming_bandwidth_text->set_custom_minimum_size(Size2(120, 0) * EDSCALE);
  281. incoming_bandwidth_text->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT);
  282. hb->add_child(incoming_bandwidth_text);
  283. Control *down_up_spacer = memnew(Control);
  284. down_up_spacer->set_custom_minimum_size(Size2(30, 0) * EDSCALE);
  285. hb->add_child(down_up_spacer);
  286. lb = memnew(Label);
  287. // TRANSLATORS: This is the label for the network profiler's outgoing bandwidth.
  288. lb->set_text(TTR("Up", "Network"));
  289. hb->add_child(lb);
  290. outgoing_bandwidth_text = memnew(LineEdit);
  291. outgoing_bandwidth_text->set_editable(false);
  292. outgoing_bandwidth_text->set_custom_minimum_size(Size2(120, 0) * EDSCALE);
  293. outgoing_bandwidth_text->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT);
  294. hb->add_child(outgoing_bandwidth_text);
  295. // Set initial texts in the incoming/outgoing bandwidth labels
  296. set_bandwidth(0, 0);
  297. HSplitContainer *sc = memnew(HSplitContainer);
  298. add_child(sc);
  299. sc->set_v_size_flags(SIZE_EXPAND_FILL);
  300. sc->set_h_size_flags(SIZE_EXPAND_FILL);
  301. sc->set_split_offset(100 * EDSCALE);
  302. // RPC
  303. counters_display = memnew(Tree);
  304. counters_display->set_custom_minimum_size(Size2(320, 0) * EDSCALE);
  305. counters_display->set_v_size_flags(SIZE_EXPAND_FILL);
  306. counters_display->set_h_size_flags(SIZE_EXPAND_FILL);
  307. counters_display->set_hide_folding(true);
  308. counters_display->set_hide_root(true);
  309. counters_display->set_columns(3);
  310. counters_display->set_column_titles_visible(true);
  311. counters_display->set_column_title(0, TTR("Node"));
  312. counters_display->set_column_expand(0, true);
  313. counters_display->set_column_clip_content(0, true);
  314. counters_display->set_column_custom_minimum_width(0, 60 * EDSCALE);
  315. counters_display->set_column_title(1, TTR("Incoming RPC"));
  316. counters_display->set_column_expand(1, false);
  317. counters_display->set_column_clip_content(1, true);
  318. counters_display->set_column_custom_minimum_width(1, 120 * EDSCALE);
  319. counters_display->set_column_title(2, TTR("Outgoing RPC"));
  320. counters_display->set_column_expand(2, false);
  321. counters_display->set_column_clip_content(2, true);
  322. counters_display->set_column_custom_minimum_width(2, 120 * EDSCALE);
  323. sc->add_child(counters_display);
  324. // Replication
  325. replication_display = memnew(Tree);
  326. replication_display->set_custom_minimum_size(Size2(320, 0) * EDSCALE);
  327. replication_display->set_v_size_flags(SIZE_EXPAND_FILL);
  328. replication_display->set_h_size_flags(SIZE_EXPAND_FILL);
  329. replication_display->set_hide_folding(true);
  330. replication_display->set_hide_root(true);
  331. replication_display->set_columns(5);
  332. replication_display->set_column_titles_visible(true);
  333. replication_display->set_column_title(0, TTR("Root"));
  334. replication_display->set_column_expand(0, true);
  335. replication_display->set_column_clip_content(0, true);
  336. replication_display->set_column_custom_minimum_width(0, 80 * EDSCALE);
  337. replication_display->set_column_title(1, TTR("Synchronizer"));
  338. replication_display->set_column_expand(1, true);
  339. replication_display->set_column_clip_content(1, true);
  340. replication_display->set_column_custom_minimum_width(1, 80 * EDSCALE);
  341. replication_display->set_column_title(2, TTR("Config"));
  342. replication_display->set_column_expand(2, true);
  343. replication_display->set_column_clip_content(2, true);
  344. replication_display->set_column_custom_minimum_width(2, 80 * EDSCALE);
  345. replication_display->set_column_title(3, TTR("Count"));
  346. replication_display->set_column_expand(3, false);
  347. replication_display->set_column_clip_content(3, true);
  348. replication_display->set_column_custom_minimum_width(3, 80 * EDSCALE);
  349. replication_display->set_column_title(4, TTR("Size"));
  350. replication_display->set_column_expand(4, false);
  351. replication_display->set_column_clip_content(4, true);
  352. replication_display->set_column_custom_minimum_width(4, 80 * EDSCALE);
  353. replication_display->connect("button_clicked", callable_mp(this, &EditorNetworkProfiler::_replication_button_clicked));
  354. sc->add_child(replication_display);
  355. refresh_timer = memnew(Timer);
  356. refresh_timer->set_wait_time(0.5);
  357. refresh_timer->connect("timeout", callable_mp(this, &EditorNetworkProfiler::_refresh));
  358. add_child(refresh_timer);
  359. }