Backported godot4 PR: Support threads in the script debugger

* This implementation adds threads on the side of the client (script debugger).
* Some functions of the debugger are optimized.
* The profile is also now thread safe using atomics.
* The editor can switch between multiple threads when debugging.
This PR adds threaded support for the script language debugger. Every thread has its own thread local data and it will connect to the debugger using multiple thread IDs.
This means that, now, the editor can receive multiple threads entering debug mode at the same time.
- reduz
PR 76582
Will be available here after it's merged:
6b176671c4
This commit is contained in:
Relintai 2023-06-14 14:43:55 +02:00
parent 4b9989b553
commit 1a5cba555c
14 changed files with 626 additions and 172 deletions

View File

@ -407,6 +407,11 @@ void ScriptLanguage::get_core_type_words(List<String> *p_words) const {
void ScriptLanguage::frame() {
}
#if !defined(NO_THREADS)
thread_local int ScriptDebugger::lines_left = -1;
thread_local int ScriptDebugger::depth = -1;
thread_local ScriptLanguage *ScriptDebugger::break_lang = nullptr;
#endif
ScriptDebugger *ScriptDebugger::singleton = nullptr;
@ -444,7 +449,7 @@ void ScriptDebugger::remove_breakpoint(int p_line, const StringName &p_source) {
}
}
bool ScriptDebugger::is_breakpoint(int p_line, const StringName &p_source) const {
if (!breakpoints.has(p_line)) {
if (likely(!breakpoints.has(p_line))) {
return false;
}
return breakpoints[p_line].has(p_source);

View File

@ -30,9 +30,9 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#include "core/io/multiplayer_api.h"
#include "core/containers/rb_map.h"
#include "core/containers/pair.h"
#include "core/containers/rb_map.h"
#include "core/io/multiplayer_api.h"
#include "core/object/resource.h"
class ScriptLanguage;
@ -415,13 +415,19 @@ public:
};
class ScriptDebugger {
#if !defined(NO_THREADS)
static thread_local int lines_left;
static thread_local int depth;
static thread_local ScriptLanguage *break_lang;
#else
int lines_left;
int depth;
ScriptLanguage *break_lang;
#endif
static ScriptDebugger *singleton;
RBMap<int, RBSet<StringName>> breakpoints;
ScriptLanguage *break_lang;
RBMap<int, RBSet<StringName>> breakpoints;
public:
_FORCE_INLINE_ static ScriptDebugger *get_singleton() { return singleton; }

View File

@ -5,7 +5,6 @@
</brief_description>
<description>
A unit of execution in a process. Can run methods on [Object]s simultaneously. The use of synchronization via [Mutex] or [Semaphore] is advised if working with shared objects.
[b]Note:[/b] Breakpoints won't break on code if it's running in a thread. This is a current limitation of the GDScript debugger.
</description>
<tutorials>
<link title="Using multiple threads">$DOCS_URL/tutorials/performance/threads/using_multiple_threads.html</link>

View File

@ -30,9 +30,6 @@
#include "script_editor_debugger.h"
#include "core/object/class_db.h"
#include "core/math/color.h"
#include "core/variant/dictionary.h"
#include "core/error/error_list.h"
#include "core/error/error_macros.h"
#include "core/io/marshalls.h"
@ -43,19 +40,22 @@
#include "core/io/stream_peer.h"
#include "core/io/stream_peer_tcp.h"
#include "core/io/tcp_server.h"
#include "core/math/color.h"
#include "core/math/math_defs.h"
#include "core/math/math_funcs.h"
#include "core/math/rect2.h"
#include "core/math/transform_2d.h"
#include "core/object/class_db.h"
#include "core/object/ref_ptr.h"
#include "core/object/script_language.h"
#include "core/object/undo_redo.h"
#include "core/os/file_access.h"
#include "core/os/memory.h"
#include "core/os/os.h"
#include "core/string/print_string.h"
#include "core/object/ref_ptr.h"
#include "core/object/script_language.h"
#include "core/typedefs.h"
#include "core/object/undo_redo.h"
#include "core/string/ustring.h"
#include "core/typedefs.h"
#include "core/variant/dictionary.h"
#include "core/version.h"
#include "editor/editor_data.h"
#include "editor/editor_file_dialog.h"
@ -80,6 +80,7 @@
#include "scene/gui/label.h"
#include "scene/gui/line_edit.h"
#include "scene/gui/margin_container.h"
#include "scene/gui/option_button.h"
#include "scene/gui/popup_menu.h"
#include "scene/gui/separator.h"
#include "scene/gui/split_container.h"
@ -254,43 +255,47 @@ void EditorScriptEditorDebugger::debug_skip_breakpoints() {
if (connection.is_valid()) {
Array msg;
msg.push_back("set_skip_breakpoints");
msg.push_back(debugging_thread_id != 0 ? debugging_thread_id : process_main_thread_id);
msg.push_back(skip_breakpoints_value);
ppeer->put_var(msg);
}
}
void EditorScriptEditorDebugger::debug_next() {
ERR_FAIL_COND(!breaked);
ERR_FAIL_COND(!is_breaked());
ERR_FAIL_COND(connection.is_null());
ERR_FAIL_COND(!connection->is_connected_to_host());
Array msg;
msg.push_back("next");
msg.push_back(debugging_thread_id);
ppeer->put_var(msg);
_clear_execution();
}
void EditorScriptEditorDebugger::debug_step() {
ERR_FAIL_COND(!breaked);
ERR_FAIL_COND(!is_breaked());
ERR_FAIL_COND(connection.is_null());
ERR_FAIL_COND(!connection->is_connected_to_host());
Array msg;
msg.push_back("step");
msg.push_back(debugging_thread_id);
ppeer->put_var(msg);
_clear_execution();
}
void EditorScriptEditorDebugger::debug_break() {
ERR_FAIL_COND(breaked);
ERR_FAIL_COND(is_breaked());
ERR_FAIL_COND(connection.is_null());
ERR_FAIL_COND(!connection->is_connected_to_host());
Array msg;
msg.push_back("break");
msg.push_back(process_main_thread_id);
ppeer->put_var(msg);
}
void EditorScriptEditorDebugger::debug_continue() {
ERR_FAIL_COND(!breaked);
ERR_FAIL_COND(!is_breaked());
ERR_FAIL_COND(connection.is_null());
ERR_FAIL_COND(!connection->is_connected_to_host());
@ -299,9 +304,14 @@ void EditorScriptEditorDebugger::debug_continue() {
Array msg;
_clear_execution();
msg.push_back("continue");
msg.push_back(debugging_thread_id);
ppeer->put_var(msg);
}
bool EditorScriptEditorDebugger::is_session_active() {
return !connection.is_null() && connection->is_connected_to_host();
}
void EditorScriptEditorDebugger::_scene_tree_folded(Object *obj) {
if (updating_scene_tree) {
return;
@ -333,6 +343,7 @@ void EditorScriptEditorDebugger::_scene_tree_selected() {
Array msg;
msg.push_back("inspect_object");
msg.push_back(process_main_thread_id);
msg.push_back(inspected_object_id);
ppeer->put_var(msg);
}
@ -357,6 +368,7 @@ void EditorScriptEditorDebugger::_file_selected(const String &p_file) {
case SAVE_NODE: {
Array msg;
msg.push_back("save_node");
msg.push_back(process_main_thread_id);
msg.push_back(inspected_object_id);
msg.push_back(p_file);
ppeer->put_var(msg);
@ -432,6 +444,7 @@ void EditorScriptEditorDebugger::_file_selected(const String &p_file) {
void EditorScriptEditorDebugger::_scene_tree_property_value_edited(const String &p_prop, const Variant &p_value) {
Array msg;
msg.push_back("set_object_property");
msg.push_back(process_main_thread_id);
msg.push_back(inspected_object_id);
msg.push_back(p_prop);
msg.push_back(p_value);
@ -443,6 +456,7 @@ void EditorScriptEditorDebugger::_scene_tree_property_select_object(ObjectID p_o
inspected_object_id = p_object;
Array msg;
msg.push_back("inspect_object");
msg.push_back(process_main_thread_id);
msg.push_back(inspected_object_id);
ppeer->put_var(msg);
}
@ -453,6 +467,7 @@ void EditorScriptEditorDebugger::_scene_tree_request() {
Array msg;
msg.push_back("request_scene_tree");
msg.push_back(process_main_thread_id);
ppeer->put_var(msg);
}
@ -551,6 +566,7 @@ void EditorScriptEditorDebugger::_video_mem_request() {
Array msg;
msg.push_back("request_video_mem");
msg.push_back(process_main_thread_id);
ppeer->put_var(msg);
}
@ -568,14 +584,37 @@ Size2 EditorScriptEditorDebugger::get_minimum_size() const {
return ms;
}
void EditorScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_data) {
void EditorScriptEditorDebugger::_thread_debug_enter(uint64_t p_thread_id) {
ERR_FAIL_COND(!threads_debugged.has(p_thread_id));
ThreadDebugged &td = threads_debugged[p_thread_id];
_set_reason_text(td.error, MESSAGE_ERROR);
emit_signal("breaked", true, td.can_debug, td.error, td.has_stackdump);
if (!td.error.empty()) {
tabs->set_current_tab(0);
}
inspector->refresh(); // Take a chance to force remote objects update.
Array msg;
msg.push_back("get_stack_dump");
msg.push_back(p_thread_id);
ppeer->put_var(msg);
}
void EditorScriptEditorDebugger::_select_thread(int p_index) {
debugging_thread_id = threads->get_item_metadata(threads->get_selected());
_thread_debug_enter(debugging_thread_id);
}
void EditorScriptEditorDebugger::_parse_message(const String &p_msg, uint64_t p_thread_id, const Array &p_data) {
if (p_msg == "debug_enter") {
/*
Array msg;
msg.push_back("get_stack_dump");
ppeer->put_var(msg);
ERR_FAIL_COND(p_data.size() != 2);
bool can_continue = p_data[0];
String error = p_data[1];
step->set_disabled(!can_continue);
next->set_disabled(!can_continue);
_set_reason_text(error, MESSAGE_ERROR);
@ -588,12 +627,51 @@ void EditorScriptEditorDebugger::_parse_message(const String &p_msg, const Array
if (error != "") {
tabs->set_current_tab(0);
}
*/
ERR_FAIL_COND(p_data.size() != 4);
ThreadDebugged td;
td.name = p_data[3];
td.error = p_data[1];
td.can_debug = p_data[0];
td.has_stackdump = p_data[2];
td.thread_id = p_thread_id;
static uint32_t order_inc = 0;
td.debug_order = order_inc++;
threads_debugged.insert(p_thread_id, td);
if (threads_debugged.size() == 1) {
// First thread that requests debug
debugging_thread_id = p_thread_id;
_thread_debug_enter(p_thread_id);
bool can_continue = p_data[0];
String error = p_data[1];
step->set_disabled(!can_continue);
next->set_disabled(!can_continue);
_set_reason_text(error, MESSAGE_ERROR);
copy->set_disabled(false);
dobreak->set_disabled(true);
docontinue->set_disabled(false);
OS::get_singleton()->move_window_to_foreground();
if (reason->get_text() != "") {
tabs->set_current_tab(0);
}
}
profiler->set_enabled(false);
EditorNode::get_singleton()->get_pause_button()->set_pressed(true);
EditorNode::get_singleton()->make_bottom_panel_item_visible(this);
_clear_remote_objects();
_update_buttons_state();
} else if (p_msg == "debug_exit") {
/*
breaked = false;
_clear_execution();
copy->set_disabled(true);
@ -606,6 +684,51 @@ void EditorScriptEditorDebugger::_parse_message(const String &p_msg, const Array
dobreak->set_disabled(false);
docontinue->set_disabled(true);
emit_signal("breaked", false, false, Variant());
*/
threads_debugged.erase(p_thread_id);
if (p_thread_id == debugging_thread_id) {
_clear_execution();
if (threads_debugged.size() == 0) {
debugging_thread_id = 0;
} else {
// Find next thread to debug.
uint32_t min_order = 0xFFFFFFFF;
uint64_t next_thread = 0;
for (HashMap<uint64_t, ThreadDebugged>::Element *T = threads_debugged.front(); T; T = T->next) {
if (T->value().debug_order < min_order) {
min_order = T->value().debug_order;
next_thread = T->key();
}
}
debugging_thread_id = next_thread;
}
if (debugging_thread_id == 0) {
// Nothing else to debug.
copy->set_disabled(true);
step->set_disabled(true);
next->set_disabled(true);
reason->set_text("");
reason->set_tooltip("");
back->set_disabled(true);
forward->set_disabled(true);
dobreak->set_disabled(false);
docontinue->set_disabled(true);
_set_reason_text(TTR("Execution resumed."), MESSAGE_SUCCESS);
emit_signal("breaked", false, false, "", false);
_update_buttons_state();
} else {
_thread_debug_enter(debugging_thread_id);
}
} else {
_update_buttons_state();
}
profiler->set_enabled(true);
profiler->disable_seeking();
EditorNode::get_singleton()->get_pause_button()->set_pressed(false);
@ -1161,6 +1284,8 @@ void EditorScriptEditorDebugger::_parse_message(const String &p_msg, const Array
network_profiler->set_bandwidth(p_data[0], p_data[1]);
} else if (p_msg == "kill_me") {
editor->call_deferred("stop_child_process");
} else if (p_msg == "main_thread_id") {
process_main_thread_id = p_thread_id;
}
}
@ -1179,6 +1304,42 @@ void EditorScriptEditorDebugger::_set_reason_text(const String &p_reason, Messag
reason->set_tooltip(p_reason.word_wrap(80));
}
void EditorScriptEditorDebugger::_update_buttons_state() {
const bool active = is_session_active();
const bool has_editor_tree = active && inspect_scene_tree;
vmem_refresh->set_disabled(!active);
step->set_disabled(!active || !is_breaked() || !is_debuggable());
next->set_disabled(!active || !is_breaked() || !is_debuggable());
copy->set_disabled(!active || !is_breaked());
docontinue->set_disabled(!active || !is_breaked());
dobreak->set_disabled(!active || is_breaked());
le_clear->set_disabled(!active);
le_set->set_disabled(!has_editor_tree);
thread_list_updating = true;
LocalVector<ThreadDebugged *> threadss;
for (HashMap<uint64_t, ThreadDebugged>::Element *T = threads_debugged.front(); T; T = T->next) {
threadss.push_back(&T->value());
}
threadss.sort_custom<ThreadSort>();
threads->clear();
int32_t selected_index = -1;
for (uint32_t i = 0; i < threadss.size(); i++) {
if (debugging_thread_id == threadss[i]->thread_id) {
selected_index = i;
}
threads->add_item(threadss[i]->name);
threads->set_item_metadata(threads->get_item_count() - 1, threadss[i]->thread_id);
}
if (selected_index != -1) {
threads->select(selected_index);
}
thread_list_updating = false;
}
void EditorScriptEditorDebugger::_performance_select() {
perf_draw->update();
}
@ -1357,6 +1518,7 @@ void EditorScriptEditorDebugger::_notification(int p_what) {
//take the chance and re-inspect selected object
Array msg;
msg.push_back("inspect_object");
msg.push_back(process_main_thread_id);
msg.push_back(inspected_object_id);
ppeer->put_var(msg);
}
@ -1377,6 +1539,7 @@ void EditorScriptEditorDebugger::_notification(int p_what) {
Array msg;
msg.push_back("override_camera_2D:transform");
msg.push_back(process_main_thread_id);
msg.push_back(transform);
ppeer->put_var(msg);
@ -1387,6 +1550,7 @@ void EditorScriptEditorDebugger::_notification(int p_what) {
Array msg;
msg.push_back("override_camera_3D:transform");
msg.push_back(process_main_thread_id);
msg.push_back(cam->get_camera_transform());
if (cam->get_projection() == Camera::PROJECTION_ORTHOGONAL) {
msg.push_back(false);
@ -1448,6 +1612,8 @@ void EditorScriptEditorDebugger::_notification(int p_what) {
//EditorNode::get_singleton()->make_bottom_panel_item_visible(this);
//emit_signal("show_debugger",true);
get_process_main_thread_id();
dobreak->set_disabled(false);
tabs->set_current_tab(0);
@ -1512,12 +1678,12 @@ void EditorScriptEditorDebugger::_notification(int p_what) {
}
if (pending_in_queue == 0) {
_parse_message(message_type, message);
_parse_message(message_type, message_thread, message);
message.clear();
}
} else {
if (ppeer->get_available_packet_count() >= 2) {
if (ppeer->get_available_packet_count() >= 3) {
Variant cmd;
Error ret = ppeer->get_var(cmd);
if (ret != OK) {
@ -1541,10 +1707,22 @@ void EditorScriptEditorDebugger::_notification(int p_what) {
ERR_FAIL_COND(cmd.get_type() != Variant::INT);
}
message_thread = cmd;
ret = ppeer->get_var(cmd);
if (ret != OK) {
stop();
ERR_FAIL_COND(ret != OK);
}
if (cmd.get_type() != Variant::INT) {
stop();
ERR_FAIL_COND(cmd.get_type() != Variant::INT);
}
pending_in_queue = cmd;
if (pending_in_queue == 0) {
_parse_message(message_type, Array());
_parse_message(message_type, message_thread, Array());
message.clear();
}
@ -1637,7 +1815,6 @@ void EditorScriptEditorDebugger::start(int p_port, const IP_Address &p_bind_addr
}
set_process(true);
breaked = false;
camera_override = OVERRIDE_NONE;
}
@ -1649,7 +1826,6 @@ void EditorScriptEditorDebugger::unpause() {
void EditorScriptEditorDebugger::stop() {
set_process(false);
breaked = false;
_clear_execution();
server->stop();
@ -1662,6 +1838,10 @@ void EditorScriptEditorDebugger::stop() {
reason->set_text("");
reason->set_tooltip("");
process_main_thread_id = 0;
debugging_thread_id = 0;
threads_debugged.clear();
}
remote_port = 0;
@ -1700,6 +1880,7 @@ void EditorScriptEditorDebugger::_profiler_activate(bool p_enable) {
profiler_signature.clear();
Array msg;
msg.push_back("start_profiling");
msg.push_back(process_main_thread_id);
int max_funcs = EditorSettings::get_singleton()->get("debugger/profiler_frame_max_functions");
max_funcs = CLAMP(max_funcs, 16, 512);
msg.push_back(max_funcs);
@ -1709,6 +1890,7 @@ void EditorScriptEditorDebugger::_profiler_activate(bool p_enable) {
} else {
Array msg;
msg.push_back("stop_profiling");
msg.push_back(process_main_thread_id);
ppeer->put_var(msg);
print_verbose("Ending profiling.");
}
@ -1722,12 +1904,14 @@ void EditorScriptEditorDebugger::_network_profiler_activate(bool p_enable) {
if (p_enable) {
Array msg;
msg.push_back("start_network_profiling");
msg.push_back(process_main_thread_id);
ppeer->put_var(msg);
print_verbose("Starting network profiling.");
} else {
Array msg;
msg.push_back("stop_network_profiling");
msg.push_back(process_main_thread_id);
ppeer->put_var(msg);
print_verbose("Ending network profiling.");
}
@ -1738,7 +1922,7 @@ void EditorScriptEditorDebugger::_profiler_seeked() {
return;
}
if (breaked) {
if (is_breaked()) {
return;
}
debug_break();
@ -1760,6 +1944,7 @@ void EditorScriptEditorDebugger::_stack_dump_frame_selected() {
if (connection.is_valid() && connection->is_connected_to_host()) {
Array msg;
msg.push_back("get_stack_frame_vars");
msg.push_back(debugging_thread_id);
msg.push_back(d["frame"]);
ppeer->put_var(msg);
} else {
@ -1781,7 +1966,7 @@ void EditorScriptEditorDebugger::_export_csv() {
}
String EditorScriptEditorDebugger::get_var_value(const String &p_var) const {
if (!breaked) {
if (!is_breaked()) {
return String();
}
return variables->get_var_value(p_var);
@ -1798,6 +1983,7 @@ int EditorScriptEditorDebugger::_get_node_path_cache(const NodePath &p_path) {
node_path_cache[p_path] = last_path_id;
Array msg;
msg.push_back("live_node_path");
msg.push_back(process_main_thread_id);
msg.push_back(p_path);
msg.push_back(last_path_id);
ppeer->put_var(msg);
@ -1817,6 +2003,7 @@ int EditorScriptEditorDebugger::_get_res_path_cache(const String &p_path) {
res_path_cache[p_path] = last_path_id;
Array msg;
msg.push_back("live_res_path");
msg.push_back(process_main_thread_id);
msg.push_back(p_path);
msg.push_back(last_path_id);
ppeer->put_var(msg);
@ -1846,6 +2033,7 @@ void EditorScriptEditorDebugger::_method_changed(Object *p_base, const StringNam
Array msg;
msg.push_back("live_node_call");
msg.push_back(process_main_thread_id);
msg.push_back(pathid);
msg.push_back(p_name);
for (int i = 0; i < VARIANT_ARG_MAX; i++) {
@ -1865,6 +2053,7 @@ void EditorScriptEditorDebugger::_method_changed(Object *p_base, const StringNam
Array msg;
msg.push_back("live_res_call");
msg.push_back(process_main_thread_id);
msg.push_back(pathid);
msg.push_back(p_name);
for (int i = 0; i < VARIANT_ARG_MAX; i++) {
@ -1893,6 +2082,7 @@ void EditorScriptEditorDebugger::_property_changed(Object *p_base, const StringN
if (res.is_valid() && res->get_path() != String()) {
Array msg;
msg.push_back("live_node_prop_res");
msg.push_back(process_main_thread_id);
msg.push_back(pathid);
msg.push_back(p_property);
msg.push_back(res->get_path());
@ -1901,6 +2091,7 @@ void EditorScriptEditorDebugger::_property_changed(Object *p_base, const StringN
} else {
Array msg;
msg.push_back("live_node_prop");
msg.push_back(process_main_thread_id);
msg.push_back(pathid);
msg.push_back(p_property);
msg.push_back(p_value);
@ -1921,6 +2112,7 @@ void EditorScriptEditorDebugger::_property_changed(Object *p_base, const StringN
if (res2.is_valid() && res2->get_path() != String()) {
Array msg;
msg.push_back("live_res_prop_res");
msg.push_back(process_main_thread_id);
msg.push_back(pathid);
msg.push_back(p_property);
msg.push_back(res2->get_path());
@ -1929,6 +2121,7 @@ void EditorScriptEditorDebugger::_property_changed(Object *p_base, const StringN
} else {
Array msg;
msg.push_back("live_res_prop");
msg.push_back(process_main_thread_id);
msg.push_back(pathid);
msg.push_back(p_property);
msg.push_back(p_value);
@ -1984,12 +2177,23 @@ void EditorScriptEditorDebugger::_live_edit_clear() {
update_live_edit_root();
}
void EditorScriptEditorDebugger::get_process_main_thread_id() {
if (connection.is_valid()) {
Array msg;
msg.push_back("get_main_thread_id");
msg.push_back(0);
msg.push_back(0);
ppeer->put_var(msg);
}
}
void EditorScriptEditorDebugger::update_live_edit_root() {
NodePath np = editor->get_editor_data().get_edited_scene_live_edit_root();
if (connection.is_valid()) {
Array msg;
msg.push_back("live_set_root");
msg.push_back(process_main_thread_id);
msg.push_back(np);
if (editor->get_edited_scene()) {
msg.push_back(editor->get_edited_scene()->get_filename());
@ -2005,6 +2209,7 @@ void EditorScriptEditorDebugger::live_debug_create_node(const NodePath &p_parent
if (live_debug && connection.is_valid()) {
Array msg;
msg.push_back("live_create_node");
msg.push_back(process_main_thread_id);
msg.push_back(p_parent);
msg.push_back(p_type);
msg.push_back(p_name);
@ -2016,6 +2221,7 @@ void EditorScriptEditorDebugger::live_debug_instance_node(const NodePath &p_pare
if (live_debug && connection.is_valid()) {
Array msg;
msg.push_back("live_instance_node");
msg.push_back(process_main_thread_id);
msg.push_back(p_parent);
msg.push_back(p_path);
msg.push_back(p_name);
@ -2026,6 +2232,7 @@ void EditorScriptEditorDebugger::live_debug_remove_node(const NodePath &p_at) {
if (live_debug && connection.is_valid()) {
Array msg;
msg.push_back("live_remove_node");
msg.push_back(process_main_thread_id);
msg.push_back(p_at);
ppeer->put_var(msg);
}
@ -2034,6 +2241,7 @@ void EditorScriptEditorDebugger::live_debug_remove_and_keep_node(const NodePath
if (live_debug && connection.is_valid()) {
Array msg;
msg.push_back("live_remove_and_keep_node");
msg.push_back(process_main_thread_id);
msg.push_back(p_at);
msg.push_back(p_keep_id);
ppeer->put_var(msg);
@ -2043,6 +2251,7 @@ void EditorScriptEditorDebugger::live_debug_restore_node(ObjectID p_id, const No
if (live_debug && connection.is_valid()) {
Array msg;
msg.push_back("live_restore_node");
msg.push_back(process_main_thread_id);
msg.push_back(p_id);
msg.push_back(p_at);
msg.push_back(p_at_pos);
@ -2053,6 +2262,7 @@ void EditorScriptEditorDebugger::live_debug_duplicate_node(const NodePath &p_at,
if (live_debug && connection.is_valid()) {
Array msg;
msg.push_back("live_duplicate_node");
msg.push_back(process_main_thread_id);
msg.push_back(p_at);
msg.push_back(p_new_name);
ppeer->put_var(msg);
@ -2062,6 +2272,7 @@ void EditorScriptEditorDebugger::live_debug_reparent_node(const NodePath &p_at,
if (live_debug && connection.is_valid()) {
Array msg;
msg.push_back("live_reparent_node");
msg.push_back(process_main_thread_id);
msg.push_back(p_at);
msg.push_back(p_new_place);
msg.push_back(p_new_name);
@ -2079,6 +2290,7 @@ void EditorScriptEditorDebugger::set_camera_override(CameraOverride p_override)
if (connection.is_valid()) {
Array msg;
msg.push_back("override_camera_2D:set");
msg.push_back(process_main_thread_id);
msg.push_back(true);
ppeer->put_var(msg);
}
@ -2086,6 +2298,7 @@ void EditorScriptEditorDebugger::set_camera_override(CameraOverride p_override)
if (connection.is_valid()) {
Array msg;
msg.push_back("override_camera_2D:set");
msg.push_back(process_main_thread_id);
msg.push_back(false);
ppeer->put_var(msg);
}
@ -2093,6 +2306,7 @@ void EditorScriptEditorDebugger::set_camera_override(CameraOverride p_override)
if (connection.is_valid()) {
Array msg;
msg.push_back("override_camera_3D:set");
msg.push_back(process_main_thread_id);
msg.push_back(true);
ppeer->put_var(msg);
}
@ -2100,6 +2314,7 @@ void EditorScriptEditorDebugger::set_camera_override(CameraOverride p_override)
if (connection.is_valid()) {
Array msg;
msg.push_back("override_camera_3D:set");
msg.push_back(process_main_thread_id);
msg.push_back(false);
ppeer->put_var(msg);
}
@ -2112,6 +2327,7 @@ void EditorScriptEditorDebugger::set_breakpoint(const String &p_path, int p_line
if (connection.is_valid()) {
Array msg;
msg.push_back("breakpoint");
msg.push_back(process_main_thread_id);
msg.push_back(p_path);
msg.push_back(p_line);
msg.push_back(p_enabled);
@ -2123,6 +2339,7 @@ void EditorScriptEditorDebugger::reload_scripts() {
if (connection.is_valid()) {
Array msg;
msg.push_back("reload_scripts");
msg.push_back(process_main_thread_id);
ppeer->put_var(msg);
}
}
@ -2204,11 +2421,11 @@ void EditorScriptEditorDebugger::_paused() {
ERR_FAIL_COND(connection.is_null());
ERR_FAIL_COND(!connection->is_connected_to_host());
if (!breaked && EditorNode::get_singleton()->get_pause_button()->is_pressed()) {
if (!is_breaked() && EditorNode::get_singleton()->get_pause_button()->is_pressed()) {
debug_break();
}
if (breaked && !EditorNode::get_singleton()->get_pause_button()->is_pressed()) {
if (is_breaked() && !EditorNode::get_singleton()->get_pause_button()->is_pressed()) {
debug_continue();
}
}
@ -2413,14 +2630,20 @@ void EditorScriptEditorDebugger::_bind_methods() {
ClassDB::bind_method(D_METHOD("_scene_tree_property_select_object"), &EditorScriptEditorDebugger::_scene_tree_property_select_object);
ClassDB::bind_method(D_METHOD("_scene_tree_property_value_edited"), &EditorScriptEditorDebugger::_scene_tree_property_value_edited);
ClassDB::bind_method(D_METHOD("_select_thread"), &EditorScriptEditorDebugger::_select_thread);
ADD_SIGNAL(MethodInfo("goto_script_line"));
ADD_SIGNAL(MethodInfo("set_execution", PropertyInfo("script"), PropertyInfo(Variant::INT, "line")));
ADD_SIGNAL(MethodInfo("clear_execution", PropertyInfo("script")));
ADD_SIGNAL(MethodInfo("breaked", PropertyInfo(Variant::BOOL, "reallydid"), PropertyInfo(Variant::BOOL, "can_debug")));
ADD_SIGNAL(MethodInfo("breaked", PropertyInfo(Variant::BOOL, "reallydid"), PropertyInfo(Variant::BOOL, "can_debug"), PropertyInfo(Variant::STRING, "reason"), PropertyInfo(Variant::BOOL, "has_stackdump")));
ADD_SIGNAL(MethodInfo("show_debugger", PropertyInfo(Variant::BOOL, "reallydid")));
}
EditorScriptEditorDebugger::EditorScriptEditorDebugger(EditorNode *p_editor) {
debugging_thread_id = 0;
process_main_thread_id = 0;
thread_list_updating = false;
add_theme_constant_override("margin_left", -EditorNode::get_singleton()->get_gui_base()->get_theme_stylebox("BottomPanelDebuggerOverride", "EditorStyles")->get_margin(MARGIN_LEFT));
add_theme_constant_override("margin_right", -EditorNode::get_singleton()->get_gui_base()->get_theme_stylebox("BottomPanelDebuggerOverride", "EditorStyles")->get_margin(MARGIN_RIGHT));
@ -2510,15 +2733,27 @@ EditorScriptEditorDebugger::EditorScriptEditorDebugger(EditorNode *p_editor) {
vbc->add_child(sc);
sc->set_v_size_flags(SIZE_EXPAND_FILL);
VBoxContainer *stack_vb = memnew(VBoxContainer);
stack_vb->set_h_size_flags(SIZE_EXPAND_FILL);
sc->add_child(stack_vb);
HBoxContainer *thread_hb = memnew(HBoxContainer);
stack_vb->add_child(thread_hb);
thread_hb->add_child(memnew(Label(TTR("Thread:"))));
threads = memnew(OptionButton);
thread_hb->add_child(threads);
threads->set_h_size_flags(SIZE_EXPAND_FILL);
threads->connect("item_selected", this, "_select_thread");
stack_dump = memnew(Tree);
stack_dump->set_allow_reselect(true);
stack_dump->set_columns(1);
stack_dump->set_column_titles_visible(true);
stack_dump->set_column_title(0, TTR("Stack Frames"));
stack_dump->set_v_size_flags(SIZE_EXPAND_FILL);
stack_dump->set_h_size_flags(SIZE_EXPAND_FILL);
stack_dump->set_hide_root(true);
stack_dump->connect("cell_selected", this, "_stack_dump_frame_selected");
sc->add_child(stack_dump);
stack_vb->add_child(stack_dump);
VBoxContainer *inspector_vbox = memnew(VBoxContainer);
sc->add_child(inspector_vbox);
@ -2548,8 +2783,6 @@ EditorScriptEditorDebugger::EditorScriptEditorDebugger(EditorNode *p_editor) {
variables = memnew(EditorScriptEditorDebuggerVariables);
breaked = false;
tabs->add_child(dbg);
}

View File

@ -32,21 +32,21 @@
#include "scene/gui/margin_container.h"
#include "core/variant/array.h"
#include "core/containers/hash_map.h"
#include "core/containers/list.h"
#include "core/containers/rb_map.h"
#include "core/containers/rb_set.h"
#include "core/containers/vector.h"
#include "core/math/vector2.h"
#include "core/string/node_path.h"
#include "core/object/object.h"
#include "core/object/object_id.h"
#include "core/object/reference.h"
#include "core/object/resource.h"
#include "core/containers/rb_set.h"
#include "core/string/node_path.h"
#include "core/string/string_name.h"
#include "core/string/ustring.h"
#include "core/variant/array.h"
#include "core/variant/variant.h"
#include "core/containers/vector.h"
class Tree;
class EditorNode;
@ -73,6 +73,7 @@ class Script;
class StreamPeerTCP;
class TCP_Server;
class VBoxContainer;
class OptionButton;
class EditorScriptEditorDebugger : public MarginContainer {
GDCLASS(EditorScriptEditorDebugger, MarginContainer);
@ -179,6 +180,7 @@ private:
Tree *stack_dump;
LineEdit *search;
OptionButton *threads;
EditorInspector *inspector;
Ref<TCP_Server> server;
@ -186,6 +188,7 @@ private:
Ref<PacketPeerStream> ppeer;
String message_type;
uint64_t message_thread;
Array message;
int pending_in_queue;
@ -198,10 +201,38 @@ private:
EditorNode *editor;
bool breaked;
bool live_debug;
uint64_t debugging_thread_id;
uint64_t process_main_thread_id;
struct ThreadDebugged {
String name;
String error;
bool can_debug;
bool has_stackdump;
uint32_t debug_order;
uint64_t thread_id;
ThreadDebugged() {
can_debug = false;
has_stackdump = false;
debug_order = 0;
thread_id = 0;
}
};
struct ThreadSort {
bool operator()(const ThreadDebugged *a, const ThreadDebugged *b) const {
return a->debug_order < b->debug_order;
}
};
HashMap<uint64_t, ThreadDebugged> threads_debugged;
bool thread_list_updating;
void _select_thread(int p_index);
CameraOverride camera_override;
void _performance_draw();
@ -214,8 +245,9 @@ private:
void _scene_tree_rmb_selected(const Vector2 &p_position);
void _file_selected(const String &p_file);
void _scene_tree_request();
void _parse_message(const String &p_msg, const Array &p_data);
void _parse_message(const String &p_msg, uint64_t p_thread_id, const Array &p_data);
void _set_reason_text(const String &p_reason, MessageType p_type);
void _update_buttons_state();
void _scene_tree_property_select_object(ObjectID p_object);
void _scene_tree_property_value_edited(const String &p_prop, const Variant &p_value);
int _update_scene_tree(TreeItem *parent, const Array &nodes, int current_index);
@ -258,6 +290,8 @@ private:
void _clear_execution();
void _thread_debug_enter(uint64_t p_thread_id);
protected:
void _notification(int p_what);
static void _bind_methods();
@ -276,6 +310,10 @@ public:
void debug_break();
void debug_continue();
bool is_breaked() const { return threads_debugged.size() > 0; }
bool is_debuggable() const { return threads_debugged.size() > 0 && threads_debugged[debugging_thread_id].can_debug; }
bool is_session_active();
String get_var_value(const String &p_var) const;
void set_live_debugging(bool p_enable);
@ -296,6 +334,7 @@ public:
void set_breakpoint(const String &p_path, int p_line, bool p_enabled);
void get_process_main_thread_id();
void update_live_edit_root();
void set_hide_on_stop(bool p_hide);

View File

@ -194,7 +194,7 @@ String EditorScriptEditor::_get_debug_tooltip(const String &p_text, Node *_se) {
}
}
void EditorScriptEditor::_breaked(bool p_breaked, bool p_can_debug) {
void EditorScriptEditor::_breaked(bool p_breaked, bool p_can_debug, const String &p_reason, bool p_has_stackdump) {
if (bool(EditorSettings::get_singleton()->get("text_editor/external/use_external_editor"))) {
return;
}

View File

@ -289,7 +289,7 @@ class EditorScriptEditor : public PanelContainer {
void _goto_script_line(REF p_script, int p_line);
void _set_execution(REF p_script, int p_line);
void _clear_execution(REF p_script);
void _breaked(bool p_breaked, bool p_can_debug);
void _breaked(bool p_breaked, bool p_can_debug, const String &p_reason, bool p_has_stackdump);
void _show_debugger(bool p_show);
void _script_created(Ref<Script> p_script);

View File

@ -1425,12 +1425,12 @@ void GDScriptLanguage::profiling_start() {
SelfList<GDScriptFunction> *elem = function_list.first();
while (elem) {
elem->self()->profile.call_count = 0;
elem->self()->profile.self_time = 0;
elem->self()->profile.total_time = 0;
elem->self()->profile.frame_call_count = 0;
elem->self()->profile.frame_self_time = 0;
elem->self()->profile.frame_total_time = 0;
elem->self()->profile.call_count.set(0);
elem->self()->profile.self_time.set(0);
elem->self()->profile.total_time.set(0);
elem->self()->profile.frame_call_count.set(0);
elem->self()->profile.frame_self_time.set(0);
elem->self()->profile.frame_total_time.set(0);
elem->self()->profile.last_frame_call_count = 0;
elem->self()->profile.last_frame_self_time = 0;
elem->self()->profile.last_frame_total_time = 0;
@ -1460,9 +1460,9 @@ int GDScriptLanguage::profiling_get_accumulated_data(ProfilingInfo *p_info_arr,
if (current >= p_info_max) {
break;
}
p_info_arr[current].call_count = elem->self()->profile.call_count;
p_info_arr[current].self_time = elem->self()->profile.self_time;
p_info_arr[current].total_time = elem->self()->profile.total_time;
p_info_arr[current].call_count = elem->self()->profile.call_count.get();
p_info_arr[current].self_time = elem->self()->profile.self_time.get();
p_info_arr[current].total_time = elem->self()->profile.total_time.get();
p_info_arr[current].signature = elem->self()->profile.signature;
elem = elem->next();
current++;
@ -1684,12 +1684,12 @@ void GDScriptLanguage::frame() {
SelfList<GDScriptFunction> *elem = function_list.first();
while (elem) {
elem->self()->profile.last_frame_call_count = elem->self()->profile.frame_call_count;
elem->self()->profile.last_frame_self_time = elem->self()->profile.frame_self_time;
elem->self()->profile.last_frame_total_time = elem->self()->profile.frame_total_time;
elem->self()->profile.frame_call_count = 0;
elem->self()->profile.frame_self_time = 0;
elem->self()->profile.frame_total_time = 0;
elem->self()->profile.last_frame_call_count = elem->self()->profile.frame_call_count.get();
elem->self()->profile.last_frame_self_time = elem->self()->profile.frame_self_time.get();
elem->self()->profile.last_frame_total_time = elem->self()->profile.frame_total_time.get();
elem->self()->profile.frame_call_count.set(0);
elem->self()->profile.frame_self_time.set(0);
elem->self()->profile.frame_total_time.set(0);
elem = elem->next();
}
@ -2052,6 +2052,13 @@ GDScriptWarning::Code GDScriptWarning::get_code_from_name(const String &p_name)
#endif // DEBUG_ENABLED
#if !defined(NO_THREADS)
thread_local GDScriptLanguage::CallStack GDScriptLanguage::_call_stack;
thread_local int GDScriptLanguage::_debug_parse_err_line = -1;
thread_local String GDScriptLanguage::_debug_parse_err_file;
thread_local String GDScriptLanguage::_debug_error;
#endif
GDScriptLanguage::GDScriptLanguage() {
GDScriptTokenizer::initialize();
@ -2070,7 +2077,6 @@ GDScriptLanguage::GDScriptLanguage() {
profiling = false;
script_frame_time = 0;
_debug_call_stack_pos = 0;
int dmcs = GLOBAL_DEF("debug/settings/gdscript/max_call_stack", 1024);
ProjectSettings::get_singleton()->set_custom_property_info("debug/settings/gdscript/max_call_stack", PropertyInfo(Variant::INT, "debug/settings/gdscript/max_call_stack", PROPERTY_HINT_RANGE, "1024,4096,1,or_greater")); //minimum is 1024
@ -2078,11 +2084,8 @@ GDScriptLanguage::GDScriptLanguage() {
//debugging enabled!
_debug_max_call_stack = dmcs;
_call_stack = memnew_arr(CallLevel, _debug_max_call_stack + 1);
} else {
_debug_max_call_stack = 0;
_call_stack = nullptr;
}
#ifdef DEBUG_ENABLED
@ -2101,9 +2104,7 @@ GDScriptLanguage::GDScriptLanguage() {
GDScriptLanguage::~GDScriptLanguage() {
GDScriptTokenizer::terminate();
if (_call_stack) {
memdelete_arr(_call_stack);
}
_call_stack.free();
// Clear dependencies between scripts, to ensure cyclic references are broken (to avoid leaks at exit).
SelfList<GDScript> *s = script_list.first();

View File

@ -334,12 +334,38 @@ class GDScriptLanguage : public ScriptLanguage {
int *line;
};
struct CallStack {
CallLevel *levels;
int stack_pos;
void free() {
if (levels) {
memdelete(levels);
levels = nullptr;
}
}
CallStack() {
levels = nullptr;
stack_pos = 0;
}
~CallStack() {
free();
}
};
#if !defined(NO_THREADS)
static thread_local int _debug_parse_err_line;
static thread_local String _debug_parse_err_file;
static thread_local String _debug_error;
static thread_local CallStack _call_stack;
#else
int _debug_parse_err_line;
String _debug_parse_err_file;
String _debug_error;
int _debug_call_stack_pos;
CallStack _call_stack;
#endif
int _debug_max_call_stack;
CallLevel *_call_stack;
void _add_global(const StringName &p_name, const Variant &p_value);
@ -365,59 +391,51 @@ public:
bool debug_break_parse(const String &p_file, int p_line, const String &p_error);
_FORCE_INLINE_ void enter_function(GDScriptInstance *p_instance, GDScriptFunction *p_function, Variant *p_stack, int *p_ip, int *p_line) {
if (Thread::get_main_id() != Thread::get_caller_id()) {
return; //no support for other threads than main for now
if (unlikely(_call_stack.levels == nullptr)) {
_call_stack.levels = memnew_arr(CallLevel, _debug_max_call_stack + 1);
}
if (ScriptDebugger::get_singleton()->get_lines_left() > 0 && ScriptDebugger::get_singleton()->get_depth() >= 0) {
ScriptDebugger::get_singleton()->set_depth(ScriptDebugger::get_singleton()->get_depth() + 1);
}
if (_debug_call_stack_pos >= _debug_max_call_stack) {
if (_call_stack.stack_pos >= _debug_max_call_stack) {
//stack overflow
_debug_error = "Stack Overflow (Stack Size: " + itos(_debug_max_call_stack) + ")";
ScriptDebugger::get_singleton()->debug(this);
return;
}
_call_stack[_debug_call_stack_pos].stack = p_stack;
_call_stack[_debug_call_stack_pos].instance = p_instance;
_call_stack[_debug_call_stack_pos].function = p_function;
_call_stack[_debug_call_stack_pos].ip = p_ip;
_call_stack[_debug_call_stack_pos].line = p_line;
_debug_call_stack_pos++;
_call_stack.levels[_call_stack.stack_pos].stack = p_stack;
_call_stack.levels[_call_stack.stack_pos].instance = p_instance;
_call_stack.levels[_call_stack.stack_pos].function = p_function;
_call_stack.levels[_call_stack.stack_pos].ip = p_ip;
_call_stack.levels[_call_stack.stack_pos].line = p_line;
_call_stack.stack_pos++;
}
_FORCE_INLINE_ void exit_function() {
if (Thread::get_main_id() != Thread::get_caller_id()) {
return; //no support for other threads than main for now
}
if (ScriptDebugger::get_singleton()->get_lines_left() > 0 && ScriptDebugger::get_singleton()->get_depth() >= 0) {
ScriptDebugger::get_singleton()->set_depth(ScriptDebugger::get_singleton()->get_depth() - 1);
}
if (_debug_call_stack_pos == 0) {
if (_call_stack.stack_pos == 0) {
_debug_error = "Stack Underflow (Engine Bug)";
ScriptDebugger::get_singleton()->debug(this);
return;
}
_debug_call_stack_pos--;
_call_stack.stack_pos--;
}
virtual Vector<StackInfo> debug_get_current_stack_info() {
if (Thread::get_main_id() != Thread::get_caller_id()) {
return Vector<StackInfo>();
}
Vector<StackInfo> csi;
csi.resize(_debug_call_stack_pos);
for (int i = 0; i < _debug_call_stack_pos; i++) {
csi.write[_debug_call_stack_pos - i - 1].line = _call_stack[i].line ? *_call_stack[i].line : 0;
if (_call_stack[i].function) {
csi.write[_debug_call_stack_pos - i - 1].func = _call_stack[i].function->get_name();
csi.write[_debug_call_stack_pos - i - 1].file = _call_stack[i].function->get_script()->get_path();
csi.resize(_call_stack.stack_pos);
for (int i = 0; i < _call_stack.stack_pos; i++) {
csi.write[_call_stack.stack_pos - i - 1].line = _call_stack.levels[i].line ? *_call_stack.levels[i].line : 0;
if (_call_stack.levels[i].function) {
csi.write[_call_stack.stack_pos - i - 1].func = _call_stack.levels[i].function->get_name();
csi.write[_call_stack.stack_pos - i - 1].file = _call_stack.levels[i].function->get_script()->get_path();
}
}
return csi;

View File

@ -31,9 +31,9 @@
#include "gdscript.h"
#include "core/config/engine.h"
#include "core/config/project_settings.h"
#include "core/global_constants.h"
#include "core/os/file_access.h"
#include "core/config/project_settings.h"
#include "gdscript_compiler.h"
#ifdef TOOLS_ENABLED
@ -209,6 +209,12 @@ bool GDScriptLanguage::debug_break_parse(const String &p_file, int p_line, const
_debug_parse_err_file = p_file;
_debug_error = p_error;
ScriptDebugger::get_singleton()->debug(this, false, true);
#if !defined(NO_THREADS)
// Because this is thread local, clear the memory afterwards.
_debug_parse_err_file = String();
_debug_error = String();
#endif
return true;
} else {
return false;
@ -216,12 +222,18 @@ bool GDScriptLanguage::debug_break_parse(const String &p_file, int p_line, const
}
bool GDScriptLanguage::debug_break(const String &p_error, bool p_allow_continue) {
if (ScriptDebugger::get_singleton() && Thread::get_caller_id() == Thread::get_main_id()) {
if (ScriptDebugger::get_singleton()) {
_debug_parse_err_line = -1;
_debug_parse_err_file = "";
_debug_error = p_error;
bool is_error_breakpoint = p_error != "Breakpoint";
ScriptDebugger::get_singleton()->debug(this, p_allow_continue, is_error_breakpoint);
#if !defined(NO_THREADS)
// Because this is thread local, clear the memory afterwards.
_debug_parse_err_file = String();
_debug_error = String();
#endif
return true;
} else {
return false;
@ -237,53 +249,53 @@ int GDScriptLanguage::debug_get_stack_level_count() const {
return 1;
}
return _debug_call_stack_pos;
return _call_stack.stack_pos;
}
int GDScriptLanguage::debug_get_stack_level_line(int p_level) const {
if (_debug_parse_err_line >= 0) {
return _debug_parse_err_line;
}
ERR_FAIL_INDEX_V(p_level, _debug_call_stack_pos, -1);
ERR_FAIL_INDEX_V(p_level, _call_stack.stack_pos, -1);
int l = _debug_call_stack_pos - p_level - 1;
int l = _call_stack.stack_pos - p_level - 1;
return *(_call_stack[l].line);
return *(_call_stack.levels[l].line);
}
String GDScriptLanguage::debug_get_stack_level_function(int p_level) const {
if (_debug_parse_err_line >= 0) {
return "";
}
ERR_FAIL_INDEX_V(p_level, _debug_call_stack_pos, "");
int l = _debug_call_stack_pos - p_level - 1;
return _call_stack[l].function->get_name();
ERR_FAIL_INDEX_V(p_level, _call_stack.stack_pos, "");
int l = _call_stack.stack_pos - p_level - 1;
return _call_stack.levels[l].function->get_name();
}
String GDScriptLanguage::debug_get_stack_level_source(int p_level) const {
if (_debug_parse_err_line >= 0) {
return _debug_parse_err_file;
}
ERR_FAIL_INDEX_V(p_level, _debug_call_stack_pos, "");
int l = _debug_call_stack_pos - p_level - 1;
return _call_stack[l].function->get_source();
ERR_FAIL_INDEX_V(p_level, _call_stack.stack_pos, "");
int l = _call_stack.stack_pos - p_level - 1;
return _call_stack.levels[l].function->get_source();
}
void GDScriptLanguage::debug_get_stack_level_locals(int p_level, List<String> *p_locals, List<Variant> *p_values, int p_max_subitems, int p_max_depth) {
if (_debug_parse_err_line >= 0) {
return;
}
ERR_FAIL_INDEX(p_level, _debug_call_stack_pos);
int l = _debug_call_stack_pos - p_level - 1;
ERR_FAIL_INDEX(p_level, _call_stack.stack_pos);
int l = _call_stack.stack_pos - p_level - 1;
GDScriptFunction *f = _call_stack[l].function;
GDScriptFunction *f = _call_stack.levels[l].function;
List<Pair<StringName, int>> locals;
f->debug_get_stack_member_state(*_call_stack[l].line, &locals);
f->debug_get_stack_member_state(*_call_stack.levels[l].line, &locals);
for (List<Pair<StringName, int>>::Element *E = locals.front(); E; E = E->next()) {
p_locals->push_back(E->get().first);
p_values->push_back(_call_stack[l].stack[E->get().second]);
p_values->push_back(_call_stack.levels[l].stack[E->get().second]);
}
}
void GDScriptLanguage::debug_get_stack_level_members(int p_level, List<String> *p_members, List<Variant> *p_values, int p_max_subitems, int p_max_depth) {
@ -291,10 +303,10 @@ void GDScriptLanguage::debug_get_stack_level_members(int p_level, List<String> *
return;
}
ERR_FAIL_INDEX(p_level, _debug_call_stack_pos);
int l = _debug_call_stack_pos - p_level - 1;
ERR_FAIL_INDEX(p_level, _call_stack.stack_pos);
int l = _call_stack.stack_pos - p_level - 1;
GDScriptInstance *instance = _call_stack[l].instance;
GDScriptInstance *instance = _call_stack.levels[l].instance;
if (!instance) {
return;
@ -316,10 +328,10 @@ ScriptInstance *GDScriptLanguage::debug_get_stack_level_instance(int p_level) {
return nullptr;
}
ERR_FAIL_INDEX_V(p_level, _debug_call_stack_pos, nullptr);
ERR_FAIL_INDEX_V(p_level, _call_stack.stack_pos, nullptr);
int l = _debug_call_stack_pos - p_level - 1;
ScriptInstance *instance = _call_stack[l].instance;
int l = _call_stack.stack_pos - p_level - 1;
ScriptInstance *instance = _call_stack.levels[l].instance;
return instance;
}

View File

@ -411,8 +411,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
if (GDScriptLanguage::get_singleton()->profiling) {
function_start_time = OS::get_singleton()->get_ticks_usec();
function_call_time = 0;
profile.call_count++;
profile.frame_call_count++;
profile.call_count.increment();
profile.frame_call_count.increment();
}
bool exit_ok = false;
bool yielded = false;
@ -1496,7 +1496,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
do_break = true;
}
if (do_break) {
if (unlikely(do_break)) {
GDScriptLanguage::get_singleton()->debug_break("Breakpoint", true);
}
@ -1559,11 +1559,14 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
#ifdef DEBUG_ENABLED
if (GDScriptLanguage::get_singleton()->profiling) {
uint64_t time_taken = OS::get_singleton()->get_ticks_usec() - function_start_time;
profile.total_time += time_taken;
profile.self_time += time_taken - function_call_time;
profile.frame_total_time += time_taken;
profile.frame_self_time += time_taken - function_call_time;
GDScriptLanguage::get_singleton()->script_frame_time += time_taken - function_call_time;
profile.total_time.add(time_taken);
profile.self_time.add(time_taken - function_call_time);
profile.frame_total_time.add(time_taken);
profile.frame_self_time.add(time_taken - function_call_time);
if (Thread::get_caller_id() == Thread::get_main_id()) {
GDScriptLanguage::get_singleton()->script_frame_time += time_taken - function_call_time;
}
}
// Check if this is the last time the function is resuming from yield
@ -1707,12 +1710,12 @@ GDScriptFunction::GDScriptFunction() :
GDScriptLanguage::get_singleton()->function_list.add(&function_list);
GDScriptLanguage::get_singleton()->lock.unlock();
profile.call_count = 0;
profile.self_time = 0;
profile.total_time = 0;
profile.frame_call_count = 0;
profile.frame_self_time = 0;
profile.frame_total_time = 0;
profile.call_count.set(0);
profile.self_time.set(0);
profile.total_time.set(0);
profile.frame_call_count.set(0);
profile.frame_self_time.set(0);
profile.frame_total_time.set(0);
profile.last_frame_call_count = 0;
profile.last_frame_self_time = 0;
profile.last_frame_total_time = 0;

View File

@ -278,12 +278,12 @@ private:
struct Profile {
StringName signature;
uint64_t call_count;
uint64_t self_time;
uint64_t total_time;
uint64_t frame_call_count;
uint64_t frame_self_time;
uint64_t frame_total_time;
SafeNumeric<uint64_t> call_count;
SafeNumeric<uint64_t> self_time;
SafeNumeric<uint64_t> total_time;
SafeNumeric<uint64_t> frame_call_count;
SafeNumeric<uint64_t> frame_self_time;
SafeNumeric<uint64_t> frame_total_time;
uint64_t last_frame_call_count;
uint64_t last_frame_self_time;
uint64_t last_frame_total_time;

View File

@ -31,13 +31,13 @@
#include "script_debugger_remote.h"
#include "core/config/engine.h"
#include "core/config/project_settings.h"
#include "core/input/input.h"
#include "core/io/ip.h"
#include "core/io/marshalls.h"
#include "core/io/resource_loader.h"
#include "core/io/resource_saver.h"
#include "core/input/input.h"
#include "core/os/os.h"
#include "core/config/project_settings.h"
#include "scene/main/node.h"
#include "scene/main/scene_tree.h"
#include "scene/main/viewport.h"
@ -53,6 +53,7 @@ void ScriptDebuggerRemote::_send_video_memory() {
usage.sort();
packet_peer_stream->put_var("message:video_mem");
packet_peer_stream->put_var(Thread::get_caller_id());
packet_peer_stream->put_var(usage.size() * 4);
for (List<ResourceUsage>::Element *E = usage.front(); E; E = E->next()) {
@ -109,8 +110,9 @@ void ScriptDebuggerRemote::_put_variable(const String &p_name, const Variant &p_
int len = 0;
Error err = encode_variant(var, nullptr, len, true);
if (err != OK)
if (err != OK) {
ERR_PRINT("Failed to encode variant.");
}
if (len > packet_peer_stream->get_output_buffer_max_size()) { //limit to max size
packet_peer_stream->put_var(Variant());
@ -136,22 +138,43 @@ void ScriptDebuggerRemote::debug(ScriptLanguage *p_script, bool p_can_continue,
return;
}
ERR_FAIL_COND_MSG(!tcp_client->is_connected_to_host(), "Script Debugger failed to connect, but being used anyway.");
{
MutexLock lock(mutex);
// Tests that require mutex.
ERR_FAIL_COND_MSG(!tcp_client->is_connected_to_host(), "Script Debugger failed to connect, but being used anyway.");
}
if (allow_focus_steal_pid) {
OS::get_singleton()->enable_for_stealing_focus(allow_focus_steal_pid);
}
packet_peer_stream->put_var("debug_enter");
packet_peer_stream->put_var(2);
packet_peer_stream->put_var(Thread::get_caller_id());
packet_peer_stream->put_var(4);
packet_peer_stream->put_var(p_can_continue);
packet_peer_stream->put_var(p_script->debug_get_error());
packet_peer_stream->put_var(p_script->debug_get_stack_level_count() > 0);
packet_peer_stream->put_var(Thread::is_main_thread() ? String(RTR("Main Thread")) : String::num(Thread::get_caller_id()));
skip_profile_frame = true; // to avoid super long frame time for the frame
Input::MouseMode mouse_mode = Input::get_singleton()->get_mouse_mode();
if (mouse_mode != Input::MOUSE_MODE_VISIBLE) {
Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE);
if (Thread::is_main_thread()) {
if (mouse_mode != Input::MOUSE_MODE_VISIBLE) {
Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE);
}
} else {
MutexLock mutex_lock(mutex);
if (!incoming_messages.has(Thread::get_caller_id())) {
incoming_messages.insert(Thread::get_caller_id(), List<Message>());
}
if (!outgoing_messages.has(Thread::get_caller_id())) {
outgoing_messages.insert(Thread::get_caller_id(), List<Message>());
}
}
uint64_t loop_begin_usec = 0;
@ -161,22 +184,22 @@ void ScriptDebuggerRemote::debug(ScriptLanguage *p_script, bool p_can_continue,
_get_output();
if (packet_peer_stream->get_available_packet_count() > 0) {
Variant var;
Error err = packet_peer_stream->get_var(var);
_poll_messages();
ERR_CONTINUE(err != OK);
ERR_CONTINUE(var.get_type() != Variant::ARRAY);
Array cmd = var;
if (_has_incoming_messages()) {
Array cmd = _get_incoming_message();
ERR_CONTINUE(cmd.size() == 0);
ERR_CONTINUE(cmd[0].get_type() != Variant::STRING);
//cmd[1] is not the thread id here, was removed when polling
String command = cmd[0];
MutexLock mutex_lock(mutex);
if (command == "get_stack_dump") {
packet_peer_stream->put_var("stack_dump");
packet_peer_stream->put_var(Thread::get_caller_id());
int slc = p_script->debug_get_stack_level_count();
packet_peer_stream->put_var(slc);
@ -216,6 +239,7 @@ void ScriptDebuggerRemote::debug(ScriptLanguage *p_script, bool p_can_continue,
ERR_CONTINUE(globals.size() != globals_vals.size());
packet_peer_stream->put_var("stack_frame_vars");
packet_peer_stream->put_var(Thread::get_caller_id());
packet_peer_stream->put_var(3 + (locals.size() + members.size() + globals.size()) * 2);
{ //locals
@ -277,6 +301,8 @@ void ScriptDebuggerRemote::debug(ScriptLanguage *p_script, bool p_can_continue,
} else if (command == "break") {
ERR_PRINT("Got break when already broke!");
break;
//Below everything should only happen on the main thread. Should probably add a check.
} else if (command == "request_scene_tree") {
#ifdef DEBUG_ENABLED
if (scene_tree) {
@ -342,10 +368,11 @@ void ScriptDebuggerRemote::debug(ScriptLanguage *p_script, bool p_can_continue,
} else {
_parse_live_edit(cmd);
}
} else {
OS::get_singleton()->delay_usec(10000);
OS::get_singleton()->process_and_drop_events();
if (Thread::is_main_thread()) {
OS::get_singleton()->process_and_drop_events();
}
}
// This is for the camera override to stay live even when the game is paused from the editor
@ -356,19 +383,106 @@ void ScriptDebuggerRemote::debug(ScriptLanguage *p_script, bool p_can_continue,
}
}
mutex.lock();
packet_peer_stream->put_var("debug_exit");
packet_peer_stream->put_var(Thread::get_caller_id());
packet_peer_stream->put_var(0);
mutex.unlock();
if (mouse_mode != Input::MOUSE_MODE_VISIBLE) {
Input::get_singleton()->set_mouse_mode(mouse_mode);
if (Thread::is_main_thread()) {
if (mouse_mode != Input::MOUSE_MODE_VISIBLE) {
Input::get_singleton()->set_mouse_mode(mouse_mode);
}
} else {
MutexLock mutex_lock(mutex);
incoming_messages.erase(Thread::get_caller_id());
outgoing_messages.erase(Thread::get_caller_id());
}
}
void ScriptDebuggerRemote::_poll_messages() {
MutexLock mutex_lock(mutex);
while (packet_peer_stream->get_available_packet_count() > 0) {
Variant var;
Error err = packet_peer_stream->get_var(var);
ERR_CONTINUE(err != OK);
ERR_CONTINUE(var.get_type() != Variant::ARRAY);
Array cmd = var;
ERR_CONTINUE(cmd.size() < 2);
ERR_CONTINUE(cmd[0].get_type() != Variant::STRING);
ERR_CONTINUE(cmd[1].get_type() != Variant::INT);
String command = cmd[0];
//Special case, as the editor doesn't yet have a proper thread id -> can't request anbything from main thread -> no remote tree etc
if (command == "get_main_thread_id") {
packet_peer_stream->put_var("main_thread_id");
packet_peer_stream->put_var(Thread::get_main_id());
packet_peer_stream->put_var(0);
continue;
}
Thread::ID thread = cmd[1];
if (!incoming_messages.has(thread)) {
continue; // This thread is not around to receive the messages
}
Message msg;
msg.message = command;
msg.data.push_back(command);
for (int i = 2; i < cmd.size(); ++i) {
msg.data.push_back(cmd[i]);
}
incoming_messages[thread].push_back(msg);
}
}
bool ScriptDebuggerRemote::_has_incoming_messages() {
MutexLock mutex_lock(mutex);
return incoming_messages.has(Thread::get_caller_id()) && !incoming_messages[Thread::get_caller_id()].empty();
}
Array ScriptDebuggerRemote::_get_incoming_message() {
ERR_FAIL_COND_V(!incoming_messages.has(Thread::get_caller_id()), Array());
List<Message> &message_list = incoming_messages[Thread::get_caller_id()];
ERR_FAIL_COND_V(message_list.empty(), Array());
const Message &msg = message_list.front()->get();
//msg.data should be in the correct format, with the command as the first element.
Array msgarr = msg.data;
message_list.pop_front();
return msgarr;
}
bool ScriptDebuggerRemote::_has_outgoing_messages() {
MutexLock mutex_lock(mutex);
return outgoing_messages.has(Thread::get_caller_id()) && !outgoing_messages[Thread::get_caller_id()].empty();
}
ScriptDebuggerRemote::Message ScriptDebuggerRemote::_get_outgoing_message() {
ERR_FAIL_COND_V(!outgoing_messages.has(Thread::get_caller_id()), ScriptDebuggerRemote::Message());
List<Message> &message_list = outgoing_messages[Thread::get_caller_id()];
ERR_FAIL_COND_V(message_list.empty(), ScriptDebuggerRemote::Message());
Message msg = message_list.front()->get();
message_list.pop_front();
return msg;
}
void ScriptDebuggerRemote::_get_output() {
mutex.lock();
if (output_strings.size()) {
locking = true;
packet_peer_stream->put_var("output");
packet_peer_stream->put_var(Thread::get_caller_id());
packet_peer_stream->put_var(output_strings.size());
while (output_strings.size()) {
@ -388,18 +502,21 @@ void ScriptDebuggerRemote::_get_output() {
if (n_messages_dropped > 0) {
Message msg;
msg.message = "Too many messages! " + String::num_int64(n_messages_dropped) + " messages were dropped.";
messages.push_back(msg);
outgoing_messages[Thread::get_caller_id()].push_back(msg);
n_messages_dropped = 0;
}
while (messages.size()) {
while (_has_outgoing_messages()) {
Message msg = _get_outgoing_message();
locking = true;
packet_peer_stream->put_var("message:" + messages.front()->get().message);
packet_peer_stream->put_var(messages.front()->get().data.size());
for (int i = 0; i < messages.front()->get().data.size(); i++) {
packet_peer_stream->put_var(messages.front()->get().data[i]);
packet_peer_stream->put_var("message:" + msg.message);
packet_peer_stream->put_var(Thread::get_caller_id());
packet_peer_stream->put_var(msg.data.size());
for (int i = 0; i < msg.data.size(); i++) {
packet_peer_stream->put_var(msg.data[i]);
}
messages.pop_front();
locking = false;
}
@ -434,6 +551,7 @@ void ScriptDebuggerRemote::_get_output() {
while (errors.size()) {
locking = true;
packet_peer_stream->put_var("error");
packet_peer_stream->put_var(Thread::get_caller_id());
OutputError oe = errors.front()->get();
packet_peer_stream->put_var(oe.callstack.size() + 2);
@ -687,6 +805,7 @@ void ScriptDebuggerRemote::_send_object_id(ObjectID p_id) {
}
packet_peer_stream->put_var("message:inspect_object");
packet_peer_stream->put_var(Thread::get_caller_id());
packet_peer_stream->put_var(3);
packet_peer_stream->put_var(p_id);
packet_peer_stream->put_var(obj->get_class());
@ -712,24 +831,21 @@ void ScriptDebuggerRemote::_poll_events() {
//this si called from ::idle_poll, happens only when running the game,
//does not get called while on debug break
while (packet_peer_stream->get_available_packet_count() > 0) {
_get_output();
ERR_FAIL_COND(!Thread::is_main_thread());
//send over output_strings
//send over output_strings
_get_output();
Variant var;
Error err = packet_peer_stream->get_var(var);
_poll_messages();
ERR_CONTINUE(err != OK);
ERR_CONTINUE(var.get_type() != Variant::ARRAY);
Array cmd = var;
while (_has_incoming_messages()) {
Array cmd = _get_incoming_message();
ERR_CONTINUE(cmd.size() == 0);
ERR_CONTINUE(cmd[0].get_type() != Variant::STRING);
String command = cmd[0];
//cmd.remove(0);
//Thread::ID thread = cmd[1]; was removed from cmd when storing incoming messages
if (command == "break") {
if (get_break_language()) {
@ -857,6 +973,7 @@ void ScriptDebuggerRemote::_send_profiling_data(bool p_for_frame) {
if (!profiler_function_signature_map.has(profile_info_ptrs[i]->signature)) {
int idx = profiler_function_signature_map.size();
packet_peer_stream->put_var("profile_sig");
packet_peer_stream->put_var(Thread::get_caller_id());
packet_peer_stream->put_var(2);
packet_peer_stream->put_var(profile_info_ptrs[i]->signature);
packet_peer_stream->put_var(idx);
@ -871,9 +988,11 @@ void ScriptDebuggerRemote::_send_profiling_data(bool p_for_frame) {
if (p_for_frame) {
packet_peer_stream->put_var("profile_frame");
packet_peer_stream->put_var(Thread::get_caller_id());
packet_peer_stream->put_var(8 + profile_frame_data.size() * 2 + to_send * 4);
} else {
packet_peer_stream->put_var("profile_total");
packet_peer_stream->put_var(Thread::get_caller_id());
packet_peer_stream->put_var(8 + to_send * 4);
}
@ -919,10 +1038,13 @@ void ScriptDebuggerRemote::idle_poll() {
// this function is called every frame, except when there is a debugger break (::debug() in this class)
// execution stops and remains in the ::debug function
ERR_FAIL_COND(!Thread::is_main_thread());
_get_output();
if (requested_quit) {
packet_peer_stream->put_var("kill_me");
packet_peer_stream->put_var(Thread::get_caller_id());
packet_peer_stream->put_var(0);
requested_quit = false;
}
@ -938,6 +1060,7 @@ void ScriptDebuggerRemote::idle_poll() {
arr[i] = performance->call("get_monitor", i);
}
packet_peer_stream->put_var("performance");
packet_peer_stream->put_var(Thread::get_caller_id());
packet_peer_stream->put_var(1);
packet_peer_stream->put_var(arr);
}
@ -980,6 +1103,7 @@ void ScriptDebuggerRemote::_send_network_profiling_data() {
int n_nodes = multiplayer->get_profiling_frame(&network_profile_info.write[0]);
packet_peer_stream->put_var("network_profile");
packet_peer_stream->put_var(Thread::get_caller_id());
packet_peer_stream->put_var(n_nodes * 6);
for (int i = 0; i < n_nodes; ++i) {
packet_peer_stream->put_var(network_profile_info[i].node);
@ -998,6 +1122,7 @@ void ScriptDebuggerRemote::_send_network_bandwidth_usage() {
int outgoing_bandwidth = multiplayer->get_outgoing_bandwidth_usage();
packet_peer_stream->put_var("network_bandwidth");
packet_peer_stream->put_var(Thread::get_caller_id());
packet_peer_stream->put_var(2);
packet_peer_stream->put_var(incoming_bandwidth);
packet_peer_stream->put_var(outgoing_bandwidth);
@ -1006,13 +1131,13 @@ void ScriptDebuggerRemote::_send_network_bandwidth_usage() {
void ScriptDebuggerRemote::send_message(const String &p_message, const Array &p_args) {
mutex.lock();
if (!locking && tcp_client->is_connected_to_host()) {
if (messages.size() >= max_messages_per_frame) {
if (outgoing_messages[Thread::get_caller_id()].size() >= max_messages_per_frame) {
n_messages_dropped++;
} else {
Message msg;
msg.message = p_message;
msg.data = p_args;
messages.push_back(msg);
outgoing_messages[Thread::get_caller_id()].push_back(msg);
}
}
mutex.unlock();
@ -1223,6 +1348,9 @@ ScriptDebuggerRemote::ScriptDebuggerRemote() :
eh.userdata = this;
add_error_handler(&eh);
incoming_messages.insert(Thread::get_main_id(), List<Message>());
outgoing_messages.insert(Thread::get_main_id(), List<Message>());
profile_info.resize(GLOBAL_GET("debug/settings/profiler/max_functions"));
network_profile_info.resize(GLOBAL_GET("debug/settings/profiler/max_functions"));
profile_info_ptrs.resize(profile_info.size());

View File

@ -30,11 +30,12 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#include "core/containers/list.h"
#include "core/io/packet_peer.h"
#include "core/io/stream_peer_tcp.h"
#include "core/containers/list.h"
#include "core/os/os.h"
#include "core/object/script_language.h"
#include "core/os/os.h"
#include "core/os/thread.h"
class SceneTree;
@ -93,7 +94,9 @@ class ScriptDebuggerRemote : public ScriptDebugger {
};
List<OutputString> output_strings;
List<Message> messages;
HashMap<Thread::ID, List<Message>> incoming_messages;
HashMap<Thread::ID, List<Message>> outgoing_messages;
int max_messages_per_frame;
int n_messages_dropped;
List<OutputError> errors;
@ -116,6 +119,13 @@ class ScriptDebuggerRemote : public ScriptDebugger {
PrintHandlerList phl;
void _poll_messages();
bool _has_incoming_messages();
Array _get_incoming_message();
bool _has_outgoing_messages();
Message _get_outgoing_message();
void _get_output();
void _poll_events();
uint32_t poll_every;