|
@@ -71,10 +71,12 @@
|
|
|
|
|
|
using CameraOverride = EditorDebuggerNode::CameraOverride;
|
|
|
|
|
|
-void ScriptEditorDebugger::_put_msg(String p_message, Array p_data) {
|
|
|
+void ScriptEditorDebugger::_put_msg(String p_message, Array p_data, uint64_t p_thread_id) {
|
|
|
+ ERR_FAIL_COND(p_thread_id == Thread::UNASSIGNED_ID);
|
|
|
if (is_session_active()) {
|
|
|
Array msg;
|
|
|
msg.push_back(p_message);
|
|
|
+ msg.push_back(p_thread_id);
|
|
|
msg.push_back(p_data);
|
|
|
peer->put_message(msg);
|
|
|
}
|
|
@@ -98,31 +100,31 @@ void ScriptEditorDebugger::debug_skip_breakpoints() {
|
|
|
|
|
|
Array msg;
|
|
|
msg.push_back(skip_breakpoints_value);
|
|
|
- _put_msg("set_skip_breakpoints", msg);
|
|
|
+ _put_msg("set_skip_breakpoints", msg, debugging_thread_id != Thread::UNASSIGNED_ID ? debugging_thread_id : Thread::MAIN_ID);
|
|
|
}
|
|
|
|
|
|
void ScriptEditorDebugger::debug_next() {
|
|
|
- ERR_FAIL_COND(!breaked);
|
|
|
+ ERR_FAIL_COND(!is_breaked());
|
|
|
|
|
|
- _put_msg("next", Array());
|
|
|
+ _put_msg("next", Array(), debugging_thread_id);
|
|
|
_clear_execution();
|
|
|
}
|
|
|
|
|
|
void ScriptEditorDebugger::debug_step() {
|
|
|
- ERR_FAIL_COND(!breaked);
|
|
|
+ ERR_FAIL_COND(!is_breaked());
|
|
|
|
|
|
- _put_msg("step", Array());
|
|
|
+ _put_msg("step", Array(), debugging_thread_id);
|
|
|
_clear_execution();
|
|
|
}
|
|
|
|
|
|
void ScriptEditorDebugger::debug_break() {
|
|
|
- ERR_FAIL_COND(breaked);
|
|
|
+ ERR_FAIL_COND(is_breaked());
|
|
|
|
|
|
_put_msg("break", Array());
|
|
|
}
|
|
|
|
|
|
void ScriptEditorDebugger::debug_continue() {
|
|
|
- ERR_FAIL_COND(!breaked);
|
|
|
+ ERR_FAIL_COND(!is_breaked());
|
|
|
|
|
|
// Allow focus stealing only if we actually run this client for security.
|
|
|
if (remote_pid && EditorNode::get_singleton()->has_child_process(remote_pid)) {
|
|
@@ -130,7 +132,7 @@ void ScriptEditorDebugger::debug_continue() {
|
|
|
}
|
|
|
|
|
|
_clear_execution();
|
|
|
- _put_msg("continue", Array());
|
|
|
+ _put_msg("continue", Array(), debugging_thread_id);
|
|
|
_put_msg("servers:foreground", Array());
|
|
|
}
|
|
|
|
|
@@ -299,43 +301,89 @@ Size2 ScriptEditorDebugger::get_minimum_size() const {
|
|
|
return ms;
|
|
|
}
|
|
|
|
|
|
-void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_data) {
|
|
|
+void ScriptEditorDebugger::_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(SNAME("breaked"), true, td.can_debug, td.error, td.has_stackdump);
|
|
|
+ if (!td.error.is_empty()) {
|
|
|
+ tabs->set_current_tab(0);
|
|
|
+ }
|
|
|
+ inspector->clear_cache(); // Take a chance to force remote objects update.
|
|
|
+ _put_msg("get_stack_dump", Array(), p_thread_id);
|
|
|
+}
|
|
|
+
|
|
|
+void ScriptEditorDebugger::_select_thread(int p_index) {
|
|
|
+ debugging_thread_id = threads->get_item_metadata(threads->get_selected());
|
|
|
+ _thread_debug_enter(debugging_thread_id);
|
|
|
+}
|
|
|
+
|
|
|
+void ScriptEditorDebugger::_parse_message(const String &p_msg, uint64_t p_thread_id, const Array &p_data) {
|
|
|
emit_signal(SNAME("debug_data"), p_msg, p_data);
|
|
|
if (p_msg == "debug_enter") {
|
|
|
- _put_msg("get_stack_dump", Array());
|
|
|
-
|
|
|
- ERR_FAIL_COND(p_data.size() != 3);
|
|
|
- bool can_continue = p_data[0];
|
|
|
- String error = p_data[1];
|
|
|
- bool has_stackdump = p_data[2];
|
|
|
- breaked = true;
|
|
|
- can_request_idle_draw = true;
|
|
|
- can_debug = can_continue;
|
|
|
- _update_buttons_state();
|
|
|
- _set_reason_text(error, MESSAGE_ERROR);
|
|
|
- emit_signal(SNAME("breaked"), true, can_continue, error, has_stackdump);
|
|
|
- if (is_move_to_foreground()) {
|
|
|
- DisplayServer::get_singleton()->window_move_to_foreground();
|
|
|
- }
|
|
|
- if (!error.is_empty()) {
|
|
|
- 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);
|
|
|
+ can_request_idle_draw = true;
|
|
|
+ if (is_move_to_foreground()) {
|
|
|
+ DisplayServer::get_singleton()->window_move_to_foreground();
|
|
|
+ }
|
|
|
+ profiler->set_enabled(false, false);
|
|
|
+ visual_profiler->set_enabled(false);
|
|
|
}
|
|
|
- profiler->set_enabled(false, false);
|
|
|
- visual_profiler->set_enabled(false);
|
|
|
- inspector->clear_cache(); // Take a chance to force remote objects update.
|
|
|
+ _update_buttons_state();
|
|
|
|
|
|
} else if (p_msg == "debug_exit") {
|
|
|
- breaked = false;
|
|
|
- can_debug = false;
|
|
|
- _clear_execution();
|
|
|
- _update_buttons_state();
|
|
|
- _set_reason_text(TTR("Execution resumed."), MESSAGE_SUCCESS);
|
|
|
- emit_signal(SNAME("breaked"), false, false, "", false);
|
|
|
+ threads_debugged.erase(p_thread_id);
|
|
|
+ if (p_thread_id == debugging_thread_id) {
|
|
|
+ _clear_execution();
|
|
|
+ if (threads_debugged.size() == 0) {
|
|
|
+ debugging_thread_id = Thread::UNASSIGNED_ID;
|
|
|
+ } else {
|
|
|
+ // Find next thread to debug.
|
|
|
+ uint32_t min_order = 0xFFFFFFFF;
|
|
|
+ uint64_t next_thread = Thread::UNASSIGNED_ID;
|
|
|
+ for (KeyValue<uint64_t, ThreadDebugged> T : threads_debugged) {
|
|
|
+ if (T.value.debug_order < min_order) {
|
|
|
+ min_order = T.value.debug_order;
|
|
|
+ next_thread = T.key;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ debugging_thread_id = next_thread;
|
|
|
+ }
|
|
|
|
|
|
- profiler->set_enabled(true, false);
|
|
|
- profiler->disable_seeking();
|
|
|
+ if (debugging_thread_id == Thread::UNASSIGNED_ID) {
|
|
|
+ // Nothing else to debug.
|
|
|
+ profiler->set_enabled(true, false);
|
|
|
+ profiler->disable_seeking();
|
|
|
|
|
|
- visual_profiler->set_enabled(true);
|
|
|
+ visual_profiler->set_enabled(true);
|
|
|
+
|
|
|
+ _set_reason_text(TTR("Execution resumed."), MESSAGE_SUCCESS);
|
|
|
+ emit_signal(SNAME("breaked"), false, false, "", false);
|
|
|
+
|
|
|
+ _update_buttons_state();
|
|
|
+ } else {
|
|
|
+ _thread_debug_enter(debugging_thread_id);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ _update_buttons_state();
|
|
|
+ }
|
|
|
|
|
|
} else if (p_msg == "set_pid") {
|
|
|
ERR_FAIL_COND(p_data.size() < 1);
|
|
@@ -379,7 +427,6 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da
|
|
|
|
|
|
vmem_total->set_tooltip_text(TTR("Bytes:") + " " + itos(total));
|
|
|
vmem_total->set_text(String::humanize_size(total));
|
|
|
-
|
|
|
} else if (p_msg == "servers:drawn") {
|
|
|
can_request_idle_draw = true;
|
|
|
} else if (p_msg == "stack_dump") {
|
|
@@ -414,11 +461,9 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da
|
|
|
inspector->clear_stack_variables();
|
|
|
ERR_FAIL_COND(p_data.size() != 1);
|
|
|
emit_signal(SNAME("stack_frame_vars"), p_data[0]);
|
|
|
-
|
|
|
} else if (p_msg == "stack_frame_var") {
|
|
|
inspector->add_stack_variable(p_data);
|
|
|
emit_signal(SNAME("stack_frame_var"), p_data);
|
|
|
-
|
|
|
} else if (p_msg == "output") {
|
|
|
ERR_FAIL_COND(p_data.size() != 2);
|
|
|
|
|
@@ -458,7 +503,6 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da
|
|
|
frame_data.write[i] = p_data[i];
|
|
|
}
|
|
|
performance_profiler->add_profile_frame(frame_data);
|
|
|
-
|
|
|
} else if (p_msg == "visual:profile_frame") {
|
|
|
ServersDebugger::VisualProfilerFrame frame;
|
|
|
frame.deserialize(p_data);
|
|
@@ -477,7 +521,6 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da
|
|
|
}
|
|
|
}
|
|
|
visual_profiler->add_frame_metric(metric);
|
|
|
-
|
|
|
} else if (p_msg == "error") {
|
|
|
DebuggerMarshalls::OutputError oe;
|
|
|
ERR_FAIL_COND_MSG(oe.deserialize(p_data) == false, "Failed to deserialize error message");
|
|
@@ -625,13 +668,11 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da
|
|
|
} else {
|
|
|
error_count++;
|
|
|
}
|
|
|
-
|
|
|
} else if (p_msg == "servers:function_signature") {
|
|
|
// Cache a profiler signature.
|
|
|
ServersDebugger::ScriptFunctionSignature sig;
|
|
|
sig.deserialize(p_data);
|
|
|
profiler_signature[sig.id] = sig.name;
|
|
|
-
|
|
|
} else if (p_msg == "servers:profile_frame" || p_msg == "servers:profile_total") {
|
|
|
EditorProfiler::Metric metric;
|
|
|
ServersDebugger::ServersProfilerFrame frame;
|
|
@@ -744,11 +785,9 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da
|
|
|
} else {
|
|
|
profiler->add_frame_metric(metric, true);
|
|
|
}
|
|
|
-
|
|
|
} else if (p_msg == "request_quit") {
|
|
|
emit_signal(SNAME("stop_requested"));
|
|
|
_stop_and_notify();
|
|
|
-
|
|
|
} else if (p_msg == "performance:profile_names") {
|
|
|
Vector<StringName> monitors;
|
|
|
monitors.resize(p_data.size());
|
|
@@ -757,13 +796,11 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da
|
|
|
monitors.set(i, p_data[i]);
|
|
|
}
|
|
|
performance_profiler->update_monitors(monitors);
|
|
|
-
|
|
|
} else if (p_msg == "filesystem:update_file") {
|
|
|
ERR_FAIL_COND(p_data.size() < 1);
|
|
|
if (EditorFileSystem::get_singleton()) {
|
|
|
EditorFileSystem::get_singleton()->update_file(p_data[0]);
|
|
|
}
|
|
|
-
|
|
|
} else {
|
|
|
int colon_index = p_msg.find_char(':');
|
|
|
ERR_FAIL_COND_MSG(colon_index < 1, "Invalid message received");
|
|
@@ -878,7 +915,7 @@ void ScriptEditorDebugger::_notification(int p_what) {
|
|
|
msg.push_back(cam->get_far());
|
|
|
_put_msg("scene:override_camera_3D:transform", msg);
|
|
|
}
|
|
|
- if (breaked && can_request_idle_draw) {
|
|
|
+ if (is_breaked() && can_request_idle_draw) {
|
|
|
_put_msg("servers:draw", Array());
|
|
|
can_request_idle_draw = false;
|
|
|
}
|
|
@@ -888,11 +925,12 @@ void ScriptEditorDebugger::_notification(int p_what) {
|
|
|
|
|
|
while (peer.is_valid() && peer->has_message()) {
|
|
|
Array arr = peer->get_message();
|
|
|
- if (arr.size() != 2 || arr[0].get_type() != Variant::STRING || arr[1].get_type() != Variant::ARRAY) {
|
|
|
+ if (arr.size() != 3 || arr[0].get_type() != Variant::STRING || arr[1].get_type() != Variant::INT || arr[2].get_type() != Variant::ARRAY) {
|
|
|
_stop_and_notify();
|
|
|
ERR_FAIL_MSG("Invalid message format received from peer");
|
|
|
}
|
|
|
- _parse_message(arr[0], arr[1]);
|
|
|
+
|
|
|
+ _parse_message(arr[0], arr[1], arr[2]);
|
|
|
|
|
|
if (OS::get_singleton()->get_ticks_msec() > until) {
|
|
|
break;
|
|
@@ -959,8 +997,6 @@ void ScriptEditorDebugger::start(Ref<RemoteDebuggerPeer> p_peer) {
|
|
|
performance_profiler->reset();
|
|
|
|
|
|
set_process(true);
|
|
|
- breaked = false;
|
|
|
- can_debug = true;
|
|
|
camera_override = CameraOverride::OVERRIDE_NONE;
|
|
|
|
|
|
tabs->set_current_tab(0);
|
|
@@ -973,13 +1009,35 @@ void ScriptEditorDebugger::_update_buttons_state() {
|
|
|
const bool active = is_session_active();
|
|
|
const bool has_editor_tree = active && editor_remote_tree && editor_remote_tree->get_selected();
|
|
|
vmem_refresh->set_disabled(!active);
|
|
|
- step->set_disabled(!active || !breaked || !can_debug);
|
|
|
- next->set_disabled(!active || !breaked || !can_debug);
|
|
|
- copy->set_disabled(!active || !breaked);
|
|
|
- docontinue->set_disabled(!active || !breaked);
|
|
|
- dobreak->set_disabled(!active || breaked);
|
|
|
+ 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 (KeyValue<uint64_t, ThreadDebugged> &I : threads_debugged) {
|
|
|
+ threadss.push_back(&I.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 ScriptEditorDebugger::_stop_and_notify() {
|
|
@@ -990,8 +1048,8 @@ void ScriptEditorDebugger::_stop_and_notify() {
|
|
|
|
|
|
void ScriptEditorDebugger::stop() {
|
|
|
set_process(false);
|
|
|
- breaked = false;
|
|
|
- can_debug = false;
|
|
|
+ threads_debugged.clear();
|
|
|
+ debugging_thread_id = Thread::UNASSIGNED_ID;
|
|
|
remote_pid = 0;
|
|
|
_clear_execution();
|
|
|
|
|
@@ -1043,7 +1101,7 @@ void ScriptEditorDebugger::_profiler_activate(bool p_enable, int p_type) {
|
|
|
}
|
|
|
|
|
|
void ScriptEditorDebugger::_profiler_seeked() {
|
|
|
- if (breaked) {
|
|
|
+ if (is_breaked()) {
|
|
|
return;
|
|
|
}
|
|
|
debug_break();
|
|
@@ -1067,7 +1125,7 @@ void ScriptEditorDebugger::_export_csv() {
|
|
|
}
|
|
|
|
|
|
String ScriptEditorDebugger::get_var_value(const String &p_var) const {
|
|
|
- if (!breaked) {
|
|
|
+ if (!is_breaked()) {
|
|
|
return String();
|
|
|
}
|
|
|
return inspector->get_stack_variable(p_var);
|
|
@@ -1255,7 +1313,7 @@ bool ScriptEditorDebugger::request_stack_dump(const int &p_frame) {
|
|
|
|
|
|
Array msg;
|
|
|
msg.push_back(p_frame);
|
|
|
- _put_msg("get_stack_frame_vars", msg);
|
|
|
+ _put_msg("get_stack_frame_vars", msg, debugging_thread_id);
|
|
|
return true;
|
|
|
}
|
|
|
|
|
@@ -1407,7 +1465,7 @@ void ScriptEditorDebugger::set_breakpoint(const String &p_path, int p_line, bool
|
|
|
msg.push_back(p_path);
|
|
|
msg.push_back(p_line);
|
|
|
msg.push_back(p_enabled);
|
|
|
- _put_msg("breakpoint", msg);
|
|
|
+ _put_msg("breakpoint", msg, debugging_thread_id != Thread::UNASSIGNED_ID ? debugging_thread_id : Thread::MAIN_ID);
|
|
|
|
|
|
TreeItem *path_item = breakpoints_tree->search_item_text(p_path);
|
|
|
if (path_item == nullptr) {
|
|
@@ -1450,7 +1508,7 @@ void ScriptEditorDebugger::set_breakpoint(const String &p_path, int p_line, bool
|
|
|
}
|
|
|
|
|
|
void ScriptEditorDebugger::reload_scripts() {
|
|
|
- _put_msg("reload_scripts", Array());
|
|
|
+ _put_msg("reload_scripts", Array(), debugging_thread_id != Thread::UNASSIGNED_ID ? debugging_thread_id : Thread::MAIN_ID);
|
|
|
}
|
|
|
|
|
|
bool ScriptEditorDebugger::is_skip_breakpoints() {
|
|
@@ -1804,15 +1862,26 @@ ScriptEditorDebugger::ScriptEditorDebugger() {
|
|
|
sc->set_h_size_flags(SIZE_EXPAND_FILL);
|
|
|
parent_sc->add_child(sc);
|
|
|
|
|
|
+ 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", callable_mp(this, &ScriptEditorDebugger::_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_h_size_flags(SIZE_EXPAND_FILL);
|
|
|
stack_dump->set_hide_root(true);
|
|
|
+ stack_dump->set_v_size_flags(SIZE_EXPAND_FILL);
|
|
|
stack_dump->connect("cell_selected", callable_mp(this, &ScriptEditorDebugger::_stack_dump_frame_selected));
|
|
|
- sc->add_child(stack_dump);
|
|
|
+ stack_vb->add_child(stack_dump);
|
|
|
|
|
|
VBoxContainer *inspector_vbox = memnew(VBoxContainer);
|
|
|
inspector_vbox->set_h_size_flags(SIZE_EXPAND_FILL);
|