diff options
Diffstat (limited to 'editor/debugger')
-rw-r--r-- | editor/debugger/editor_debugger_inspector.cpp | 4 | ||||
-rw-r--r-- | editor/debugger/editor_debugger_node.cpp | 45 | ||||
-rw-r--r-- | editor/debugger/editor_debugger_node.h | 39 | ||||
-rw-r--r-- | editor/debugger/editor_debugger_server.cpp | 90 | ||||
-rw-r--r-- | editor/debugger/editor_debugger_server.h | 49 | ||||
-rw-r--r-- | editor/debugger/editor_network_profiler.cpp | 208 | ||||
-rw-r--r-- | editor/debugger/editor_network_profiler.h | 73 | ||||
-rw-r--r-- | editor/debugger/editor_profiler.cpp | 773 | ||||
-rw-r--r-- | editor/debugger/editor_profiler.h | 177 | ||||
-rw-r--r-- | editor/debugger/editor_visual_profiler.cpp | 842 | ||||
-rw-r--r-- | editor/debugger/editor_visual_profiler.h | 154 | ||||
-rw-r--r-- | editor/debugger/script_editor_debugger.cpp | 320 | ||||
-rw-r--r-- | editor/debugger/script_editor_debugger.h | 45 |
13 files changed, 2586 insertions, 233 deletions
diff --git a/editor/debugger/editor_debugger_inspector.cpp b/editor/debugger/editor_debugger_inspector.cpp index 1506d64b63..9587daf93e 100644 --- a/editor/debugger/editor_debugger_inspector.cpp +++ b/editor/debugger/editor_debugger_inspector.cpp @@ -30,8 +30,8 @@ #include "editor_debugger_inspector.h" +#include "core/debugger/debugger_marshalls.h" #include "core/io/marshalls.h" -#include "core/script_debugger_remote.h" #include "editor/editor_node.h" #include "scene/debugger/scene_debugger.h" @@ -226,7 +226,7 @@ Object *EditorDebuggerInspector::get_object(ObjectID p_id) { void EditorDebuggerInspector::add_stack_variable(const Array &p_array) { - ScriptDebuggerRemote::ScriptStackVariable var; + DebuggerMarshalls::ScriptStackVariable var; var.deserialize(p_array); String n = var.name; Variant v = var.value; diff --git a/editor/debugger/editor_debugger_node.cpp b/editor/debugger/editor_debugger_node.cpp index fba86f2954..f4a8102b79 100644 --- a/editor/debugger/editor_debugger_node.cpp +++ b/editor/debugger/editor_debugger_node.cpp @@ -31,9 +31,12 @@ #include "editor_debugger_node.h" #include "editor/debugger/editor_debugger_tree.h" +#include "editor/debugger/script_editor_debugger.h" #include "editor/editor_log.h" #include "editor/editor_node.h" #include "editor/plugins/script_editor_plugin.h" +#include "scene/gui/menu_button.h" +#include "scene/gui/tab_container.h" template <typename Func> void _for_all(TabContainer *p_node, const Func &p_func) { @@ -49,7 +52,6 @@ EditorDebuggerNode *EditorDebuggerNode::singleton = NULL; EditorDebuggerNode::EditorDebuggerNode() { if (!singleton) singleton = this; - server.instance(); add_constant_override("margin_left", -EditorNode::get_singleton()->get_gui_base()->get_stylebox("BottomPanelDebuggerOverride", "EditorStyles")->get_margin(MARGIN_LEFT)); add_constant_override("margin_right", -EditorNode::get_singleton()->get_gui_base()->get_stylebox("BottomPanelDebuggerOverride", "EditorStyles")->get_margin(MARGIN_RIGHT)); @@ -179,10 +181,9 @@ Error EditorDebuggerNode::start() { EditorNode::get_singleton()->make_bottom_panel_item_visible(this); } - int remote_port = (int)EditorSettings::get_singleton()->get("network/debug/remote_port"); - const Error err = server->listen(remote_port); + server = Ref<EditorDebuggerServer>(EditorDebuggerServer::create_default()); + const Error err = server->start(); if (err != OK) { - EditorNode::get_log()->add_message(String("Error listening on port ") + itos(remote_port), EditorLog::MSG_TYPE_ERROR); return err; } set_process(true); @@ -191,9 +192,10 @@ Error EditorDebuggerNode::start() { } void EditorDebuggerNode::stop() { - if (server->is_listening()) { + if (server.is_valid()) { server->stop(); EditorNode::get_log()->add_message("--- Debugging process stopped ---", EditorLog::MSG_TYPE_EDITOR); + server.unref(); } // Also close all debugging sessions. _for_all(tabs, [&](ScriptEditorDebugger *dbg) { @@ -231,9 +233,15 @@ void EditorDebuggerNode::_notification(int p_what) { break; } - if (p_what != NOTIFICATION_PROCESS || !server->is_listening()) + if (p_what != NOTIFICATION_PROCESS || !server.is_valid()) return; + if (!server.is_valid() || !server->is_active()) { + stop(); + return; + } + server->poll(); + // Errors and warnings int error_count = 0; int warning_count = 0; @@ -293,9 +301,8 @@ void EditorDebuggerNode::_notification(int p_what) { if (tabs->get_tab_count() <= 4) { // Max 4 debugging sessions active. debugger = _add_debugger(); } else { - // We already have too many sessions, disconnecting new clients to prevent it from hanging. - // (Not keeping a reference to the connection will disconnect it) - server->take_connection(); + // We already have too many sessions, disconnecting new clients to prevent them from hanging. + server->take_connection()->close(); return; // Can't add, stop here. } } @@ -462,6 +469,26 @@ void EditorDebuggerNode::reload_scripts() { }); } +void EditorDebuggerNode::debug_next() { + get_default_debugger()->debug_next(); +} + +void EditorDebuggerNode::debug_step() { + get_default_debugger()->debug_step(); +} + +void EditorDebuggerNode::debug_break() { + get_default_debugger()->debug_break(); +} + +void EditorDebuggerNode::debug_continue() { + get_default_debugger()->debug_continue(); +} + +String EditorDebuggerNode::get_var_value(const String &p_var) const { + return get_default_debugger()->get_var_value(p_var); +} + // LiveEdit/Inspector void EditorDebuggerNode::request_remote_tree() { get_current_debugger()->request_remote_tree(); diff --git a/editor/debugger/editor_debugger_node.h b/editor/debugger/editor_debugger_node.h index 13a1d6dcb3..6181ccdb5f 100644 --- a/editor/debugger/editor_debugger_node.h +++ b/editor/debugger/editor_debugger_node.h @@ -31,17 +31,30 @@ #ifndef EDITOR_DEBUGGER_NODE_H #define EDITOR_DEBUGGER_NODE_H -#include "core/io/tcp_server.h" -#include "editor/debugger/script_editor_debugger.h" -#include "scene/gui/button.h" -#include "scene/gui/tab_container.h" +#include "editor/debugger/editor_debugger_server.h" +#include "scene/gui/margin_container.h" +class Button; class EditorDebuggerTree; +class EditorDebuggerRemoteObject; +class MenuButton; +class ScriptEditorDebugger; +class TabContainer; class EditorDebuggerNode : public MarginContainer { GDCLASS(EditorDebuggerNode, MarginContainer); +public: + enum CameraOverride { + OVERRIDE_NONE, + OVERRIDE_2D, + OVERRIDE_3D_1, // 3D Viewport 1 + OVERRIDE_3D_2, // 3D Viewport 2 + OVERRIDE_3D_3, // 3D Viewport 3 + OVERRIDE_3D_4 // 3D Viewport 4 + }; + private: enum Options { DEBUG_NEXT, @@ -71,7 +84,7 @@ private: } }; - Ref<TCP_Server> server = NULL; + Ref<EditorDebuggerServer> server; TabContainer *tabs = NULL; Button *debugger_button = NULL; MenuButton *script_menu = NULL; @@ -87,7 +100,7 @@ private: bool auto_switch_remote_scene_tree = false; bool debug_with_external_editor = false; bool hide_on_stop = true; - ScriptEditorDebugger::CameraOverride camera_override = ScriptEditorDebugger::OVERRIDE_NONE; + CameraOverride camera_override = OVERRIDE_NONE; Map<Breakpoint, bool> breakpoints; ScriptEditorDebugger *_add_debugger(); @@ -130,10 +143,10 @@ public: ScriptEditorDebugger *get_default_debugger() const; ScriptEditorDebugger *get_debugger(int p_debugger) const; - void debug_next() { get_default_debugger()->debug_next(); } - void debug_step() { get_default_debugger()->debug_step(); } - void debug_break() { get_default_debugger()->debug_break(); } - void debug_continue() { get_default_debugger()->debug_continue(); } + void debug_next(); + void debug_step(); + void debug_break(); + void debug_continue(); void set_script_debug_button(MenuButton *p_button); @@ -141,7 +154,7 @@ public: debugger_button = p_button; } - String get_var_value(const String &p_var) const { return get_default_debugger()->get_var_value(p_var); } + String get_var_value(const String &p_var) const; Ref<Script> get_dump_stack_script() const { return stack_script; } // Why do we need this? bool get_debug_with_external_editor() { return debug_with_external_editor; } @@ -167,8 +180,8 @@ public: void live_debug_reparent_node(const NodePath &p_at, const NodePath &p_new_place, const String &p_new_name, int p_at_pos); // Camera - void set_camera_override(ScriptEditorDebugger::CameraOverride p_override) { camera_override = p_override; } - ScriptEditorDebugger::CameraOverride get_camera_override() { return camera_override; } + void set_camera_override(CameraOverride p_override) { camera_override = p_override; } + CameraOverride get_camera_override() { return camera_override; } Error start(); diff --git a/editor/debugger/editor_debugger_server.cpp b/editor/debugger/editor_debugger_server.cpp new file mode 100644 index 0000000000..c80988a662 --- /dev/null +++ b/editor/debugger/editor_debugger_server.cpp @@ -0,0 +1,90 @@ +/*************************************************************************/ +/* editor_debugger_server.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "editor_debugger_server.h" + +#include "core/io/marshalls.h" +#include "core/io/tcp_server.h" +#include "core/os/mutex.h" +#include "core/os/thread.h" +#include "editor/editor_log.h" +#include "editor/editor_node.h" +#include "editor/editor_settings.h" + +class EditorDebuggerServerTCP : public EditorDebuggerServer { + +private: + Ref<TCP_Server> server; + +public: + virtual void poll() {} + virtual Error start(); + virtual void stop(); + virtual bool is_active() const; + virtual bool is_connection_available() const; + virtual Ref<RemoteDebuggerPeer> take_connection(); + + EditorDebuggerServerTCP(); +}; + +EditorDebuggerServerTCP::EditorDebuggerServerTCP() { + server.instance(); +} + +Error EditorDebuggerServerTCP::start() { + int remote_port = (int)EditorSettings::get_singleton()->get("network/debug/remote_port"); + const Error err = server->listen(remote_port); + if (err != OK) { + EditorNode::get_log()->add_message(String("Error listening on port ") + itos(remote_port), EditorLog::MSG_TYPE_ERROR); + return err; + } + return err; +} + +void EditorDebuggerServerTCP::stop() { + server->stop(); +} + +bool EditorDebuggerServerTCP::is_active() const { + return server->is_listening(); +} + +bool EditorDebuggerServerTCP::is_connection_available() const { + return server->is_listening() && server->is_connection_available(); +} + +Ref<RemoteDebuggerPeer> EditorDebuggerServerTCP::take_connection() { + ERR_FAIL_COND_V(!is_connection_available(), Ref<RemoteDebuggerPeer>()); + return memnew(RemoteDebuggerPeerTCP(server->take_connection())); +} + +EditorDebuggerServer *EditorDebuggerServer::create_default() { + return memnew(EditorDebuggerServerTCP); +} diff --git a/editor/debugger/editor_debugger_server.h b/editor/debugger/editor_debugger_server.h new file mode 100644 index 0000000000..e9798c90b3 --- /dev/null +++ b/editor/debugger/editor_debugger_server.h @@ -0,0 +1,49 @@ +/*************************************************************************/ +/* editor_debugger_server.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef EDITOR_DEBUGGER_CONNECTION_H +#define EDITOR_DEBUGGER_CONNECTION_H + +#include "core/debugger/remote_debugger_peer.h" +#include "core/reference.h" + +class EditorDebuggerServer : public Reference { + +public: + static EditorDebuggerServer *create_default(); + virtual void poll() = 0; + virtual Error start() = 0; + virtual void stop() = 0; + virtual bool is_active() const = 0; + virtual bool is_connection_available() const = 0; + virtual Ref<RemoteDebuggerPeer> take_connection() = 0; +}; + +#endif // EDITOR_DEBUGGER_CONNECTION_H diff --git a/editor/debugger/editor_network_profiler.cpp b/editor/debugger/editor_network_profiler.cpp new file mode 100644 index 0000000000..21ef66d1aa --- /dev/null +++ b/editor/debugger/editor_network_profiler.cpp @@ -0,0 +1,208 @@ +/*************************************************************************/ +/* editor_network_profiler.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "editor_network_profiler.h" + +#include "core/os/os.h" +#include "editor/editor_scale.h" +#include "editor/editor_settings.h" + +void EditorNetworkProfiler::_bind_methods() { + ADD_SIGNAL(MethodInfo("enable_profiling", PropertyInfo(Variant::BOOL, "enable"))); +} + +void EditorNetworkProfiler::_notification(int p_what) { + + if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) { + activate->set_icon(get_icon("Play", "EditorIcons")); + clear_button->set_icon(get_icon("Clear", "EditorIcons")); + incoming_bandwidth_text->set_right_icon(get_icon("ArrowDown", "EditorIcons")); + outgoing_bandwidth_text->set_right_icon(get_icon("ArrowUp", "EditorIcons")); + + // This needs to be done here to set the faded color when the profiler is first opened + incoming_bandwidth_text->add_color_override("font_color_uneditable", get_color("font_color", "Editor") * Color(1, 1, 1, 0.5)); + outgoing_bandwidth_text->add_color_override("font_color_uneditable", get_color("font_color", "Editor") * Color(1, 1, 1, 0.5)); + } +} + +void EditorNetworkProfiler::_update_frame() { + + counters_display->clear(); + + TreeItem *root = counters_display->create_item(); + + for (Map<ObjectID, DebuggerMarshalls::MultiplayerNodeInfo>::Element *E = nodes_data.front(); E; E = E->next()) { + + TreeItem *node = counters_display->create_item(root); + + for (int j = 0; j < counters_display->get_columns(); ++j) { + node->set_text_align(j, j > 0 ? TreeItem::ALIGN_RIGHT : TreeItem::ALIGN_LEFT); + } + + node->set_text(0, E->get().node_path); + node->set_text(1, E->get().incoming_rpc == 0 ? "-" : itos(E->get().incoming_rpc)); + node->set_text(2, E->get().incoming_rset == 0 ? "-" : itos(E->get().incoming_rset)); + node->set_text(3, E->get().outgoing_rpc == 0 ? "-" : itos(E->get().outgoing_rpc)); + node->set_text(4, E->get().outgoing_rset == 0 ? "-" : itos(E->get().outgoing_rset)); + } +} + +void EditorNetworkProfiler::_activate_pressed() { + + if (activate->is_pressed()) { + activate->set_icon(get_icon("Stop", "EditorIcons")); + activate->set_text(TTR("Stop")); + } else { + activate->set_icon(get_icon("Play", "EditorIcons")); + activate->set_text(TTR("Start")); + } + emit_signal("enable_profiling", activate->is_pressed()); +} + +void EditorNetworkProfiler::_clear_pressed() { + nodes_data.clear(); + set_bandwidth(0, 0); + if (frame_delay->is_stopped()) { + frame_delay->set_wait_time(0.1); + frame_delay->start(); + } +} + +void EditorNetworkProfiler::add_node_frame_data(const DebuggerMarshalls::MultiplayerNodeInfo p_frame) { + + if (!nodes_data.has(p_frame.node)) { + nodes_data.insert(p_frame.node, p_frame); + } else { + nodes_data[p_frame.node].incoming_rpc += p_frame.incoming_rpc; + nodes_data[p_frame.node].incoming_rset += p_frame.incoming_rset; + nodes_data[p_frame.node].outgoing_rpc += p_frame.outgoing_rpc; + nodes_data[p_frame.node].outgoing_rset += p_frame.outgoing_rset; + } + + if (frame_delay->is_stopped()) { + frame_delay->set_wait_time(0.1); + frame_delay->start(); + } +} + +void EditorNetworkProfiler::set_bandwidth(int p_incoming, int p_outgoing) { + + incoming_bandwidth_text->set_text(vformat(TTR("%s/s"), String::humanize_size(p_incoming))); + outgoing_bandwidth_text->set_text(vformat(TTR("%s/s"), String::humanize_size(p_outgoing))); + + // Make labels more prominent when the bandwidth is greater than 0 to attract user attention + incoming_bandwidth_text->add_color_override( + "font_color_uneditable", + get_color("font_color", "Editor") * Color(1, 1, 1, p_incoming > 0 ? 1 : 0.5)); + outgoing_bandwidth_text->add_color_override( + "font_color_uneditable", + get_color("font_color", "Editor") * Color(1, 1, 1, p_outgoing > 0 ? 1 : 0.5)); +} + +bool EditorNetworkProfiler::is_profiling() { + return activate->is_pressed(); +} + +EditorNetworkProfiler::EditorNetworkProfiler() { + + HBoxContainer *hb = memnew(HBoxContainer); + hb->add_constant_override("separation", 8 * EDSCALE); + add_child(hb); + + activate = memnew(Button); + activate->set_toggle_mode(true); + activate->set_text(TTR("Start")); + activate->connect("pressed", callable_mp(this, &EditorNetworkProfiler::_activate_pressed)); + hb->add_child(activate); + + clear_button = memnew(Button); + clear_button->set_text(TTR("Clear")); + clear_button->connect("pressed", callable_mp(this, &EditorNetworkProfiler::_clear_pressed)); + hb->add_child(clear_button); + + hb->add_spacer(); + + Label *lb = memnew(Label); + lb->set_text(TTR("Down")); + hb->add_child(lb); + + incoming_bandwidth_text = memnew(LineEdit); + incoming_bandwidth_text->set_editable(false); + incoming_bandwidth_text->set_custom_minimum_size(Size2(120, 0) * EDSCALE); + incoming_bandwidth_text->set_align(LineEdit::Align::ALIGN_RIGHT); + hb->add_child(incoming_bandwidth_text); + + Control *down_up_spacer = memnew(Control); + down_up_spacer->set_custom_minimum_size(Size2(30, 0) * EDSCALE); + hb->add_child(down_up_spacer); + + lb = memnew(Label); + lb->set_text(TTR("Up")); + hb->add_child(lb); + + outgoing_bandwidth_text = memnew(LineEdit); + outgoing_bandwidth_text->set_editable(false); + outgoing_bandwidth_text->set_custom_minimum_size(Size2(120, 0) * EDSCALE); + outgoing_bandwidth_text->set_align(LineEdit::Align::ALIGN_RIGHT); + hb->add_child(outgoing_bandwidth_text); + + // Set initial texts in the incoming/outgoing bandwidth labels + set_bandwidth(0, 0); + + counters_display = memnew(Tree); + counters_display->set_custom_minimum_size(Size2(300, 0) * EDSCALE); + counters_display->set_v_size_flags(SIZE_EXPAND_FILL); + counters_display->set_hide_folding(true); + counters_display->set_hide_root(true); + counters_display->set_columns(5); + counters_display->set_column_titles_visible(true); + counters_display->set_column_title(0, TTR("Node")); + counters_display->set_column_expand(0, true); + counters_display->set_column_min_width(0, 60 * EDSCALE); + counters_display->set_column_title(1, TTR("Incoming RPC")); + counters_display->set_column_expand(1, false); + counters_display->set_column_min_width(1, 120 * EDSCALE); + counters_display->set_column_title(2, TTR("Incoming RSET")); + counters_display->set_column_expand(2, false); + counters_display->set_column_min_width(2, 120 * EDSCALE); + counters_display->set_column_title(3, TTR("Outgoing RPC")); + counters_display->set_column_expand(3, false); + counters_display->set_column_min_width(3, 120 * EDSCALE); + counters_display->set_column_title(4, TTR("Outgoing RSET")); + counters_display->set_column_expand(4, false); + counters_display->set_column_min_width(4, 120 * EDSCALE); + add_child(counters_display); + + frame_delay = memnew(Timer); + frame_delay->set_wait_time(0.1); + frame_delay->set_one_shot(true); + add_child(frame_delay); + frame_delay->connect("timeout", callable_mp(this, &EditorNetworkProfiler::_update_frame)); +} diff --git a/editor/debugger/editor_network_profiler.h b/editor/debugger/editor_network_profiler.h new file mode 100644 index 0000000000..f532dc5dd0 --- /dev/null +++ b/editor/debugger/editor_network_profiler.h @@ -0,0 +1,73 @@ +/*************************************************************************/ +/* editor_network_profiler.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef EDITORNETWORKPROFILER_H +#define EDITORNETWORKPROFILER_H + +#include "core/debugger/debugger_marshalls.h" +#include "scene/gui/box_container.h" +#include "scene/gui/button.h" +#include "scene/gui/label.h" +#include "scene/gui/split_container.h" +#include "scene/gui/tree.h" + +class EditorNetworkProfiler : public VBoxContainer { + + GDCLASS(EditorNetworkProfiler, VBoxContainer) + +private: + Button *activate; + Button *clear_button; + Tree *counters_display; + LineEdit *incoming_bandwidth_text; + LineEdit *outgoing_bandwidth_text; + + Timer *frame_delay; + + Map<ObjectID, DebuggerMarshalls::MultiplayerNodeInfo> nodes_data; + + void _update_frame(); + + void _activate_pressed(); + void _clear_pressed(); + +protected: + void _notification(int p_what); + static void _bind_methods(); + +public: + void add_node_frame_data(const DebuggerMarshalls::MultiplayerNodeInfo p_frame); + void set_bandwidth(int p_incoming, int p_outgoing); + bool is_profiling(); + + EditorNetworkProfiler(); +}; + +#endif //EDITORNETWORKPROFILER_H diff --git a/editor/debugger/editor_profiler.cpp b/editor/debugger/editor_profiler.cpp new file mode 100644 index 0000000000..2f3ad210b2 --- /dev/null +++ b/editor/debugger/editor_profiler.cpp @@ -0,0 +1,773 @@ +/*************************************************************************/ +/* editor_profiler.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "editor_profiler.h" + +#include "core/os/os.h" +#include "editor/editor_scale.h" +#include "editor/editor_settings.h" + +void EditorProfiler::_make_metric_ptrs(Metric &m) { + + for (int i = 0; i < m.categories.size(); i++) { + m.category_ptrs[m.categories[i].signature] = &m.categories.write[i]; + for (int j = 0; j < m.categories[i].items.size(); j++) { + m.item_ptrs[m.categories[i].items[j].signature] = &m.categories.write[i].items.write[j]; + } + } +} + +void EditorProfiler::add_frame_metric(const Metric &p_metric, bool p_final) { + + ++last_metric; + if (last_metric >= frame_metrics.size()) + last_metric = 0; + + frame_metrics.write[last_metric] = p_metric; + _make_metric_ptrs(frame_metrics.write[last_metric]); + + updating_frame = true; + cursor_metric_edit->set_max(frame_metrics[last_metric].frame_number); + cursor_metric_edit->set_min(MAX(frame_metrics[last_metric].frame_number - frame_metrics.size(), 0)); + + if (!seeking) { + cursor_metric_edit->set_value(frame_metrics[last_metric].frame_number); + if (hover_metric != -1) { + hover_metric++; + if (hover_metric >= frame_metrics.size()) { + hover_metric = 0; + } + } + } + updating_frame = false; + + if (frame_delay->is_stopped()) { + + frame_delay->set_wait_time(p_final ? 0.1 : 1); + frame_delay->start(); + } + + if (plot_delay->is_stopped()) { + plot_delay->set_wait_time(0.1); + plot_delay->start(); + } +} + +void EditorProfiler::clear() { + + int metric_size = EditorSettings::get_singleton()->get("debugger/profiler_frame_history_size"); + metric_size = CLAMP(metric_size, 60, 1024); + frame_metrics.clear(); + frame_metrics.resize(metric_size); + last_metric = -1; + variables->clear(); + plot_sigs.clear(); + plot_sigs.insert("physics_frame_time"); + plot_sigs.insert("category_frame_time"); + + updating_frame = true; + cursor_metric_edit->set_min(0); + cursor_metric_edit->set_max(100); // Doesn't make much sense, but we can't have min == max. Doesn't hurt. + cursor_metric_edit->set_value(0); + updating_frame = false; + hover_metric = -1; + seeking = false; +} + +static String _get_percent_txt(float p_value, float p_total) { + if (p_total == 0) { + p_total = 0.00001; + } + + return String::num((p_value / p_total) * 100, 1) + "%"; +} + +String EditorProfiler::_get_time_as_text(const Metric &m, float p_time, int p_calls) { + + const int dmode = display_mode->get_selected(); + + if (dmode == DISPLAY_FRAME_TIME) { + return rtos(p_time * 1000).pad_decimals(2) + " ms"; + } else if (dmode == DISPLAY_AVERAGE_TIME) { + if (p_calls == 0) { + return "0.00 ms"; + } else { + return rtos((p_time / p_calls) * 1000).pad_decimals(2) + " ms"; + } + } else if (dmode == DISPLAY_FRAME_PERCENT) { + return _get_percent_txt(p_time, m.frame_time); + } else if (dmode == DISPLAY_PHYSICS_FRAME_PERCENT) { + return _get_percent_txt(p_time, m.physics_frame_time); + } + + return "err"; +} + +Color EditorProfiler::_get_color_from_signature(const StringName &p_signature) const { + + Color bc = get_color("error_color", "Editor"); + double rot = ABS(double(p_signature.hash()) / double(0x7FFFFFFF)); + Color c; + c.set_hsv(rot, bc.get_s(), bc.get_v()); + return c.linear_interpolate(get_color("base_color", "Editor"), 0.07); +} + +void EditorProfiler::_item_edited() { + + if (updating_frame) + return; + + TreeItem *item = variables->get_edited(); + if (!item) + return; + StringName signature = item->get_metadata(0); + bool checked = item->is_checked(0); + + if (checked) + plot_sigs.insert(signature); + else + plot_sigs.erase(signature); + + if (!frame_delay->is_processing()) { + frame_delay->set_wait_time(0.1); + frame_delay->start(); + } + + _update_plot(); +} + +void EditorProfiler::_update_plot() { + + const int w = graph->get_size().width; + const int h = graph->get_size().height; + bool reset_texture = false; + const int desired_len = w * h * 4; + + if (graph_image.size() != desired_len) { + reset_texture = true; + graph_image.resize(desired_len); + } + + uint8_t *wr = graph_image.ptrw(); + const Color background_color = get_color("dark_color_2", "Editor"); + + // Clear the previous frame and set the background color. + for (int i = 0; i < desired_len; i += 4) { + wr[i + 0] = Math::fast_ftoi(background_color.r * 255); + wr[i + 1] = Math::fast_ftoi(background_color.g * 255); + wr[i + 2] = Math::fast_ftoi(background_color.b * 255); + wr[i + 3] = 255; + } + + //find highest value + + const bool use_self = display_time->get_selected() == DISPLAY_SELF_TIME; + float highest = 0; + + for (int i = 0; i < frame_metrics.size(); i++) { + const Metric &m = frame_metrics[i]; + if (!m.valid) + continue; + + for (Set<StringName>::Element *E = plot_sigs.front(); E; E = E->next()) { + + const Map<StringName, Metric::Category *>::Element *F = m.category_ptrs.find(E->get()); + if (F) { + highest = MAX(F->get()->total_time, highest); + } + + const Map<StringName, Metric::Category::Item *>::Element *G = m.item_ptrs.find(E->get()); + if (G) { + if (use_self) { + highest = MAX(G->get()->self, highest); + } else { + highest = MAX(G->get()->total, highest); + } + } + } + } + + if (highest > 0) { + //means some data exists.. + highest *= 1.2; //leave some upper room + graph_height = highest; + + Vector<int> columnv; + columnv.resize(h * 4); + + int *column = columnv.ptrw(); + + Map<StringName, int> plot_prev; + //Map<StringName,int> plot_max; + + for (int i = 0; i < w; i++) { + + for (int j = 0; j < h * 4; j++) { + column[j] = 0; + } + + int current = i * frame_metrics.size() / w; + int next = (i + 1) * frame_metrics.size() / w; + if (next > frame_metrics.size()) { + next = frame_metrics.size(); + } + if (next == current) + next = current + 1; //just because for loop must work + + for (Set<StringName>::Element *E = plot_sigs.front(); E; E = E->next()) { + + int plot_pos = -1; + + for (int j = current; j < next; j++) { + + //wrap + int idx = last_metric + 1 + j; + while (idx >= frame_metrics.size()) { + idx -= frame_metrics.size(); + } + + //get + const Metric &m = frame_metrics[idx]; + if (!m.valid) + continue; //skip because invalid + + float value = 0; + + const Map<StringName, Metric::Category *>::Element *F = m.category_ptrs.find(E->get()); + if (F) { + value = F->get()->total_time; + } + + const Map<StringName, Metric::Category::Item *>::Element *G = m.item_ptrs.find(E->get()); + if (G) { + if (use_self) { + value = G->get()->self; + } else { + value = G->get()->total; + } + } + + plot_pos = MAX(CLAMP(int(value * h / highest), 0, h - 1), plot_pos); + } + + int prev_plot = plot_pos; + Map<StringName, int>::Element *H = plot_prev.find(E->get()); + if (H) { + prev_plot = H->get(); + H->get() = plot_pos; + } else { + plot_prev[E->get()] = plot_pos; + } + + if (plot_pos == -1 && prev_plot == -1) { + //don't bother drawing + continue; + } + + if (prev_plot != -1 && plot_pos == -1) { + + plot_pos = prev_plot; + } + + if (prev_plot == -1 && plot_pos != -1) { + prev_plot = plot_pos; + } + + plot_pos = h - plot_pos - 1; + prev_plot = h - prev_plot - 1; + + if (prev_plot > plot_pos) { + SWAP(prev_plot, plot_pos); + } + + Color col = _get_color_from_signature(E->get()); + + for (int j = prev_plot; j <= plot_pos; j++) { + + column[j * 4 + 0] += Math::fast_ftoi(CLAMP(col.r * 255, 0, 255)); + column[j * 4 + 1] += Math::fast_ftoi(CLAMP(col.g * 255, 0, 255)); + column[j * 4 + 2] += Math::fast_ftoi(CLAMP(col.b * 255, 0, 255)); + column[j * 4 + 3] += 1; + } + } + + for (int j = 0; j < h * 4; j += 4) { + + const int a = column[j + 3]; + if (a > 0) { + column[j + 0] /= a; + column[j + 1] /= a; + column[j + 2] /= a; + } + + const uint8_t red = uint8_t(column[j + 0]); + const uint8_t green = uint8_t(column[j + 1]); + const uint8_t blue = uint8_t(column[j + 2]); + const bool is_filled = red >= 1 || green >= 1 || blue >= 1; + const int widx = ((j >> 2) * w + i) * 4; + + // If the pixel isn't filled by any profiler line, apply the background color instead. + wr[widx + 0] = is_filled ? red : Math::fast_ftoi(background_color.r * 255); + wr[widx + 1] = is_filled ? green : Math::fast_ftoi(background_color.g * 255); + wr[widx + 2] = is_filled ? blue : Math::fast_ftoi(background_color.b * 255); + wr[widx + 3] = 255; + } + } + } + + Ref<Image> img; + img.instance(); + img->create(w, h, 0, Image::FORMAT_RGBA8, graph_image); + + if (reset_texture) { + + if (graph_texture.is_null()) { + graph_texture.instance(); + } + graph_texture->create_from_image(img); + } + + graph_texture->update(img, true); + + graph->set_texture(graph_texture); + graph->update(); +} + +void EditorProfiler::_update_frame() { + + int cursor_metric = _get_cursor_index(); + + ERR_FAIL_INDEX(cursor_metric, frame_metrics.size()); + + updating_frame = true; + variables->clear(); + + TreeItem *root = variables->create_item(); + const Metric &m = frame_metrics[cursor_metric]; + + int dtime = display_time->get_selected(); + + for (int i = 0; i < m.categories.size(); i++) { + + TreeItem *category = variables->create_item(root); + category->set_cell_mode(0, TreeItem::CELL_MODE_CHECK); + category->set_editable(0, true); + category->set_metadata(0, m.categories[i].signature); + category->set_text(0, String(m.categories[i].name)); + category->set_text(1, _get_time_as_text(m, m.categories[i].total_time, 1)); + + if (plot_sigs.has(m.categories[i].signature)) { + category->set_checked(0, true); + category->set_custom_color(0, _get_color_from_signature(m.categories[i].signature)); + } + + for (int j = m.categories[i].items.size() - 1; j >= 0; j--) { + const Metric::Category::Item &it = m.categories[i].items[j]; + + TreeItem *item = variables->create_item(category); + item->set_cell_mode(0, TreeItem::CELL_MODE_CHECK); + item->set_editable(0, true); + item->set_text(0, it.name); + item->set_metadata(0, it.signature); + item->set_metadata(1, it.script); + item->set_metadata(2, it.line); + item->set_text_align(2, TreeItem::ALIGN_RIGHT); + item->set_tooltip(0, it.script + ":" + itos(it.line)); + + float time = dtime == DISPLAY_SELF_TIME ? it.self : it.total; + + item->set_text(1, _get_time_as_text(m, time, it.calls)); + + item->set_text(2, itos(it.calls)); + + if (plot_sigs.has(it.signature)) { + item->set_checked(0, true); + item->set_custom_color(0, _get_color_from_signature(it.signature)); + } + } + } + + updating_frame = false; +} + +void EditorProfiler::_activate_pressed() { + + if (activate->is_pressed()) { + activate->set_icon(get_icon("Stop", "EditorIcons")); + activate->set_text(TTR("Stop")); + } else { + activate->set_icon(get_icon("Play", "EditorIcons")); + activate->set_text(TTR("Start")); + } + emit_signal("enable_profiling", activate->is_pressed()); +} + +void EditorProfiler::_clear_pressed() { + + clear(); + _update_plot(); +} + +void EditorProfiler::_notification(int p_what) { + + if (p_what == NOTIFICATION_ENTER_TREE) { + activate->set_icon(get_icon("Play", "EditorIcons")); + clear_button->set_icon(get_icon("Clear", "EditorIcons")); + } +} + +void EditorProfiler::_graph_tex_draw() { + + if (last_metric < 0) + return; + if (seeking) { + + int max_frames = frame_metrics.size(); + int frame = cursor_metric_edit->get_value() - (frame_metrics[last_metric].frame_number - max_frames + 1); + if (frame < 0) + frame = 0; + + int cur_x = frame * graph->get_size().x / max_frames; + + graph->draw_line(Vector2(cur_x, 0), Vector2(cur_x, graph->get_size().y), Color(1, 1, 1, 0.8)); + } + + if (hover_metric != -1 && frame_metrics[hover_metric].valid) { + + int max_frames = frame_metrics.size(); + int frame = frame_metrics[hover_metric].frame_number - (frame_metrics[last_metric].frame_number - max_frames + 1); + if (frame < 0) + frame = 0; + + int cur_x = frame * graph->get_size().x / max_frames; + + graph->draw_line(Vector2(cur_x, 0), Vector2(cur_x, graph->get_size().y), Color(1, 1, 1, 0.4)); + } +} + +void EditorProfiler::_graph_tex_mouse_exit() { + + hover_metric = -1; + graph->update(); +} + +void EditorProfiler::_cursor_metric_changed(double) { + if (updating_frame) + return; + + graph->update(); + _update_frame(); +} + +void EditorProfiler::_graph_tex_input(const Ref<InputEvent> &p_ev) { + + if (last_metric < 0) + return; + + Ref<InputEventMouse> me = p_ev; + Ref<InputEventMouseButton> mb = p_ev; + Ref<InputEventMouseMotion> mm = p_ev; + + if ( + (mb.is_valid() && mb->get_button_index() == BUTTON_LEFT && mb->is_pressed()) || + (mm.is_valid())) { + + int x = me->get_position().x; + x = x * frame_metrics.size() / graph->get_size().width; + + bool show_hover = x >= 0 && x < frame_metrics.size(); + + if (x < 0) { + x = 0; + } + + if (x >= frame_metrics.size()) { + x = frame_metrics.size() - 1; + } + + int metric = frame_metrics.size() - x - 1; + metric = last_metric - metric; + while (metric < 0) { + metric += frame_metrics.size(); + } + + if (show_hover) { + + hover_metric = metric; + + } else { + hover_metric = -1; + } + + if (mb.is_valid() || mm->get_button_mask() & BUTTON_MASK_LEFT) { + //cursor_metric=x; + updating_frame = true; + + //metric may be invalid, so look for closest metric that is valid, this makes snap feel better + bool valid = false; + for (int i = 0; i < frame_metrics.size(); i++) { + + if (frame_metrics[metric].valid) { + valid = true; + break; + } + + metric++; + if (metric >= frame_metrics.size()) + metric = 0; + } + + if (valid) + cursor_metric_edit->set_value(frame_metrics[metric].frame_number); + + updating_frame = false; + + if (activate->is_pressed()) { + if (!seeking) { + emit_signal("break_request"); + } + } + + seeking = true; + + if (!frame_delay->is_processing()) { + frame_delay->set_wait_time(0.1); + frame_delay->start(); + } + } + + graph->update(); + } +} + +int EditorProfiler::_get_cursor_index() const { + + if (last_metric < 0) + return 0; + if (!frame_metrics[last_metric].valid) + return 0; + + int diff = (frame_metrics[last_metric].frame_number - cursor_metric_edit->get_value()); + + int idx = last_metric - diff; + while (idx < 0) { + idx += frame_metrics.size(); + } + + return idx; +} + +void EditorProfiler::disable_seeking() { + + seeking = false; + graph->update(); +} + +void EditorProfiler::_combo_changed(int) { + + _update_frame(); + _update_plot(); +} + +void EditorProfiler::_bind_methods() { + + ADD_SIGNAL(MethodInfo("enable_profiling", PropertyInfo(Variant::BOOL, "enable"))); + ADD_SIGNAL(MethodInfo("break_request")); +} + +void EditorProfiler::set_enabled(bool p_enable) { + + activate->set_disabled(!p_enable); +} + +bool EditorProfiler::is_profiling() { + return activate->is_pressed(); +} + +Vector<Vector<String> > EditorProfiler::get_data_as_csv() const { + Vector<Vector<String> > res; + + if (frame_metrics.empty()) { + return res; + } + + // signatures + Vector<String> signatures; + const Vector<EditorProfiler::Metric::Category> &categories = frame_metrics[0].categories; + + for (int j = 0; j < categories.size(); j++) { + + const EditorProfiler::Metric::Category &c = categories[j]; + signatures.push_back(c.signature); + + for (int k = 0; k < c.items.size(); k++) { + signatures.push_back(c.items[k].signature); + } + } + res.push_back(signatures); + + // values + Vector<String> values; + values.resize(signatures.size()); + + int index = last_metric; + + for (int i = 0; i < frame_metrics.size(); i++) { + + ++index; + + if (index >= frame_metrics.size()) { + index = 0; + } + + if (!frame_metrics[index].valid) { + continue; + } + int it = 0; + const Vector<EditorProfiler::Metric::Category> &frame_cat = frame_metrics[index].categories; + + for (int j = 0; j < frame_cat.size(); j++) { + + const EditorProfiler::Metric::Category &c = frame_cat[j]; + values.write[it++] = String::num_real(c.total_time); + + for (int k = 0; k < c.items.size(); k++) { + values.write[it++] = String::num_real(c.items[k].total); + } + } + res.push_back(values); + } + + return res; +} + +EditorProfiler::EditorProfiler() { + + HBoxContainer *hb = memnew(HBoxContainer); + add_child(hb); + activate = memnew(Button); + activate->set_toggle_mode(true); + activate->set_text(TTR("Start")); + activate->connect("pressed", callable_mp(this, &EditorProfiler::_activate_pressed)); + hb->add_child(activate); + + clear_button = memnew(Button); + clear_button->set_text(TTR("Clear")); + clear_button->connect("pressed", callable_mp(this, &EditorProfiler::_clear_pressed)); + hb->add_child(clear_button); + + hb->add_child(memnew(Label(TTR("Measure:")))); + + display_mode = memnew(OptionButton); + display_mode->add_item(TTR("Frame Time (sec)")); + display_mode->add_item(TTR("Average Time (sec)")); + display_mode->add_item(TTR("Frame %")); + display_mode->add_item(TTR("Physics Frame %")); + display_mode->connect("item_selected", callable_mp(this, &EditorProfiler::_combo_changed)); + + hb->add_child(display_mode); + + hb->add_child(memnew(Label(TTR("Time:")))); + + display_time = memnew(OptionButton); + display_time->add_item(TTR("Inclusive")); + display_time->add_item(TTR("Self")); + display_time->connect("item_selected", callable_mp(this, &EditorProfiler::_combo_changed)); + + hb->add_child(display_time); + + hb->add_spacer(); + + hb->add_child(memnew(Label(TTR("Frame #:")))); + + cursor_metric_edit = memnew(SpinBox); + cursor_metric_edit->set_h_size_flags(SIZE_FILL); + hb->add_child(cursor_metric_edit); + cursor_metric_edit->connect("value_changed", callable_mp(this, &EditorProfiler::_cursor_metric_changed)); + + hb->add_constant_override("separation", 8 * EDSCALE); + + h_split = memnew(HSplitContainer); + add_child(h_split); + h_split->set_v_size_flags(SIZE_EXPAND_FILL); + + variables = memnew(Tree); + variables->set_custom_minimum_size(Size2(320, 0) * EDSCALE); + variables->set_hide_folding(true); + h_split->add_child(variables); + variables->set_hide_root(true); + variables->set_columns(3); + variables->set_column_titles_visible(true); + variables->set_column_title(0, TTR("Name")); + variables->set_column_expand(0, true); + variables->set_column_min_width(0, 60 * EDSCALE); + variables->set_column_title(1, TTR("Time")); + variables->set_column_expand(1, false); + variables->set_column_min_width(1, 100 * EDSCALE); + variables->set_column_title(2, TTR("Calls")); + variables->set_column_expand(2, false); + variables->set_column_min_width(2, 60 * EDSCALE); + variables->connect("item_edited", callable_mp(this, &EditorProfiler::_item_edited)); + + graph = memnew(TextureRect); + graph->set_expand(true); + graph->set_mouse_filter(MOUSE_FILTER_STOP); + graph->connect("draw", callable_mp(this, &EditorProfiler::_graph_tex_draw)); + graph->connect("gui_input", callable_mp(this, &EditorProfiler::_graph_tex_input)); + graph->connect("mouse_exited", callable_mp(this, &EditorProfiler::_graph_tex_mouse_exit)); + + h_split->add_child(graph); + graph->set_h_size_flags(SIZE_EXPAND_FILL); + + int metric_size = CLAMP(int(EDITOR_DEF("debugger/profiler_frame_history_size", 600)), 60, 1024); + frame_metrics.resize(metric_size); + last_metric = -1; + hover_metric = -1; + + EDITOR_DEF("debugger/profiler_frame_max_functions", 64); + + frame_delay = memnew(Timer); + frame_delay->set_wait_time(0.1); + frame_delay->set_one_shot(true); + add_child(frame_delay); + frame_delay->connect("timeout", callable_mp(this, &EditorProfiler::_update_frame)); + + plot_delay = memnew(Timer); + plot_delay->set_wait_time(0.1); + plot_delay->set_one_shot(true); + add_child(plot_delay); + plot_delay->connect("timeout", callable_mp(this, &EditorProfiler::_update_plot)); + + plot_sigs.insert("physics_frame_time"); + plot_sigs.insert("category_frame_time"); + + seeking = false; + graph_height = 1; +} diff --git a/editor/debugger/editor_profiler.h b/editor/debugger/editor_profiler.h new file mode 100644 index 0000000000..0a442ddd5c --- /dev/null +++ b/editor/debugger/editor_profiler.h @@ -0,0 +1,177 @@ +/*************************************************************************/ +/* editor_profiler.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef EDITORPROFILER_H +#define EDITORPROFILER_H + +#include "scene/gui/box_container.h" +#include "scene/gui/button.h" +#include "scene/gui/label.h" +#include "scene/gui/option_button.h" +#include "scene/gui/spin_box.h" +#include "scene/gui/split_container.h" +#include "scene/gui/texture_rect.h" +#include "scene/gui/tree.h" + +class EditorProfiler : public VBoxContainer { + + GDCLASS(EditorProfiler, VBoxContainer); + +public: + struct Metric { + + bool valid; + + int frame_number; + float frame_time; + float idle_time; + float physics_time; + float physics_frame_time; + + struct Category { + + StringName signature; + String name; + float total_time; //total for category + + struct Item { + + StringName signature; + String name; + String script; + int line; + float self; + float total; + int calls; + }; + + Vector<Item> items; + }; + + Vector<Category> categories; + + Map<StringName, Category *> category_ptrs; + Map<StringName, Category::Item *> item_ptrs; + + Metric() { + valid = false; + frame_number = 0; + } + }; + + enum DisplayMode { + DISPLAY_FRAME_TIME, + DISPLAY_AVERAGE_TIME, + DISPLAY_FRAME_PERCENT, + DISPLAY_PHYSICS_FRAME_PERCENT, + }; + + enum DisplayTime { + DISPLAY_TOTAL_TIME, + DISPLAY_SELF_TIME, + }; + +private: + Button *activate; + Button *clear_button; + TextureRect *graph; + Ref<ImageTexture> graph_texture; + Vector<uint8_t> graph_image; + Tree *variables; + HSplitContainer *h_split; + + Set<StringName> plot_sigs; + + OptionButton *display_mode; + OptionButton *display_time; + + SpinBox *cursor_metric_edit; + + Vector<Metric> frame_metrics; + int last_metric; + + int max_functions; + + bool updating_frame; + + //int cursor_metric; + int hover_metric; + + float graph_height; + + bool seeking; + + Timer *frame_delay; + Timer *plot_delay; + + void _update_frame(); + + void _activate_pressed(); + void _clear_pressed(); + + String _get_time_as_text(const Metric &m, float p_time, int p_calls); + + void _make_metric_ptrs(Metric &m); + void _item_edited(); + + void _update_plot(); + + void _graph_tex_mouse_exit(); + + void _graph_tex_draw(); + void _graph_tex_input(const Ref<InputEvent> &p_ev); + + int _get_cursor_index() const; + + Color _get_color_from_signature(const StringName &p_signature) const; + + void _cursor_metric_changed(double); + + void _combo_changed(int); + +protected: + void _notification(int p_what); + static void _bind_methods(); + +public: + void add_frame_metric(const Metric &p_metric, bool p_final = false); + void set_enabled(bool p_enable); + bool is_profiling(); + bool is_seeking() { return seeking; } + void disable_seeking(); + + void clear(); + + Vector<Vector<String> > get_data_as_csv() const; + + EditorProfiler(); +}; + +#endif // EDITORPROFILER_H diff --git a/editor/debugger/editor_visual_profiler.cpp b/editor/debugger/editor_visual_profiler.cpp new file mode 100644 index 0000000000..52aa418922 --- /dev/null +++ b/editor/debugger/editor_visual_profiler.cpp @@ -0,0 +1,842 @@ +/*************************************************************************/ +/* editor_visual_profiler.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "editor_visual_profiler.h" + +#include "core/os/os.h" +#include "editor/editor_scale.h" +#include "editor/editor_settings.h" + +void EditorVisualProfiler::add_frame_metric(const Metric &p_metric) { + + ++last_metric; + if (last_metric >= frame_metrics.size()) + last_metric = 0; + + frame_metrics.write[last_metric] = p_metric; + // _make_metric_ptrs(frame_metrics.write[last_metric]); + + List<String> stack; + for (int i = 0; i < frame_metrics[last_metric].areas.size(); i++) { + String name = frame_metrics[last_metric].areas[i].name; + frame_metrics.write[last_metric].areas.write[i].color_cache = _get_color_from_signature(name); + String full_name; + + if (name[0] == '<') { + stack.pop_back(); + } + + if (stack.size()) { + full_name = stack.back()->get() + name; + } else { + full_name = name; + } + + if (name[0] == '>') { + + stack.push_back(full_name + "/"); + } + + frame_metrics.write[last_metric].areas.write[i].fullpath_cache = full_name; + } + + updating_frame = true; + cursor_metric_edit->set_max(frame_metrics[last_metric].frame_number); + cursor_metric_edit->set_min(MAX(frame_metrics[last_metric].frame_number - frame_metrics.size(), 0)); + + if (!seeking) { + cursor_metric_edit->set_value(frame_metrics[last_metric].frame_number); + if (hover_metric != -1) { + hover_metric++; + if (hover_metric >= frame_metrics.size()) { + hover_metric = 0; + } + } + } + updating_frame = false; + + if (frame_delay->is_stopped()) { + + frame_delay->set_wait_time(0.1); + frame_delay->start(); + } + + if (plot_delay->is_stopped()) { + plot_delay->set_wait_time(0.1); + plot_delay->start(); + } +} + +void EditorVisualProfiler::clear() { + + int metric_size = EditorSettings::get_singleton()->get("debugger/profiler_frame_history_size"); + metric_size = CLAMP(metric_size, 60, 1024); + frame_metrics.clear(); + frame_metrics.resize(metric_size); + last_metric = -1; + variables->clear(); + //activate->set_pressed(false); + + updating_frame = true; + cursor_metric_edit->set_min(0); + cursor_metric_edit->set_max(0); + cursor_metric_edit->set_value(0); + updating_frame = false; + hover_metric = -1; + seeking = false; +} + +String EditorVisualProfiler::_get_time_as_text(float p_time) { + + int dmode = display_mode->get_selected(); + + if (dmode == DISPLAY_FRAME_TIME) { + return rtos(p_time) + "ms"; + } else if (dmode == DISPLAY_FRAME_PERCENT) { + return String::num(p_time * 100 / graph_limit, 2) + "%"; + } + + return "err"; +} + +Color EditorVisualProfiler::_get_color_from_signature(const StringName &p_signature) const { + + Color bc = get_color("error_color", "Editor"); + double rot = ABS(double(p_signature.hash()) / double(0x7FFFFFFF)); + Color c; + c.set_hsv(rot, bc.get_s(), bc.get_v()); + return c.linear_interpolate(get_color("base_color", "Editor"), 0.07); +} + +void EditorVisualProfiler::_item_selected() { + + if (updating_frame) + return; + + TreeItem *item = variables->get_selected(); + if (!item) + return; + selected_area = item->get_metadata(0); + _update_plot(); +} + +void EditorVisualProfiler::_update_plot() { + + int w = graph->get_size().width; + int h = graph->get_size().height; + + bool reset_texture = false; + + int desired_len = w * h * 4; + + if (graph_image.size() != desired_len) { + reset_texture = true; + graph_image.resize(desired_len); + } + + uint8_t *wr = graph_image.ptrw(); + + //clear + for (int i = 0; i < desired_len; i += 4) { + wr[i + 0] = 0; + wr[i + 1] = 0; + wr[i + 2] = 0; + wr[i + 3] = 255; + } + + //find highest value + + float highest_cpu = 0; + float highest_gpu = 0; + + for (int i = 0; i < frame_metrics.size(); i++) { + const Metric &m = frame_metrics[i]; + if (!m.valid) + continue; + + if (m.areas.size()) { + highest_cpu = MAX(highest_cpu, m.areas[m.areas.size() - 1].cpu_time); + highest_gpu = MAX(highest_gpu, m.areas[m.areas.size() - 1].gpu_time); + } + } + + if (highest_cpu > 0 || highest_gpu > 0) { + + if (frame_relative->is_pressed()) { + highest_cpu = MAX(graph_limit, highest_cpu); + highest_gpu = MAX(graph_limit, highest_gpu); + } + + if (linked->is_pressed()) { + float highest = MAX(highest_cpu, highest_gpu); + highest_cpu = highest_gpu = highest; + } + + //means some data exists.. + highest_cpu *= 1.2; //leave some upper room + highest_gpu *= 1.2; //leave some upper room + graph_height_cpu = highest_cpu; + graph_height_gpu = highest_gpu; + + Vector<Color> columnv_cpu; + columnv_cpu.resize(h); + Color *column_cpu = columnv_cpu.ptrw(); + + Vector<Color> columnv_gpu; + columnv_gpu.resize(h); + Color *column_gpu = columnv_gpu.ptrw(); + + int half_w = w / 2; + for (int i = 0; i < half_w; i++) { + for (int j = 0; j < h; j++) { + column_cpu[j] = Color(0, 0, 0, 0); + column_gpu[j] = Color(0, 0, 0, 0); + } + + int current = i * frame_metrics.size() / half_w; + int next = (i + 1) * frame_metrics.size() / half_w; + if (next > frame_metrics.size()) { + next = frame_metrics.size(); + } + if (next == current) + next = current + 1; //just because for loop must work + + for (int j = current; j < next; j++) { + + //wrap + int idx = last_metric + 1 + j; + while (idx >= frame_metrics.size()) { + idx -= frame_metrics.size(); + } + + int area_count = frame_metrics[idx].areas.size(); + const Metric::Area *areas = frame_metrics[idx].areas.ptr(); + int prev_cpu = 0; + int prev_gpu = 0; + for (int k = 1; k < area_count; k++) { + int ofs_cpu = int(areas[k].cpu_time * h / highest_cpu); + ofs_cpu = CLAMP(ofs_cpu, 0, h - 1); + Color color = selected_area == areas[k - 1].fullpath_cache ? Color(1, 1, 1, 1) : areas[k - 1].color_cache; + + for (int l = prev_cpu; l < ofs_cpu; l++) { + column_cpu[h - l - 1] += color; + } + prev_cpu = ofs_cpu; + + int ofs_gpu = int(areas[k].gpu_time * h / highest_gpu); + ofs_gpu = CLAMP(ofs_gpu, 0, h - 1); + for (int l = prev_gpu; l < ofs_gpu; l++) { + column_gpu[h - l - 1] += color; + } + + prev_gpu = ofs_gpu; + } + } + + //plot CPU + for (int j = 0; j < h; j++) { + + uint8_t r, g, b; + + if (column_cpu[j].a == 0) { + r = 0; + g = 0; + b = 0; + } else { + r = CLAMP((column_cpu[j].r / column_cpu[j].a) * 255.0, 0, 255); + g = CLAMP((column_cpu[j].g / column_cpu[j].a) * 255.0, 0, 255); + b = CLAMP((column_cpu[j].b / column_cpu[j].a) * 255.0, 0, 255); + } + + int widx = (j * w + i) * 4; + wr[widx + 0] = r; + wr[widx + 1] = g; + wr[widx + 2] = b; + wr[widx + 3] = 255; + } + //plot GPU + for (int j = 0; j < h; j++) { + + uint8_t r, g, b; + + if (column_gpu[j].a == 0) { + r = 0; + g = 0; + b = 0; + } else { + r = CLAMP((column_gpu[j].r / column_gpu[j].a) * 255.0, 0, 255); + g = CLAMP((column_gpu[j].g / column_gpu[j].a) * 255.0, 0, 255); + b = CLAMP((column_gpu[j].b / column_gpu[j].a) * 255.0, 0, 255); + } + + int widx = (j * w + w / 2 + i) * 4; + wr[widx + 0] = r; + wr[widx + 1] = g; + wr[widx + 2] = b; + wr[widx + 3] = 255; + } + } + } + + Ref<Image> img; + img.instance(); + img->create(w, h, 0, Image::FORMAT_RGBA8, graph_image); + + if (reset_texture) { + + if (graph_texture.is_null()) { + graph_texture.instance(); + } + graph_texture->create_from_image(img); + } + + graph_texture->update(img, true); + + graph->set_texture(graph_texture); + graph->update(); +} + +void EditorVisualProfiler::_update_frame(bool p_focus_selected) { + + int cursor_metric = _get_cursor_index(); + + Ref<Texture> track_icon = get_icon("TrackColor", "EditorIcons"); + + ERR_FAIL_INDEX(cursor_metric, frame_metrics.size()); + + updating_frame = true; + variables->clear(); + + TreeItem *root = variables->create_item(); + const Metric &m = frame_metrics[cursor_metric]; + + List<TreeItem *> stack; + List<TreeItem *> categories; + + TreeItem *ensure_selected = nullptr; + + for (int i = 1; i < m.areas.size() - 1; i++) { + + TreeItem *parent = stack.size() ? stack.back()->get() : root; + + String name = m.areas[i].name; + + float cpu_time = m.areas[i].cpu_time; + float gpu_time = m.areas[i].gpu_time; + if (i < m.areas.size() - 1) { + cpu_time = m.areas[i + 1].cpu_time - cpu_time; + gpu_time = m.areas[i + 1].gpu_time - gpu_time; + } + + if (name.begins_with(">")) { + TreeItem *category = variables->create_item(parent); + + stack.push_back(category); + categories.push_back(category); + + name = name.substr(1, name.length()); + + category->set_text(0, name); + category->set_metadata(1, cpu_time); + category->set_metadata(2, gpu_time); + continue; + } + + if (name.begins_with("<")) { + stack.pop_back(); + continue; + } + TreeItem *category = variables->create_item(parent); + + for (List<TreeItem *>::Element *E = stack.front(); E; E = E->next()) { + float total_cpu = E->get()->get_metadata(1); + float total_gpu = E->get()->get_metadata(2); + total_cpu += cpu_time; + total_gpu += gpu_time; + E->get()->set_metadata(1, cpu_time); + E->get()->set_metadata(2, gpu_time); + } + + category->set_icon(0, track_icon); + category->set_icon_modulate(0, m.areas[i].color_cache); + category->set_selectable(0, true); + category->set_metadata(0, m.areas[i].fullpath_cache); + category->set_text(0, m.areas[i].name); + category->set_text(1, _get_time_as_text(cpu_time)); + category->set_metadata(1, m.areas[i].cpu_time); + category->set_text(2, _get_time_as_text(gpu_time)); + category->set_metadata(2, m.areas[i].gpu_time); + + if (selected_area == m.areas[i].fullpath_cache) { + category->select(0); + if (p_focus_selected) { + ensure_selected = category; + } + } + } + + for (List<TreeItem *>::Element *E = categories.front(); E; E = E->next()) { + float total_cpu = E->get()->get_metadata(1); + float total_gpu = E->get()->get_metadata(2); + E->get()->set_text(1, _get_time_as_text(total_cpu)); + E->get()->set_text(2, _get_time_as_text(total_gpu)); + } + + if (ensure_selected) { + variables->ensure_cursor_is_visible(); + } + updating_frame = false; +} + +void EditorVisualProfiler::_activate_pressed() { + + if (activate->is_pressed()) { + activate->set_icon(get_icon("Stop", "EditorIcons")); + activate->set_text(TTR("Stop")); + _clear_pressed(); //always clear on start + } else { + activate->set_icon(get_icon("Play", "EditorIcons")); + activate->set_text(TTR("Start")); + } + emit_signal("enable_profiling", activate->is_pressed()); +} + +void EditorVisualProfiler::_clear_pressed() { + + clear(); + _update_plot(); +} + +void EditorVisualProfiler::_notification(int p_what) { + + if (p_what == NOTIFICATION_ENTER_TREE) { + activate->set_icon(get_icon("Play", "EditorIcons")); + clear_button->set_icon(get_icon("Clear", "EditorIcons")); + } +} + +void EditorVisualProfiler::_graph_tex_draw() { + + if (last_metric < 0) + return; + Ref<Font> font = get_font("font", "Label"); + if (seeking) { + + int max_frames = frame_metrics.size(); + int frame = cursor_metric_edit->get_value() - (frame_metrics[last_metric].frame_number - max_frames + 1); + if (frame < 0) + frame = 0; + + int half_width = graph->get_size().x / 2; + int cur_x = frame * half_width / max_frames; + //cur_x /= 2.0; + + graph->draw_line(Vector2(cur_x, 0), Vector2(cur_x, graph->get_size().y), Color(1, 1, 1, 0.8)); + graph->draw_line(Vector2(cur_x + half_width, 0), Vector2(cur_x + half_width, graph->get_size().y), Color(1, 1, 1, 0.8)); + } + + if (graph_height_cpu > 0) { + int frame_y = graph->get_size().y - graph_limit * graph->get_size().y / graph_height_cpu - 1; + + int half_width = graph->get_size().x / 2; + + graph->draw_line(Vector2(0, frame_y), Vector2(half_width, frame_y), Color(1, 1, 1, 0.3)); + + String limit_str = String::num(graph_limit, 2); + graph->draw_string(font, Vector2(half_width - font->get_string_size(limit_str).x - 2, frame_y - 2), limit_str, Color(1, 1, 1, 0.6)); + } + + if (graph_height_gpu > 0) { + int frame_y = graph->get_size().y - graph_limit * graph->get_size().y / graph_height_gpu - 1; + + int half_width = graph->get_size().x / 2; + + graph->draw_line(Vector2(half_width, frame_y), Vector2(graph->get_size().x, frame_y), Color(1, 1, 1, 0.3)); + + String limit_str = String::num(graph_limit, 2); + graph->draw_string(font, Vector2(half_width * 2 - font->get_string_size(limit_str).x - 2, frame_y - 2), limit_str, Color(1, 1, 1, 0.6)); + } + + graph->draw_string(font, Vector2(font->get_string_size("X").x, font->get_ascent() + 2), "CPU:", Color(1, 1, 1, 0.8)); + graph->draw_string(font, Vector2(font->get_string_size("X").x + graph->get_size().width / 2, font->get_ascent() + 2), "GPU:", Color(1, 1, 1, 0.8)); + + /* + if (hover_metric != -1 && frame_metrics[hover_metric].valid) { + + int max_frames = frame_metrics.size(); + int frame = frame_metrics[hover_metric].frame_number - (frame_metrics[last_metric].frame_number - max_frames + 1); + if (frame < 0) + frame = 0; + + int cur_x = frame * graph->get_size().x / max_frames; + + graph->draw_line(Vector2(cur_x, 0), Vector2(cur_x, graph->get_size().y), Color(1, 1, 1, 0.4)); + } +*/ +} + +void EditorVisualProfiler::_graph_tex_mouse_exit() { + + hover_metric = -1; + graph->update(); +} + +void EditorVisualProfiler::_cursor_metric_changed(double) { + if (updating_frame) + return; + + graph->update(); + _update_frame(); +} + +void EditorVisualProfiler::_graph_tex_input(const Ref<InputEvent> &p_ev) { + + if (last_metric < 0) + return; + + Ref<InputEventMouse> me = p_ev; + Ref<InputEventMouseButton> mb = p_ev; + Ref<InputEventMouseMotion> mm = p_ev; + + if ( + (mb.is_valid() && mb->get_button_index() == BUTTON_LEFT && mb->is_pressed()) || + (mm.is_valid())) { + + int half_w = graph->get_size().width / 2; + int x = me->get_position().x; + if (x > half_w) { + x -= half_w; + } + x = x * frame_metrics.size() / half_w; + + bool show_hover = x >= 0 && x < frame_metrics.size(); + + if (x < 0) { + x = 0; + } + + if (x >= frame_metrics.size()) { + x = frame_metrics.size() - 1; + } + + int metric = frame_metrics.size() - x - 1; + metric = last_metric - metric; + while (metric < 0) { + metric += frame_metrics.size(); + } + + if (show_hover) { + + hover_metric = metric; + + } else { + hover_metric = -1; + } + + if (mb.is_valid() || mm->get_button_mask() & BUTTON_MASK_LEFT) { + //cursor_metric=x; + updating_frame = true; + + //metric may be invalid, so look for closest metric that is valid, this makes snap feel better + bool valid = false; + for (int i = 0; i < frame_metrics.size(); i++) { + + if (frame_metrics[metric].valid) { + valid = true; + break; + } + + metric++; + if (metric >= frame_metrics.size()) + metric = 0; + } + + if (!valid) { + return; + } + + cursor_metric_edit->set_value(frame_metrics[metric].frame_number); + + updating_frame = false; + + if (activate->is_pressed()) { + if (!seeking) { + // Break request is not required, just stop profiling + } + } + + seeking = true; + + if (!frame_delay->is_processing()) { + frame_delay->set_wait_time(0.1); + frame_delay->start(); + } + + bool touched_cpu = me->get_position().x < graph->get_size().width * 0.5; + + const Metric::Area *areas = frame_metrics[metric].areas.ptr(); + int area_count = frame_metrics[metric].areas.size(); + float posy = (1.0 - (me->get_position().y / graph->get_size().height)) * (touched_cpu ? graph_height_cpu : graph_height_gpu); + int last_valid = -1; + bool found = false; + for (int i = 0; i < area_count - 1; i++) { + + if (areas[i].name[0] != '<' && areas[i].name[0] != '>') { + last_valid = i; + } + float h = touched_cpu ? areas[i + 1].cpu_time : areas[i + 1].gpu_time; + + if (h > posy) { + found = true; + break; + } + } + + StringName area_found; + if (found && last_valid != -1) { + area_found = areas[last_valid].fullpath_cache; + } + + if (area_found != selected_area) { + selected_area = area_found; + _update_frame(true); + _update_plot(); + } + } + + graph->update(); + } +} + +int EditorVisualProfiler::_get_cursor_index() const { + + if (last_metric < 0) + return 0; + if (!frame_metrics[last_metric].valid) + return 0; + + int diff = (frame_metrics[last_metric].frame_number - cursor_metric_edit->get_value()); + + int idx = last_metric - diff; + while (idx < 0) { + idx += frame_metrics.size(); + } + + return idx; +} + +void EditorVisualProfiler::disable_seeking() { + + seeking = false; + graph->update(); +} + +void EditorVisualProfiler::_combo_changed(int) { + + _update_frame(); + _update_plot(); +} + +void EditorVisualProfiler::_bind_methods() { + + ADD_SIGNAL(MethodInfo("enable_profiling", PropertyInfo(Variant::BOOL, "enable"))); +} + +void EditorVisualProfiler::set_enabled(bool p_enable) { + + activate->set_disabled(!p_enable); +} + +bool EditorVisualProfiler::is_profiling() { + return activate->is_pressed(); +} + +Vector<Vector<String> > EditorVisualProfiler::get_data_as_csv() const { + Vector<Vector<String> > res; +#if 0 + if (frame_metrics.empty()) { + return res; + } + + // signatures + Vector<String> signatures; + const Vector<EditorFrameProfiler::Metric::Category> &categories = frame_metrics[0].categories; + + for (int j = 0; j < categories.size(); j++) { + + const EditorFrameProfiler::Metric::Category &c = categories[j]; + signatures.push_back(c.signature); + + for (int k = 0; k < c.items.size(); k++) { + signatures.push_back(c.items[k].signature); + } + } + res.push_back(signatures); + + // values + Vector<String> values; + values.resize(signatures.size()); + + int index = last_metric; + + for (int i = 0; i < frame_metrics.size(); i++) { + + ++index; + + if (index >= frame_metrics.size()) { + index = 0; + } + + if (!frame_metrics[index].valid) { + continue; + } + int it = 0; + const Vector<EditorFrameProfiler::Metric::Category> &frame_cat = frame_metrics[index].categories; + + for (int j = 0; j < frame_cat.size(); j++) { + + const EditorFrameProfiler::Metric::Category &c = frame_cat[j]; + values.write[it++] = String::num_real(c.total_time); + + for (int k = 0; k < c.items.size(); k++) { + values.write[it++] = String::num_real(c.items[k].total); + } + } + res.push_back(values); + } +#endif + return res; +} + +EditorVisualProfiler::EditorVisualProfiler() { + + HBoxContainer *hb = memnew(HBoxContainer); + add_child(hb); + activate = memnew(Button); + activate->set_toggle_mode(true); + activate->set_text(TTR("Start")); + activate->connect("pressed", callable_mp(this, &EditorVisualProfiler::_activate_pressed)); + hb->add_child(activate); + + clear_button = memnew(Button); + clear_button->set_text(TTR("Clear")); + clear_button->connect("pressed", callable_mp(this, &EditorVisualProfiler::_clear_pressed)); + hb->add_child(clear_button); + + hb->add_child(memnew(Label(TTR("Measure:")))); + + display_mode = memnew(OptionButton); + display_mode->add_item(TTR("Frame Time (msec)")); + display_mode->add_item(TTR("Frame %")); + display_mode->connect("item_selected", callable_mp(this, &EditorVisualProfiler::_combo_changed)); + + hb->add_child(display_mode); + + frame_relative = memnew(CheckBox(TTR("Fit to Frame"))); + frame_relative->set_pressed(true); + hb->add_child(frame_relative); + frame_relative->connect("pressed", callable_mp(this, &EditorVisualProfiler::_update_plot)); + linked = memnew(CheckBox(TTR("Linked"))); + linked->set_pressed(true); + hb->add_child(linked); + linked->connect("pressed", callable_mp(this, &EditorVisualProfiler::_update_plot)); + + hb->add_spacer(); + + hb->add_child(memnew(Label(TTR("Frame #:")))); + + cursor_metric_edit = memnew(SpinBox); + cursor_metric_edit->set_h_size_flags(SIZE_FILL); + hb->add_child(cursor_metric_edit); + cursor_metric_edit->connect("value_changed", callable_mp(this, &EditorVisualProfiler::_cursor_metric_changed)); + + hb->add_constant_override("separation", 8 * EDSCALE); + + h_split = memnew(HSplitContainer); + add_child(h_split); + h_split->set_v_size_flags(SIZE_EXPAND_FILL); + + variables = memnew(Tree); + variables->set_custom_minimum_size(Size2(300, 0) * EDSCALE); + variables->set_hide_folding(true); + h_split->add_child(variables); + variables->set_hide_root(true); + variables->set_columns(3); + variables->set_column_titles_visible(true); + variables->set_column_title(0, TTR("Name")); + variables->set_column_expand(0, true); + variables->set_column_min_width(0, 60); + variables->set_column_title(1, TTR("CPU")); + variables->set_column_expand(1, false); + variables->set_column_min_width(1, 60 * EDSCALE); + variables->set_column_title(2, TTR("GPU")); + variables->set_column_expand(2, false); + variables->set_column_min_width(2, 60 * EDSCALE); + variables->connect("cell_selected", callable_mp(this, &EditorVisualProfiler::_item_selected)); + + graph = memnew(TextureRect); + graph->set_expand(true); + graph->set_mouse_filter(MOUSE_FILTER_STOP); + //graph->set_ignore_mouse(false); + graph->connect("draw", callable_mp(this, &EditorVisualProfiler::_graph_tex_draw)); + graph->connect("gui_input", callable_mp(this, &EditorVisualProfiler::_graph_tex_input)); + graph->connect("mouse_exited", callable_mp(this, &EditorVisualProfiler::_graph_tex_mouse_exit)); + + h_split->add_child(graph); + graph->set_h_size_flags(SIZE_EXPAND_FILL); + + int metric_size = CLAMP(int(EDITOR_DEF("debugger/profiler_frame_history_size", 600)), 60, 1024); + frame_metrics.resize(metric_size); + last_metric = -1; + //cursor_metric=-1; + hover_metric = -1; + + //display_mode=DISPLAY_FRAME_TIME; + + frame_delay = memnew(Timer); + frame_delay->set_wait_time(0.1); + frame_delay->set_one_shot(true); + add_child(frame_delay); + frame_delay->connect("timeout", callable_mp(this, &EditorVisualProfiler::_update_frame), make_binds(false)); + + plot_delay = memnew(Timer); + plot_delay->set_wait_time(0.1); + plot_delay->set_one_shot(true); + add_child(plot_delay); + plot_delay->connect("timeout", callable_mp(this, &EditorVisualProfiler::_update_plot)); + + seeking = false; + graph_height_cpu = 1; + graph_height_gpu = 1; + + graph_limit = 1000 / 60.0; + + //activate->set_disabled(true); +} diff --git a/editor/debugger/editor_visual_profiler.h b/editor/debugger/editor_visual_profiler.h new file mode 100644 index 0000000000..5194c08b96 --- /dev/null +++ b/editor/debugger/editor_visual_profiler.h @@ -0,0 +1,154 @@ +/*************************************************************************/ +/* editor_visual_profiler.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef EDITOR_FRAME_PROFILER_H +#define EDITOR_FRAME_PROFILER_H + +#include "scene/gui/box_container.h" +#include "scene/gui/button.h" +#include "scene/gui/check_box.h" +#include "scene/gui/label.h" +#include "scene/gui/option_button.h" +#include "scene/gui/spin_box.h" +#include "scene/gui/split_container.h" +#include "scene/gui/texture_rect.h" +#include "scene/gui/tree.h" + +class EditorVisualProfiler : public VBoxContainer { + + GDCLASS(EditorVisualProfiler, VBoxContainer); + +public: + struct Metric { + + bool valid; + + uint64_t frame_number; + + struct Area { + String name; + Color color_cache; + StringName fullpath_cache; + float cpu_time = 0; + float gpu_time = 0; + }; + + Vector<Area> areas; + + Metric() { + valid = false; + } + }; + + enum DisplayTimeMode { + DISPLAY_FRAME_TIME, + DISPLAY_FRAME_PERCENT, + }; + +private: + Button *activate; + Button *clear_button; + + TextureRect *graph; + Ref<ImageTexture> graph_texture; + Vector<uint8_t> graph_image; + Tree *variables; + HSplitContainer *h_split; + CheckBox *frame_relative; + CheckBox *linked; + + OptionButton *display_mode; + + SpinBox *cursor_metric_edit; + + Vector<Metric> frame_metrics; + int last_metric; + + StringName selected_area; + + bool updating_frame; + + //int cursor_metric; + int hover_metric; + + float graph_height_cpu; + float graph_height_gpu; + + float graph_limit; + + bool seeking; + + Timer *frame_delay; + Timer *plot_delay; + + void _update_frame(bool p_focus_selected = false); + + void _activate_pressed(); + void _clear_pressed(); + + String _get_time_as_text(float p_time); + + //void _make_metric_ptrs(Metric &m); + void _item_selected(); + + void _update_plot(); + + void _graph_tex_mouse_exit(); + + void _graph_tex_draw(); + void _graph_tex_input(const Ref<InputEvent> &p_ev); + + int _get_cursor_index() const; + + Color _get_color_from_signature(const StringName &p_signature) const; + + void _cursor_metric_changed(double); + + void _combo_changed(int); + +protected: + void _notification(int p_what); + static void _bind_methods(); + +public: + void add_frame_metric(const Metric &p_metric); + void set_enabled(bool p_enable); + bool is_profiling(); + bool is_seeking() { return seeking; } + void disable_seeking(); + + void clear(); + + Vector<Vector<String> > get_data_as_csv() const; + + EditorVisualProfiler(); +}; + +#endif // EDITOR_FRAME_PROFILER_H diff --git a/editor/debugger/script_editor_debugger.cpp b/editor/debugger/script_editor_debugger.cpp index 169ff61e71..920f4d858a 100644 --- a/editor/debugger/script_editor_debugger.cpp +++ b/editor/debugger/script_editor_debugger.cpp @@ -30,21 +30,22 @@ #include "script_editor_debugger.h" +#include "core/debugger/debugger_marshalls.h" #include "core/io/marshalls.h" #include "core/project_settings.h" -#include "core/script_debugger_remote.h" #include "core/ustring.h" +#include "editor/debugger/editor_network_profiler.h" +#include "editor/debugger/editor_profiler.h" +#include "editor/debugger/editor_visual_profiler.h" #include "editor/editor_log.h" -#include "editor/editor_network_profiler.h" #include "editor/editor_node.h" -#include "editor/editor_profiler.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" -#include "editor/editor_visual_profiler.h" #include "editor/plugins/canvas_item_editor_plugin.h" #include "editor/plugins/spatial_editor_plugin.h" #include "editor/property_editor.h" #include "main/performance.h" +#include "scene/3d/camera.h" #include "scene/debugger/scene_debugger.h" #include "scene/gui/dialogs.h" #include "scene/gui/label.h" @@ -58,12 +59,14 @@ #include "scene/gui/tree.h" #include "scene/resources/packed_scene.h" +using CameraOverride = EditorDebuggerNode::CameraOverride; + void ScriptEditorDebugger::_put_msg(String p_message, Array p_data) { if (is_session_active()) { Array msg; msg.push_back(p_message); msg.push_back(p_data); - ppeer->put_var(msg); + peer->put_message(msg); } } @@ -141,7 +144,7 @@ void ScriptEditorDebugger::save_node(ObjectID p_id, const String &p_file) { Array msg; msg.push_back(p_id); msg.push_back(p_file); - _put_msg("save_node", msg); + _put_msg("scene:save_node", msg); } void ScriptEditorDebugger::_file_selected(const String &p_file) { @@ -183,7 +186,7 @@ void ScriptEditorDebugger::_file_selected(const String &p_file) { void ScriptEditorDebugger::request_remote_tree() { - _put_msg("request_scene_tree", Array()); + _put_msg("scene:request_scene_tree", Array()); } const SceneDebuggerTree *ScriptEditorDebugger::get_remote_tree() { @@ -196,7 +199,7 @@ void ScriptEditorDebugger::update_remote_object(ObjectID p_obj_id, const String msg.push_back(p_obj_id); msg.push_back(p_prop); msg.push_back(p_value); - _put_msg("set_object_property", msg); + _put_msg("scene:set_object_property", msg); } void ScriptEditorDebugger::request_remote_object(ObjectID p_obj_id) { @@ -204,7 +207,7 @@ void ScriptEditorDebugger::request_remote_object(ObjectID p_obj_id) { ERR_FAIL_COND(p_obj_id.is_null()); Array msg; msg.push_back(p_obj_id); - _put_msg("inspect_object", msg); + _put_msg("scene:inspect_object", msg); } Object *ScriptEditorDebugger::get_remote_object(ObjectID p_id) { @@ -226,7 +229,7 @@ void ScriptEditorDebugger::_remote_object_property_updated(ObjectID p_id, const void ScriptEditorDebugger::_video_mem_request() { - _put_msg("request_video_mem", Array()); + _put_msg("core:memory", Array()); } Size2 ScriptEditorDebugger::get_minimum_size() const { @@ -267,36 +270,36 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da emit_signal("breaked", false, false); profiler->set_enabled(true); profiler->disable_seeking(); - } else if (p_msg == "message:set_pid") { + } else if (p_msg == "set_pid") { ERR_FAIL_COND(p_data.size() < 1); remote_pid = p_data[0]; - } else if (p_msg == "message:click_ctrl") { + } else if (p_msg == "scene:click_ctrl") { ERR_FAIL_COND(p_data.size() < 2); clicked_ctrl->set_text(p_data[0]); clicked_ctrl_type->set_text(p_data[1]); - } else if (p_msg == "message:scene_tree") { + } else if (p_msg == "scene:scene_tree") { scene_tree->nodes.clear(); scene_tree->deserialize(p_data); emit_signal("remote_tree_updated"); _update_buttons_state(); - } else if (p_msg == "message:inspect_object") { + } else if (p_msg == "scene:inspect_object") { ObjectID id = inspector->add_object(p_data); if (id.is_valid()) emit_signal("remote_object_updated", id); - } else if (p_msg == "message:video_mem") { + } else if (p_msg == "memory:usage") { vmem_tree->clear(); TreeItem *root = vmem_tree->create_item(); - ScriptDebuggerRemote::ResourceUsage usage; + DebuggerMarshalls::ResourceUsage usage; usage.deserialize(p_data); int total = 0; - for (List<ScriptDebuggerRemote::ResourceInfo>::Element *E = usage.infos.front(); E; E = E->next()) { + for (List<DebuggerMarshalls::ResourceInfo>::Element *E = usage.infos.front(); E; E = E->next()) { TreeItem *it = vmem_tree->create_item(root); String type = E->get().type; @@ -316,7 +319,7 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da } else if (p_msg == "stack_dump") { - ScriptDebuggerRemote::ScriptStackDump stack; + DebuggerMarshalls::ScriptStackDump stack; stack.deserialize(p_data); stack_dump->clear(); @@ -349,10 +352,10 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da } else if (p_msg == "output") { ERR_FAIL_COND(p_data.size() < 1); - String t = p_data[0]; - EditorNode::get_log()->add_message(t); - - } else if (p_msg == "performance") { + ERR_FAIL_COND(p_data[0].get_type() != Variant::PACKED_STRING_ARRAY); + Vector<String> strings = p_data[0]; + EditorNode::get_log()->add_message(String("\n").join(strings)); + } else if (p_msg == "performance:profile_frame") { Vector<float> p; p.resize(p_data.size()); for (int i = 0; i < p_data.size(); i++) { @@ -385,36 +388,28 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da perf_history.push_front(p); perf_draw->update(); - } else if (p_msg == "visual_profile") { - // TODO check me. - uint64_t frame = p_data[0]; - Vector<String> names = p_data[1]; - Vector<real_t> values = p_data[2]; + } else if (p_msg == "visual:profile_frame") { + DebuggerMarshalls::VisualProfilerFrame frame; + frame.deserialize(p_data); EditorVisualProfiler::Metric metric; - metric.areas.resize(names.size()); - metric.frame_number = frame; + metric.areas.resize(frame.areas.size()); + metric.frame_number = frame.frame_number; metric.valid = true; { EditorVisualProfiler::Metric::Area *areas_ptr = metric.areas.ptrw(); - int metric_count = names.size(); - - const String *rs = names.ptr(); - const real_t *rr = values.ptr(); - - for (int i = 0; i < metric_count; i++) { - - areas_ptr[i].name = rs[i]; - areas_ptr[i].cpu_time = rr[i * 2 + 0]; - areas_ptr[i].gpu_time = rr[i * 2 + 1]; + for (int i = 0; i < frame.areas.size(); i++) { + areas_ptr[i].name = frame.areas[i].name; + areas_ptr[i].cpu_time = frame.areas[i].cpu_msec; + areas_ptr[i].gpu_time = frame.areas[i].gpu_msec; } } visual_profiler->add_frame_metric(metric); } else if (p_msg == "error") { - ScriptDebuggerRemote::OutputError oe; + DebuggerMarshalls::OutputError oe; ERR_FAIL_COND_MSG(oe.deserialize(p_data) == false, "Failed to deserialize error message"); // Format time. @@ -520,15 +515,15 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da else error_count++; - } else if (p_msg == "profile_sig") { + } else if (p_msg == "servers:function_signature") { // Cache a profiler signature. - ScriptDebuggerRemote::ProfilerSignature sig; + DebuggerMarshalls::ScriptFunctionSignature sig; sig.deserialize(p_data); profiler_signature[sig.id] = sig.name; - } else if (p_msg == "profile_frame" || p_msg == "profile_total") { + } else if (p_msg == "servers:profile_frame" || p_msg == "servers:profile_total") { EditorProfiler::Metric metric; - ScriptDebuggerRemote::ProfilerFrame frame; + DebuggerMarshalls::ServersProfilerFrame frame; frame.deserialize(p_data); metric.valid = true; metric.frame_number = frame.frame_number; @@ -536,10 +531,8 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da metric.idle_time = frame.idle_time; metric.physics_time = frame.physics_time; metric.physics_frame_time = frame.physics_frame_time; - int frame_data_amount = frame.frames_data.size(); - int frame_function_amount = frame.frame_functions.size(); - if (frame_data_amount) { + if (frame.servers.size()) { EditorProfiler::Metric::Category frame_time; frame_time.signature = "category_frame_time"; frame_time.name = "Frame Time"; @@ -573,42 +566,42 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da metric.categories.push_back(frame_time); } - for (int i = 0; i < frame_data_amount; i++) { + for (int i = 0; i < frame.servers.size(); i++) { + const DebuggerMarshalls::ServerInfo &srv = frame.servers[i]; EditorProfiler::Metric::Category c; - String name = frame.frames_data[i].name; - Array values = frame.frames_data[i].data; + const String name = srv.name; c.name = name.capitalize(); - c.items.resize(values.size() / 2); + c.items.resize(srv.functions.size()); c.total_time = 0; c.signature = "categ::" + name; - for (int j = 0; j < values.size(); j += 2) { + for (int j = 0; j < srv.functions.size(); j++) { EditorProfiler::Metric::Category::Item item; item.calls = 1; item.line = 0; - item.name = values[j]; - item.self = values[j + 1]; + item.name = srv.functions[j].name; + item.self = srv.functions[j].time; item.total = item.self; item.signature = "categ::" + name + "::" + item.name; item.name = item.name.capitalize(); c.total_time += item.total; - c.items.write[j / 2] = item; + c.items.write[j] = item; } metric.categories.push_back(c); } EditorProfiler::Metric::Category funcs; funcs.total_time = frame.script_time; - funcs.items.resize(frame_function_amount); + funcs.items.resize(frame.script_functions.size()); funcs.name = "Script Functions"; funcs.signature = "script_functions"; - for (int i = 0; i < frame_function_amount; i++) { + for (int i = 0; i < frame.script_functions.size(); i++) { - int signature = frame.frame_functions[i].sig_id; - int calls = frame.frame_functions[i].call_count; - float total = frame.frame_functions[i].total_time; - float self = frame.frame_functions[i].self_time; + int signature = frame.script_functions[i].sig_id; + int calls = frame.script_functions[i].call_count; + float total = frame.script_functions[i].total_time; + float self = frame.script_functions[i].self_time; EditorProfiler::Metric::Category::Item item; if (profiler_signature.has(signature)) { @@ -639,24 +632,28 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da metric.categories.push_back(funcs); - if (p_msg == "profile_frame") + if (p_msg == "servers:profile_frame") profiler->add_frame_metric(metric, false); else profiler->add_frame_metric(metric, true); - } else if (p_msg == "network_profile") { - ScriptDebuggerRemote::NetworkProfilerFrame frame; + } else if (p_msg == "network:profile_frame") { + DebuggerMarshalls::NetworkProfilerFrame frame; frame.deserialize(p_data); for (int i = 0; i < frame.infos.size(); i++) { network_profiler->add_node_frame_data(frame.infos[i]); } - } else if (p_msg == "network_bandwidth") { + + } else if (p_msg == "network:bandwidth") { ERR_FAIL_COND(p_data.size() < 2); network_profiler->set_bandwidth(p_data[0], p_data[1]); - } else if (p_msg == "kill_me") { + } else if (p_msg == "request_quit") { emit_signal("stop_requested"); _stop_and_notify(); + + } else { + WARN_PRINT("unknown message " + p_msg); } } @@ -776,7 +773,7 @@ void ScriptEditorDebugger::_notification(int p_what) { if (is_session_active()) { - if (camera_override == OVERRIDE_2D) { + if (camera_override == CameraOverride::OVERRIDE_2D) { CanvasItemEditor *editor = CanvasItemEditor::get_singleton(); Dictionary state = editor->get_state(); @@ -789,10 +786,10 @@ void ScriptEditorDebugger::_notification(int p_what) { Array msg; msg.push_back(transform); - _put_msg("override_camera_2D:transform", msg); + _put_msg("scene:override_camera_2D:transform", msg); - } else if (camera_override >= OVERRIDE_3D_1) { - int viewport_idx = camera_override - OVERRIDE_3D_1; + } else if (camera_override >= CameraOverride::OVERRIDE_3D_1) { + int viewport_idx = camera_override - CameraOverride::OVERRIDE_3D_1; SpatialEditorViewport *viewport = SpatialEditor::get_singleton()->get_editor_viewport(viewport_idx); Camera *const cam = viewport->get_camera(); @@ -807,34 +804,15 @@ void ScriptEditorDebugger::_notification(int p_what) { } msg.push_back(cam->get_znear()); msg.push_back(cam->get_zfar()); - _put_msg("override_camera_3D:transform", msg); + _put_msg("scene:override_camera_3D:transform", msg); } } - if (!is_session_active()) { - _stop_and_notify(); - break; - }; - - if (ppeer->get_available_packet_count() <= 0) { - break; - }; - const uint64_t until = OS::get_singleton()->get_ticks_msec() + 20; - while (ppeer->get_available_packet_count() > 0) { + while (peer->has_message()) { - Variant cmd; - Error ret = ppeer->get_var(cmd); - if (ret != OK) { - _stop_and_notify(); - ERR_FAIL_MSG("Error decoding variant from peer"); - } - if (cmd.get_type() != Variant::ARRAY) { - _stop_and_notify(); - ERR_FAIL_MSG("Invalid message format received from peer"); - } - Array arr = cmd; + Array arr = peer->get_message(); if (arr.size() != 2 || arr[0].get_type() != Variant::STRING || arr[1].get_type() != Variant::ARRAY) { _stop_and_notify(); ERR_FAIL_MSG("Invalid message format received from peer"); @@ -844,6 +822,10 @@ void ScriptEditorDebugger::_notification(int p_what) { if (OS::get_singleton()->get_ticks_msec() > until) break; } + if (!is_session_active()) { + _stop_and_notify(); + break; + }; } break; case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { @@ -875,14 +857,14 @@ void ScriptEditorDebugger::_clear_execution() { inspector->clear_stack_variables(); } -void ScriptEditorDebugger::start(Ref<StreamPeerTCP> p_connection) { +void ScriptEditorDebugger::start(Ref<RemoteDebuggerPeer> p_peer) { error_count = 0; warning_count = 0; stop(); - connection = p_connection; - ppeer->set_stream_peer(connection); + peer = p_peer; + ERR_FAIL_COND(p_peer.is_null()); perf_history.clear(); for (int i = 0; i < Performance::MONITOR_MAX; i++) { @@ -893,19 +875,11 @@ void ScriptEditorDebugger::start(Ref<StreamPeerTCP> p_connection) { set_process(true); breaked = false; can_debug = true; - camera_override = OVERRIDE_NONE; + camera_override = CameraOverride::OVERRIDE_NONE; tabs->set_current_tab(0); _set_reason_text(TTR("Debug session started."), MESSAGE_SUCCESS); _update_buttons_state(); - - if (profiler->is_profiling()) { - _profiler_activate(true); - } - - if (network_profiler->is_profiling()) { - _network_profiler_activate(true); - } } void ScriptEditorDebugger::_update_buttons_state() { @@ -936,10 +910,10 @@ void ScriptEditorDebugger::stop() { _clear_execution(); inspector->clear_cache(); - ppeer->set_stream_peer(Ref<StreamPeer>()); - if (connection.is_valid()) { - connection.unref(); + if (peer.is_valid()) { + peer->close(); + peer.unref(); reason->set_text(""); reason->set_tooltip(""); } @@ -952,49 +926,31 @@ void ScriptEditorDebugger::stop() { _update_buttons_state(); } -void ScriptEditorDebugger::_profiler_activate(bool p_enable) { - - if (p_enable) { - profiler_signature.clear(); - Array msg; - int max_funcs = EditorSettings::get_singleton()->get("debugger/profiler_frame_max_functions"); - max_funcs = CLAMP(max_funcs, 16, 512); - msg.push_back(max_funcs); - _put_msg("start_profiling", msg); - print_verbose("Starting profiling."); - - } else { - _put_msg("stop_profiling", Array()); - print_verbose("Ending profiling."); - } -} - -void ScriptEditorDebugger::_visual_profiler_activate(bool p_enable) { +void ScriptEditorDebugger::_profiler_activate(bool p_enable, int p_type) { - if (!connection.is_valid()) - return; - - if (p_enable) { - profiler_signature.clear(); - _put_msg("start_visual_profiling", Array()); - print_verbose("Starting visual profiling."); - - } else { - _put_msg("stop_visual_profiling", Array()); - print_verbose("Ending visual profiling."); - } -} - -void ScriptEditorDebugger::_network_profiler_activate(bool p_enable) { - - if (p_enable) { - profiler_signature.clear(); - _put_msg("start_network_profiling", Array()); - print_verbose("Starting network profiling."); - - } else { - _put_msg("stop_network_profiling", Array()); - print_verbose("Ending network profiling."); + Array data; + data.push_back(p_enable); + switch (p_type) { + case PROFILER_NETWORK: + _put_msg("profiler:network", data); + break; + case PROFILER_VISUAL: + _put_msg("profiler:visual", data); + break; + case PROFILER_SCRIPTS_SERVERS: + if (p_enable) { + // Clear old script signatures. (should we move all this into the profiler?) + profiler_signature.clear(); + // Add max funcs options to request. + Array opts; + int max_funcs = EditorSettings::get_singleton()->get("debugger/profiler_frame_max_functions"); + opts.push_back(CLAMP(max_funcs, 16, 512)); + data.push_back(opts); + } + _put_msg("profiler:servers", data); + break; + default: + ERR_FAIL_MSG("Invalid profiler type"); } } @@ -1045,7 +1001,7 @@ int ScriptEditorDebugger::_get_node_path_cache(const NodePath &p_path) { Array msg; msg.push_back(p_path); msg.push_back(last_path_id); - _put_msg("live_node_path", msg); + _put_msg("scene:live_node_path", msg); return last_path_id; } @@ -1063,7 +1019,7 @@ int ScriptEditorDebugger::_get_res_path_cache(const String &p_path) { Array msg; msg.push_back(p_path); msg.push_back(last_path_id); - _put_msg("live_res_path", msg); + _put_msg("scene:live_res_path", msg); return last_path_id; } @@ -1095,7 +1051,7 @@ void ScriptEditorDebugger::_method_changed(Object *p_base, const StringName &p_n //no pointers, sorry msg.push_back(*argptr[i]); } - _put_msg("live_node_call", msg); + _put_msg("scene:live_node_call", msg); return; } @@ -1114,7 +1070,7 @@ void ScriptEditorDebugger::_method_changed(Object *p_base, const StringName &p_n //no pointers, sorry msg.push_back(*argptr[i]); } - _put_msg("live_res_call", msg); + _put_msg("scene:live_res_call", msg); return; } @@ -1140,7 +1096,7 @@ void ScriptEditorDebugger::_property_changed(Object *p_base, const StringName &p msg.push_back(pathid); msg.push_back(p_property); msg.push_back(res->get_path()); - _put_msg("live_node_prop_res", msg); + _put_msg("scene:live_node_prop_res", msg); } } else { @@ -1148,7 +1104,7 @@ void ScriptEditorDebugger::_property_changed(Object *p_base, const StringName &p msg.push_back(pathid); msg.push_back(p_property); msg.push_back(p_value); - _put_msg("live_node_prop", msg); + _put_msg("scene:live_node_prop", msg); } return; @@ -1169,7 +1125,7 @@ void ScriptEditorDebugger::_property_changed(Object *p_base, const StringName &p msg.push_back(pathid); msg.push_back(p_property); msg.push_back(res2->get_path()); - _put_msg("live_res_prop_res", msg); + _put_msg("scene:live_res_prop_res", msg); } } else { @@ -1177,7 +1133,7 @@ void ScriptEditorDebugger::_property_changed(Object *p_base, const StringName &p msg.push_back(pathid); msg.push_back(p_property); msg.push_back(p_value); - _put_msg("live_res_prop", msg); + _put_msg("scene:live_res_prop", msg); } return; @@ -1255,7 +1211,7 @@ void ScriptEditorDebugger::update_live_edit_root() { msg.push_back(editor->get_edited_scene()->get_filename()); else msg.push_back(""); - _put_msg("live_set_root", msg); + _put_msg("scene:live_set_root", msg); live_edit_root->set_text(np); } @@ -1266,7 +1222,7 @@ void ScriptEditorDebugger::live_debug_create_node(const NodePath &p_parent, cons msg.push_back(p_parent); msg.push_back(p_type); msg.push_back(p_name); - _put_msg("live_create_node", msg); + _put_msg("scene:live_create_node", msg); } } @@ -1277,7 +1233,7 @@ void ScriptEditorDebugger::live_debug_instance_node(const NodePath &p_parent, co msg.push_back(p_parent); msg.push_back(p_path); msg.push_back(p_name); - _put_msg("live_instance_node", msg); + _put_msg("scene:live_instance_node", msg); } } void ScriptEditorDebugger::live_debug_remove_node(const NodePath &p_at) { @@ -1285,7 +1241,7 @@ void ScriptEditorDebugger::live_debug_remove_node(const NodePath &p_at) { if (live_debug) { Array msg; msg.push_back(p_at); - _put_msg("live_remove_node", msg); + _put_msg("scene:live_remove_node", msg); } } void ScriptEditorDebugger::live_debug_remove_and_keep_node(const NodePath &p_at, ObjectID p_keep_id) { @@ -1294,7 +1250,7 @@ void ScriptEditorDebugger::live_debug_remove_and_keep_node(const NodePath &p_at, Array msg; msg.push_back(p_at); msg.push_back(p_keep_id); - _put_msg("live_remove_and_keep_node", msg); + _put_msg("scene:live_remove_and_keep_node", msg); } } void ScriptEditorDebugger::live_debug_restore_node(ObjectID p_id, const NodePath &p_at, int p_at_pos) { @@ -1304,7 +1260,7 @@ void ScriptEditorDebugger::live_debug_restore_node(ObjectID p_id, const NodePath msg.push_back(p_id); msg.push_back(p_at); msg.push_back(p_at_pos); - _put_msg("live_restore_node", msg); + _put_msg("scene:live_restore_node", msg); } } void ScriptEditorDebugger::live_debug_duplicate_node(const NodePath &p_at, const String &p_new_name) { @@ -1313,7 +1269,7 @@ void ScriptEditorDebugger::live_debug_duplicate_node(const NodePath &p_at, const Array msg; msg.push_back(p_at); msg.push_back(p_new_name); - _put_msg("live_duplicate_node", msg); + _put_msg("scene:live_duplicate_node", msg); } } void ScriptEditorDebugger::live_debug_reparent_node(const NodePath &p_at, const NodePath &p_new_place, const String &p_new_name, int p_at_pos) { @@ -1324,32 +1280,32 @@ void ScriptEditorDebugger::live_debug_reparent_node(const NodePath &p_at, const msg.push_back(p_new_place); msg.push_back(p_new_name); msg.push_back(p_at_pos); - _put_msg("live_reparent_node", msg); + _put_msg("scene:live_reparent_node", msg); } } -ScriptEditorDebugger::CameraOverride ScriptEditorDebugger::get_camera_override() const { +CameraOverride ScriptEditorDebugger::get_camera_override() const { return camera_override; } void ScriptEditorDebugger::set_camera_override(CameraOverride p_override) { - if (p_override == OVERRIDE_2D && camera_override != OVERRIDE_2D) { + if (p_override == CameraOverride::OVERRIDE_2D && camera_override != CameraOverride::OVERRIDE_2D) { Array msg; msg.push_back(true); - _put_msg("override_camera_2D:set", msg); - } else if (p_override != OVERRIDE_2D && camera_override == OVERRIDE_2D) { + _put_msg("scene:override_camera_2D:set", msg); + } else if (p_override != CameraOverride::OVERRIDE_2D && camera_override == CameraOverride::OVERRIDE_2D) { Array msg; msg.push_back(false); - _put_msg("override_camera_2D:set", msg); - } else if (p_override >= OVERRIDE_3D_1 && camera_override < OVERRIDE_3D_1) { + _put_msg("scene:override_camera_2D:set", msg); + } else if (p_override >= CameraOverride::OVERRIDE_3D_1 && camera_override < CameraOverride::OVERRIDE_3D_1) { Array msg; msg.push_back(true); - _put_msg("override_camera_3D:set", msg); - } else if (p_override < OVERRIDE_3D_1 && camera_override >= OVERRIDE_3D_1) { + _put_msg("scene:override_camera_3D:set", msg); + } else if (p_override < CameraOverride::OVERRIDE_3D_1 && camera_override >= CameraOverride::OVERRIDE_3D_1) { Array msg; msg.push_back(false); - _put_msg("override_camera_3D:set", msg); + _put_msg("scene:override_camera_3D:set", msg); } camera_override = p_override; @@ -1423,7 +1379,6 @@ void ScriptEditorDebugger::_clear_errors_list() { error_tree->clear(); error_count = 0; warning_count = 0; - update_tabs(); } // Right click on specific file(s) or folder(s). @@ -1502,8 +1457,6 @@ void ScriptEditorDebugger::_bind_methods() { ScriptEditorDebugger::ScriptEditorDebugger(EditorNode *p_editor) { - ppeer = Ref<PacketPeerStream>(memnew(PacketPeerStream)); - ppeer->set_input_buffer_max_size((1024 * 1024 * 8) - 4); // 8 MiB should be enough, minus 4 bytes for separator. editor = p_editor; tabs = memnew(TabContainer); @@ -1655,7 +1608,7 @@ ScriptEditorDebugger::ScriptEditorDebugger(EditorNode *p_editor) { profiler = memnew(EditorProfiler); profiler->set_name(TTR("Profiler")); tabs->add_child(profiler); - profiler->connect("enable_profiling", callable_mp(this, &ScriptEditorDebugger::_profiler_activate)); + profiler->connect("enable_profiling", callable_mp(this, &ScriptEditorDebugger::_profiler_activate), varray(PROFILER_SCRIPTS_SERVERS)); profiler->connect("break_request", callable_mp(this, &ScriptEditorDebugger::_profiler_seeked)); } @@ -1663,14 +1616,14 @@ ScriptEditorDebugger::ScriptEditorDebugger(EditorNode *p_editor) { visual_profiler = memnew(EditorVisualProfiler); visual_profiler->set_name(TTR("Visual Profiler")); tabs->add_child(visual_profiler); - visual_profiler->connect("enable_profiling", callable_mp(this, &ScriptEditorDebugger::_visual_profiler_activate)); + visual_profiler->connect("enable_profiling", callable_mp(this, &ScriptEditorDebugger::_profiler_activate), varray(PROFILER_VISUAL)); } { //network profiler network_profiler = memnew(EditorNetworkProfiler); network_profiler->set_name(TTR("Network Profiler")); tabs->add_child(network_profiler); - network_profiler->connect("enable_profiling", callable_mp(this, &ScriptEditorDebugger::_network_profiler_activate)); + network_profiler->connect("enable_profiling", callable_mp(this, &ScriptEditorDebugger::_profiler_activate), varray(PROFILER_NETWORK)); } { //monitors @@ -1824,7 +1777,7 @@ ScriptEditorDebugger::ScriptEditorDebugger(EditorNode *p_editor) { add_child(msgdialog); live_debug = true; - camera_override = OVERRIDE_NONE; + camera_override = CameraOverride::OVERRIDE_NONE; last_path_id = false; error_count = 0; warning_count = 0; @@ -1833,6 +1786,9 @@ ScriptEditorDebugger::ScriptEditorDebugger(EditorNode *p_editor) { ScriptEditorDebugger::~ScriptEditorDebugger() { - ppeer->set_stream_peer(Ref<StreamPeer>()); + if (peer.is_valid()) { + peer->close(); + peer.unref(); + } memdelete(scene_tree); } diff --git a/editor/debugger/script_editor_debugger.h b/editor/debugger/script_editor_debugger.h index fd034ba983..e7ce917543 100644 --- a/editor/debugger/script_editor_debugger.h +++ b/editor/debugger/script_editor_debugger.h @@ -31,14 +31,13 @@ #ifndef SCRIPT_EDITOR_DEBUGGER_H #define SCRIPT_EDITOR_DEBUGGER_H -#include "core/io/packet_peer.h" -#include "core/io/stream_peer_tcp.h" +#include "core/os/os.h" #include "editor/debugger/editor_debugger_inspector.h" -#include "editor/editor_inspector.h" -#include "editor/property_editor.h" -#include "scene/3d/camera.h" -#include "scene/gui/box_container.h" +#include "editor/debugger/editor_debugger_node.h" +#include "editor/debugger/editor_debugger_server.h" +#include "editor/editor_file_dialog.h" #include "scene/gui/button.h" +#include "scene/gui/margin_container.h" class Tree; class EditorNode; @@ -61,16 +60,6 @@ class ScriptEditorDebugger : public MarginContainer { friend class EditorDebuggerNode; -public: - enum CameraOverride { - OVERRIDE_NONE, - OVERRIDE_2D, - OVERRIDE_3D_1, // 3D Viewport 1 - OVERRIDE_3D_2, // 3D Viewport 2 - OVERRIDE_3D_3, // 3D Viewport 3 - OVERRIDE_3D_4 // 3D Viewport 4 - }; - private: enum MessageType { MESSAGE_ERROR, @@ -78,6 +67,12 @@ private: MESSAGE_SUCCESS, }; + enum ProfilerType { + PROFILER_NETWORK, + PROFILER_VISUAL, + PROFILER_SCRIPTS_SERVERS + }; + AcceptDialog *msgdialog; LineEdit *clicked_ctrl; @@ -132,8 +127,7 @@ private: EditorDebuggerInspector *inspector; SceneDebuggerTree *scene_tree; - Ref<StreamPeerTCP> connection; - Ref<PacketPeerStream> ppeer; + Ref<RemoteDebuggerPeer> peer; HashMap<NodePath, int> node_path_cache; int last_path_id; @@ -151,7 +145,7 @@ private: bool live_debug; - CameraOverride camera_override; + EditorDebuggerNode::CameraOverride camera_override; void _performance_draw(); void _performance_select(); @@ -183,12 +177,9 @@ private: void _expand_errors_list(); void _collapse_errors_list(); - void _visual_profiler_activate(bool p_enable); - void _profiler_activate(bool p_enable); + void _profiler_activate(bool p_enable, int p_profiler); void _profiler_seeked(); - void _network_profiler_activate(bool p_enable); - void _clear_errors_list(); void _error_tree_item_rmb_selected(const Vector2 &p_pos); @@ -216,7 +207,7 @@ public: void request_remote_tree(); const SceneDebuggerTree *get_remote_tree(); - void start(Ref<StreamPeerTCP> p_connection); + void start(Ref<RemoteDebuggerPeer> p_peer); void stop(); void debug_skip_breakpoints(); @@ -228,7 +219,7 @@ public: void debug_continue(); bool is_breaked() const { return breaked; } bool is_debuggable() const { return can_debug; } - bool is_session_active() { return connection.is_valid() && connection->is_connected_to_host(); }; + bool is_session_active() { return peer.is_valid() && peer->is_peer_connected(); }; int get_remote_pid() const { return remote_pid; } int get_error_count() const { return error_count; } @@ -252,8 +243,8 @@ public: void live_debug_duplicate_node(const NodePath &p_at, const String &p_new_name); void live_debug_reparent_node(const NodePath &p_at, const NodePath &p_new_place, const String &p_new_name, int p_at_pos); - CameraOverride get_camera_override() const; - void set_camera_override(CameraOverride p_override); + EditorDebuggerNode::CameraOverride get_camera_override() const; + void set_camera_override(EditorDebuggerNode::CameraOverride p_override); void set_breakpoint(const String &p_path, int p_line, bool p_enabled); |