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.
This commit is contained in:
Juan Linietsky 2023-04-29 17:20:38 +02:00
parent 202e4b2c1e
commit 5e512b705e
17 changed files with 409 additions and 223 deletions

View File

@ -67,6 +67,7 @@ Array DebuggerMarshalls::ScriptStackVariable::serialize(int max_size) {
Array arr; Array arr;
arr.push_back(name); arr.push_back(name);
arr.push_back(type); arr.push_back(type);
arr.push_back(value.get_type());
Variant var = value; Variant var = value;
if (value.get_type() == Variant::OBJECT && value.get_validated_object() == nullptr) { if (value.get_type() == Variant::OBJECT && value.get_validated_object() == nullptr) {
@ -74,7 +75,7 @@ Array DebuggerMarshalls::ScriptStackVariable::serialize(int max_size) {
} }
int len = 0; int len = 0;
Error err = encode_variant(var, nullptr, len, true); Error err = encode_variant(var, nullptr, len, false);
if (err != OK) { if (err != OK) {
ERR_PRINT("Failed to encode variant."); ERR_PRINT("Failed to encode variant.");
} }
@ -88,11 +89,12 @@ Array DebuggerMarshalls::ScriptStackVariable::serialize(int max_size) {
} }
bool DebuggerMarshalls::ScriptStackVariable::deserialize(const Array &p_arr) { bool DebuggerMarshalls::ScriptStackVariable::deserialize(const Array &p_arr) {
CHECK_SIZE(p_arr, 3, "ScriptStackVariable"); CHECK_SIZE(p_arr, 4, "ScriptStackVariable");
name = p_arr[0]; name = p_arr[0];
type = p_arr[1]; type = p_arr[1];
value = p_arr[2]; var_type = p_arr[2];
CHECK_END(p_arr, 3, "ScriptStackVariable"); value = p_arr[3];
CHECK_END(p_arr, 4, "ScriptStackVariable");
return true; return true;
} }

View File

@ -38,6 +38,7 @@ struct DebuggerMarshalls {
String name; String name;
Variant value; Variant value;
int type = -1; int type = -1;
int var_type = -1;
Array serialize(int max_size = 1 << 20); // 1 MiB default. Array serialize(int max_size = 1 << 20); // 1 MiB default.
bool deserialize(const Array &p_arr); bool deserialize(const Array &p_arr);

View File

@ -111,14 +111,6 @@ Error EngineDebugger::capture_parse(const StringName &p_name, const String &p_ms
return cap.capture(cap.data, p_msg, p_args, r_captured); return cap.capture(cap.data, p_msg, p_args, r_captured);
} }
void EngineDebugger::line_poll() {
// The purpose of this is just processing events every now and then when the script might get too busy otherwise bugs like infinite loops can't be caught
if (poll_every % 2048 == 0) {
poll_events(false);
}
poll_every++;
}
void EngineDebugger::iteration(uint64_t p_frame_ticks, uint64_t p_process_ticks, uint64_t p_physics_ticks, double p_physics_frame_time) { void EngineDebugger::iteration(uint64_t p_frame_ticks, uint64_t p_process_ticks, uint64_t p_physics_ticks, double p_physics_frame_time) {
frame_time = USEC_TO_SEC(p_frame_ticks); frame_time = USEC_TO_SEC(p_frame_ticks);
process_time = USEC_TO_SEC(p_process_ticks); process_time = USEC_TO_SEC(p_process_ticks);

View File

@ -126,7 +126,13 @@ public:
void profiler_enable(const StringName &p_name, bool p_enabled, const Array &p_opts = Array()); void profiler_enable(const StringName &p_name, bool p_enabled, const Array &p_opts = Array());
Error capture_parse(const StringName &p_name, const String &p_msg, const Array &p_args, bool &r_captured); Error capture_parse(const StringName &p_name, const String &p_msg, const Array &p_args, bool &r_captured);
void line_poll(); void line_poll() {
// The purpose of this is just processing events every now and then when the script might get too busy otherwise bugs like infinite loops can't be caught.
if (unlikely(poll_every % 2048) == 0) {
poll_events(false);
}
poll_every++;
}
virtual void poll_events(bool p_is_idle) {} virtual void poll_events(bool p_is_idle) {}
virtual void send_message(const String &p_msg, const Array &p_data) = 0; virtual void send_message(const String &p_msg, const Array &p_data) = 0;

View File

@ -94,6 +94,7 @@ public:
Error RemoteDebugger::_put_msg(String p_message, Array p_data) { Error RemoteDebugger::_put_msg(String p_message, Array p_data) {
Array msg; Array msg;
msg.push_back(p_message); msg.push_back(p_message);
msg.push_back(Thread::get_caller_id());
msg.push_back(p_data); msg.push_back(p_data);
Error err = peer->put_message(msg); Error err = peer->put_message(msg);
if (err != OK) { if (err != OK) {
@ -185,9 +186,9 @@ RemoteDebugger::ErrorMessage RemoteDebugger::_create_overflow_error(const String
} }
void RemoteDebugger::flush_output() { void RemoteDebugger::flush_output() {
MutexLock lock(mutex);
flush_thread = Thread::get_caller_id(); flush_thread = Thread::get_caller_id();
flushing = true; flushing = true;
MutexLock lock(mutex);
if (!is_peer_connected()) { if (!is_peer_connected()) {
return; return;
} }
@ -348,18 +349,65 @@ Error RemoteDebugger::_try_capture(const String &p_msg, const Array &p_data, boo
return capture_parse(cap, msg, p_data, r_captured); return capture_parse(cap, msg, p_data, r_captured);
} }
void RemoteDebugger::_poll_messages() {
MutexLock mutex_lock(mutex);
peer->poll();
while (peer->has_message()) {
Array cmd = peer->get_message();
ERR_CONTINUE(cmd.size() != 3);
ERR_CONTINUE(cmd[0].get_type() != Variant::STRING);
ERR_CONTINUE(cmd[1].get_type() != Variant::INT);
ERR_CONTINUE(cmd[2].get_type() != Variant::ARRAY);
Thread::ID thread = cmd[1];
if (!messages.has(thread)) {
continue; // This thread is not around to receive the messages
}
Message msg;
msg.message = cmd[0];
msg.data = cmd[2];
messages[thread].push_back(msg);
}
}
bool RemoteDebugger::_has_messages() {
MutexLock mutex_lock(mutex);
return messages.has(Thread::get_caller_id()) && !messages[Thread::get_caller_id()].is_empty();
}
Array RemoteDebugger::_get_message() {
MutexLock mutex_lock(mutex);
ERR_FAIL_COND_V(!messages.has(Thread::get_caller_id()), Array());
List<Message> &message_list = messages[Thread::get_caller_id()];
ERR_FAIL_COND_V(message_list.is_empty(), Array());
Array msg;
msg.resize(2);
msg[0] = message_list.front()->get().message;
msg[1] = message_list.front()->get().data;
message_list.pop_front();
return msg;
}
void RemoteDebugger::debug(bool p_can_continue, bool p_is_error_breakpoint) { void RemoteDebugger::debug(bool p_can_continue, bool p_is_error_breakpoint) {
//this function is called when there is a debugger break (bug on script) //this function is called when there is a debugger break (bug on script)
//or when execution is paused from editor //or when execution is paused from editor
if (script_debugger->is_skipping_breakpoints() && !p_is_error_breakpoint) { {
return; MutexLock lock(mutex);
} // Tests that require mutex.
if (script_debugger->is_skipping_breakpoints() && !p_is_error_breakpoint) {
return;
}
ERR_FAIL_COND_MSG(!is_peer_connected(), "Script Debugger failed to connect, but being used anyway."); ERR_FAIL_COND_MSG(!is_peer_connected(), "Script Debugger failed to connect, but being used anyway.");
if (!peer->can_block()) { if (!peer->can_block()) {
return; // Peer does not support blocking IO. We could at least send the error though. return; // Peer does not support blocking IO. We could at least send the error though.
}
} }
ScriptLanguage *script_lang = script_debugger->get_break_language(); ScriptLanguage *script_lang = script_debugger->get_break_language();
@ -369,22 +417,33 @@ void RemoteDebugger::debug(bool p_can_continue, bool p_is_error_breakpoint) {
msg.push_back(error_str); msg.push_back(error_str);
ERR_FAIL_COND(!script_lang); ERR_FAIL_COND(!script_lang);
msg.push_back(script_lang->debug_get_stack_level_count() > 0); msg.push_back(script_lang->debug_get_stack_level_count() > 0);
msg.push_back(Thread::get_caller_id() == Thread::get_main_id() ? String(RTR("Main Thread")) : itos(Thread::get_caller_id()));
if (allow_focus_steal_fn) { if (allow_focus_steal_fn) {
allow_focus_steal_fn(); allow_focus_steal_fn();
} }
send_message("debug_enter", msg); send_message("debug_enter", msg);
Input::MouseMode mouse_mode = Input::get_singleton()->get_mouse_mode(); Input::MouseMode mouse_mode = Input::MOUSE_MODE_VISIBLE;
if (mouse_mode != Input::MOUSE_MODE_VISIBLE) {
Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE); if (Thread::get_caller_id() == Thread::get_main_id()) {
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);
}
} else {
MutexLock mutex_lock(mutex);
messages.insert(Thread::get_caller_id(), List<Message>());
} }
mutex.lock();
while (is_peer_connected()) { while (is_peer_connected()) {
mutex.unlock();
flush_output(); flush_output();
peer->poll();
if (peer->has_message()) { _poll_messages();
Array cmd = peer->get_message();
if (_has_messages()) {
Array cmd = _get_message();
ERR_CONTINUE(cmd.size() != 2); ERR_CONTINUE(cmd.size() != 2);
ERR_CONTINUE(cmd[0].get_type() != Variant::STRING); ERR_CONTINUE(cmd[0].get_type() != Variant::STRING);
@ -479,14 +538,22 @@ void RemoteDebugger::debug(bool p_can_continue, bool p_is_error_breakpoint) {
} }
} else { } else {
OS::get_singleton()->delay_usec(10000); OS::get_singleton()->delay_usec(10000);
OS::get_singleton()->process_and_drop_events(); if (Thread::get_caller_id() == Thread::get_main_id()) {
// If this is a busy loop on the main thread, events still need to be processed.
OS::get_singleton()->process_and_drop_events();
}
} }
} }
send_message("debug_exit", Array()); send_message("debug_exit", Array());
if (mouse_mode != Input::MOUSE_MODE_VISIBLE) { if (Thread::get_caller_id() == Thread::get_main_id()) {
Input::get_singleton()->set_mouse_mode(mouse_mode); if (mouse_mode != Input::MOUSE_MODE_VISIBLE) {
Input::get_singleton()->set_mouse_mode(mouse_mode);
}
} else {
MutexLock mutex_lock(mutex);
messages.erase(Thread::get_caller_id());
} }
} }
@ -496,9 +563,11 @@ void RemoteDebugger::poll_events(bool p_is_idle) {
} }
flush_output(); flush_output();
peer->poll();
while (peer->has_message()) { _poll_messages();
Array arr = peer->get_message();
while (_has_messages()) {
Array arr = _get_message();
ERR_CONTINUE(arr.size() != 2); ERR_CONTINUE(arr.size() != 2);
ERR_CONTINUE(arr[0].get_type() != Variant::STRING); ERR_CONTINUE(arr[0].get_type() != Variant::STRING);
@ -604,6 +673,8 @@ RemoteDebugger::RemoteDebugger(Ref<RemoteDebuggerPeer> p_peer) {
eh.errfunc = _err_handler; eh.errfunc = _err_handler;
eh.userdata = this; eh.userdata = this;
add_error_handler(&eh); add_error_handler(&eh);
messages.insert(Thread::get_main_id(), List<Message>());
} }
RemoteDebugger::~RemoteDebugger() { RemoteDebugger::~RemoteDebugger() {

View File

@ -80,6 +80,17 @@ private:
bool flushing = false; bool flushing = false;
Thread::ID flush_thread = 0; Thread::ID flush_thread = 0;
struct Message {
String message;
Array data;
};
HashMap<Thread::ID, List<Message>> messages;
void _poll_messages();
bool _has_messages();
Array _get_message();
PrintHandlerList phl; PrintHandlerList phl;
static void _print_handler(void *p_this, const String &p_string, bool p_error, bool p_rich); static void _print_handler(void *p_this, const String &p_string, bool p_error, bool p_rich);
ErrorHandlerList eh; ErrorHandlerList eh;

View File

@ -32,22 +32,19 @@
#include "core/debugger/engine_debugger.h" #include "core/debugger/engine_debugger.h"
thread_local int ScriptDebugger::lines_left = -1;
thread_local int ScriptDebugger::depth = -1;
thread_local ScriptLanguage *ScriptDebugger::break_lang = nullptr;
thread_local Vector<ScriptDebugger::StackInfo> ScriptDebugger::error_stack_info;
void ScriptDebugger::set_lines_left(int p_left) { void ScriptDebugger::set_lines_left(int p_left) {
lines_left = p_left; lines_left = p_left;
} }
int ScriptDebugger::get_lines_left() const {
return lines_left;
}
void ScriptDebugger::set_depth(int p_depth) { void ScriptDebugger::set_depth(int p_depth) {
depth = p_depth; depth = p_depth;
} }
int ScriptDebugger::get_depth() const {
return depth;
}
void ScriptDebugger::insert_breakpoint(int p_line, const StringName &p_source) { void ScriptDebugger::insert_breakpoint(int p_line, const StringName &p_source) {
if (!breakpoints.has(p_line)) { if (!breakpoints.has(p_line)) {
breakpoints[p_line] = HashSet<StringName>(); breakpoints[p_line] = HashSet<StringName>();
@ -66,13 +63,6 @@ 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)) {
return false;
}
return breakpoints[p_line].has(p_source);
}
String ScriptDebugger::breakpoint_find_source(const String &p_source) const { String ScriptDebugger::breakpoint_find_source(const String &p_source) const {
return p_source; return p_source;
} }
@ -100,7 +90,7 @@ void ScriptDebugger::send_error(const String &p_func, const String &p_file, int
// Store stack info, this is ugly, but allows us to separate EngineDebugger and ScriptDebugger. There might be a better way. // Store stack info, this is ugly, but allows us to separate EngineDebugger and ScriptDebugger. There might be a better way.
error_stack_info.append_array(p_stack_info); error_stack_info.append_array(p_stack_info);
EngineDebugger::get_singleton()->send_error(p_func, p_file, p_line, p_err, p_descr, p_editor_notify, p_type); EngineDebugger::get_singleton()->send_error(p_func, p_file, p_line, p_err, p_descr, p_editor_notify, p_type);
error_stack_info.clear(); error_stack_info.clear(); // Clear because this is thread local
} }
Vector<ScriptLanguage::StackInfo> ScriptDebugger::get_error_stack_info() const { Vector<ScriptLanguage::StackInfo> ScriptDebugger::get_error_stack_info() const {

View File

@ -40,21 +40,25 @@
class ScriptDebugger { class ScriptDebugger {
typedef ScriptLanguage::StackInfo StackInfo; typedef ScriptLanguage::StackInfo StackInfo;
int lines_left = -1;
int depth = -1;
bool skip_breakpoints = false; bool skip_breakpoints = false;
HashMap<int, HashSet<StringName>> breakpoints; HashMap<int, HashSet<StringName>> breakpoints;
ScriptLanguage *break_lang = nullptr; static thread_local int lines_left;
Vector<StackInfo> error_stack_info; static thread_local int depth;
static thread_local ScriptLanguage *break_lang;
static thread_local Vector<StackInfo> error_stack_info;
public: public:
void set_lines_left(int p_left); void set_lines_left(int p_left);
int get_lines_left() const; _ALWAYS_INLINE_ int get_lines_left() const {
return lines_left;
}
void set_depth(int p_depth); void set_depth(int p_depth);
int get_depth() const; _ALWAYS_INLINE_ int get_depth() const {
return depth;
}
String breakpoint_find_source(const String &p_source) const; String breakpoint_find_source(const String &p_source) const;
void set_break_language(ScriptLanguage *p_lang) { break_lang = p_lang; } void set_break_language(ScriptLanguage *p_lang) { break_lang = p_lang; }
@ -63,7 +67,12 @@ public:
bool is_skipping_breakpoints(); bool is_skipping_breakpoints();
void insert_breakpoint(int p_line, const StringName &p_source); void insert_breakpoint(int p_line, const StringName &p_source);
void remove_breakpoint(int p_line, const StringName &p_source); void remove_breakpoint(int p_line, const StringName &p_source);
bool is_breakpoint(int p_line, const StringName &p_source) const; _ALWAYS_INLINE_ bool is_breakpoint(int p_line, const StringName &p_source) const {
if (likely(!breakpoints.has(p_line))) {
return false;
}
return breakpoints[p_line].has(p_source);
}
void clear_breakpoints(); void clear_breakpoints();
const HashMap<int, HashSet<StringName>> &get_breakpoints() const { return breakpoints; } const HashMap<int, HashSet<StringName>> &get_breakpoints() const { return breakpoints; }

View File

@ -5,7 +5,6 @@
</brief_description> </brief_description>
<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. 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.
[b]Warning:[/b] [b]Warning:[/b]
To ensure proper cleanup without crashes or deadlocks, when a [Thread]'s reference count reaches zero and it is therefore destroyed, the following conditions must be met: To ensure proper cleanup without crashes or deadlocks, when a [Thread]'s reference count reaches zero and it is therefore destroyed, the following conditions must be met:
- It must not have any [Mutex] objects locked. - It must not have any [Mutex] objects locked.

View File

@ -232,7 +232,7 @@ void EditorDebuggerInspector::add_stack_variable(const Array &p_array) {
PropertyHint h = PROPERTY_HINT_NONE; PropertyHint h = PROPERTY_HINT_NONE;
String hs; String hs;
if (v.get_type() == Variant::OBJECT) { if (var.var_type == Variant::OBJECT) {
v = Object::cast_to<EncodedObjectAsID>(v)->get_object_id(); v = Object::cast_to<EncodedObjectAsID>(v)->get_object_id();
h = PROPERTY_HINT_OBJECT_ID; h = PROPERTY_HINT_OBJECT_ID;
hs = "Object"; hs = "Object";

View File

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

View File

@ -138,6 +138,7 @@ private:
Tree *stack_dump = nullptr; Tree *stack_dump = nullptr;
LineEdit *search = nullptr; LineEdit *search = nullptr;
OptionButton *threads = nullptr;
EditorDebuggerInspector *inspector = nullptr; EditorDebuggerInspector *inspector = nullptr;
SceneDebuggerTree *scene_tree = nullptr; SceneDebuggerTree *scene_tree = nullptr;
@ -152,19 +153,39 @@ private:
EditorPerformanceProfiler *performance_profiler = nullptr; EditorPerformanceProfiler *performance_profiler = nullptr;
OS::ProcessID remote_pid = 0; OS::ProcessID remote_pid = 0;
bool breaked = false;
bool can_debug = false;
bool move_to_foreground = true; bool move_to_foreground = true;
bool can_request_idle_draw = false; bool can_request_idle_draw = false;
bool live_debug; bool live_debug;
uint64_t debugging_thread_id = Thread::UNASSIGNED_ID;
struct ThreadDebugged {
String name;
String error;
bool can_debug = false;
bool has_stackdump = false;
uint32_t debug_order = 0;
uint64_t thread_id = Thread::UNASSIGNED_ID; // for order
};
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 = false;
void _select_thread(int p_index);
EditorDebuggerNode::CameraOverride camera_override; EditorDebuggerNode::CameraOverride camera_override;
void _stack_dump_frame_selected(); void _stack_dump_frame_selected();
void _file_selected(const String &p_file); void _file_selected(const String &p_file);
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 _set_reason_text(const String &p_reason, MessageType p_type);
void _update_buttons_state(); void _update_buttons_state();
void _remote_object_selected(ObjectID p_object); void _remote_object_selected(ObjectID p_object);
@ -200,7 +221,7 @@ private:
void _item_menu_id_pressed(int p_option); void _item_menu_id_pressed(int p_option);
void _tab_changed(int p_tab); void _tab_changed(int p_tab);
void _put_msg(String p_message, Array p_data); void _put_msg(String p_message, Array p_data, uint64_t p_thread_id = Thread::MAIN_ID);
void _export_csv(); void _export_csv();
void _clear_execution(); void _clear_execution();
@ -213,6 +234,8 @@ private:
String _format_frame_text(const ScriptLanguage::StackInfo *info); String _format_frame_text(const ScriptLanguage::StackInfo *info);
void _thread_debug_enter(uint64_t p_thread_id);
protected: protected:
void _notification(int p_what); void _notification(int p_what);
static void _bind_methods(); static void _bind_methods();
@ -238,9 +261,9 @@ public:
void debug_step(); void debug_step();
void debug_break(); void debug_break();
void debug_continue(); void debug_continue();
bool is_breaked() const { return breaked; } bool is_breaked() const { return threads_debugged.size() > 0; }
bool is_debuggable() const { return can_debug; } bool is_debuggable() const { return threads_debugged.size() > 0 && threads_debugged[debugging_thread_id].can_debug; }
bool is_session_active() { return peer.is_valid() && peer->is_peer_connected(); }; bool is_session_active() { return peer.is_valid() && peer->is_peer_connected(); }
int get_remote_pid() const { return remote_pid; } int get_remote_pid() const { return remote_pid; }
bool is_move_to_foreground() const; bool is_move_to_foreground() const;

View File

@ -2094,10 +2094,7 @@ String GDScriptLanguage::get_extension() const {
} }
void GDScriptLanguage::finish() { void GDScriptLanguage::finish() {
if (_call_stack) { _call_stack.free();
memdelete_arr(_call_stack);
_call_stack = nullptr;
}
// Clear the cache before parsing the script_list // Clear the cache before parsing the script_list
GDScriptCache::clear(); GDScriptCache::clear();
@ -2140,12 +2137,12 @@ void GDScriptLanguage::profiling_start() {
SelfList<GDScriptFunction> *elem = function_list.first(); SelfList<GDScriptFunction> *elem = function_list.first();
while (elem) { while (elem) {
elem->self()->profile.call_count = 0; elem->self()->profile.call_count.set(0);
elem->self()->profile.self_time = 0; elem->self()->profile.self_time.set(0);
elem->self()->profile.total_time = 0; elem->self()->profile.total_time.set(0);
elem->self()->profile.frame_call_count = 0; elem->self()->profile.frame_call_count.set(0);
elem->self()->profile.frame_self_time = 0; elem->self()->profile.frame_self_time.set(0);
elem->self()->profile.frame_total_time = 0; elem->self()->profile.frame_total_time.set(0);
elem->self()->profile.last_frame_call_count = 0; elem->self()->profile.last_frame_call_count = 0;
elem->self()->profile.last_frame_self_time = 0; elem->self()->profile.last_frame_self_time = 0;
elem->self()->profile.last_frame_total_time = 0; elem->self()->profile.last_frame_total_time = 0;
@ -2175,9 +2172,9 @@ int GDScriptLanguage::profiling_get_accumulated_data(ProfilingInfo *p_info_arr,
if (current >= p_info_max) { if (current >= p_info_max) {
break; break;
} }
p_info_arr[current].call_count = elem->self()->profile.call_count; p_info_arr[current].call_count = elem->self()->profile.call_count.get();
p_info_arr[current].self_time = elem->self()->profile.self_time; p_info_arr[current].self_time = elem->self()->profile.self_time.get();
p_info_arr[current].total_time = elem->self()->profile.total_time; p_info_arr[current].total_time = elem->self()->profile.total_time.get();
p_info_arr[current].signature = elem->self()->profile.signature; p_info_arr[current].signature = elem->self()->profile.signature;
elem = elem->next(); elem = elem->next();
current++; current++;
@ -2395,12 +2392,12 @@ void GDScriptLanguage::frame() {
SelfList<GDScriptFunction> *elem = function_list.first(); SelfList<GDScriptFunction> *elem = function_list.first();
while (elem) { while (elem) {
elem->self()->profile.last_frame_call_count = elem->self()->profile.frame_call_count; 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; 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; elem->self()->profile.last_frame_total_time = elem->self()->profile.frame_total_time.get();
elem->self()->profile.frame_call_count = 0; elem->self()->profile.frame_call_count.set(0);
elem->self()->profile.frame_self_time = 0; elem->self()->profile.frame_self_time.set(0);
elem->self()->profile.frame_total_time = 0; elem->self()->profile.frame_total_time.set(0);
elem = elem->next(); elem = elem->next();
} }
} }
@ -2607,6 +2604,8 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b
return c->identifier != nullptr ? String(c->identifier->name) : String(); return c->identifier != nullptr ? String(c->identifier->name) : String();
} }
thread_local GDScriptLanguage::CallStack GDScriptLanguage::_call_stack;
GDScriptLanguage::GDScriptLanguage() { GDScriptLanguage::GDScriptLanguage() {
calls = 0; calls = 0;
ERR_FAIL_COND(singleton); ERR_FAIL_COND(singleton);
@ -2626,18 +2625,14 @@ GDScriptLanguage::GDScriptLanguage() {
profiling = false; profiling = false;
script_frame_time = 0; script_frame_time = 0;
_debug_call_stack_pos = 0;
int dmcs = GLOBAL_DEF(PropertyInfo(Variant::INT, "debug/settings/gdscript/max_call_stack", PROPERTY_HINT_RANGE, "512," + itos(GDScriptFunction::MAX_CALL_DEPTH - 1) + ",1"), 1024); int dmcs = GLOBAL_DEF(PropertyInfo(Variant::INT, "debug/settings/gdscript/max_call_stack", PROPERTY_HINT_RANGE, "512," + itos(GDScriptFunction::MAX_CALL_DEPTH - 1) + ",1"), 1024);
if (EngineDebugger::is_active()) { if (EngineDebugger::is_active()) {
//debugging enabled! //debugging enabled!
_debug_max_call_stack = dmcs; _debug_max_call_stack = dmcs;
_call_stack = memnew_arr(CallLevel, _debug_max_call_stack + 1);
} else { } else {
_debug_max_call_stack = 0; _debug_max_call_stack = 0;
_call_stack = nullptr;
} }
#ifdef DEBUG_ENABLED #ifdef DEBUG_ENABLED

View File

@ -364,12 +364,26 @@ class GDScriptLanguage : public ScriptLanguage {
int *line = nullptr; int *line = nullptr;
}; };
int _debug_parse_err_line; static thread_local int _debug_parse_err_line;
String _debug_parse_err_file; static thread_local String _debug_parse_err_file;
String _debug_error; static thread_local String _debug_error;
int _debug_call_stack_pos; struct CallStack {
int _debug_max_call_stack; CallLevel *levels = nullptr;
CallLevel *_call_stack = nullptr; int stack_pos = 0;
void free() {
if (levels) {
memdelete(levels);
levels = nullptr;
}
}
~CallStack() {
free();
}
};
static thread_local CallStack _call_stack;
int _debug_max_call_stack = 0;
void _add_global(const StringName &p_name, const Variant &p_value); void _add_global(const StringName &p_name, const Variant &p_value);
@ -395,59 +409,51 @@ public:
bool debug_break_parse(const String &p_file, int p_line, const String &p_error); 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) { _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()) { if (unlikely(_call_stack.levels == nullptr)) {
return; //no support for other threads than main for now _call_stack.levels = memnew_arr(CallLevel, _debug_max_call_stack + 1);
} }
if (EngineDebugger::get_script_debugger()->get_lines_left() > 0 && EngineDebugger::get_script_debugger()->get_depth() >= 0) { if (EngineDebugger::get_script_debugger()->get_lines_left() > 0 && EngineDebugger::get_script_debugger()->get_depth() >= 0) {
EngineDebugger::get_script_debugger()->set_depth(EngineDebugger::get_script_debugger()->get_depth() + 1); EngineDebugger::get_script_debugger()->set_depth(EngineDebugger::get_script_debugger()->get_depth() + 1);
} }
if (_debug_call_stack_pos >= _debug_max_call_stack) { if (_call_stack.stack_pos >= _debug_max_call_stack) {
//stack overflow //stack overflow
_debug_error = vformat("Stack overflow (stack size: %s). Check for infinite recursion in your script.", _debug_max_call_stack); _debug_error = vformat("Stack overflow (stack size: %s). Check for infinite recursion in your script.", _debug_max_call_stack);
EngineDebugger::get_script_debugger()->debug(this); EngineDebugger::get_script_debugger()->debug(this);
return; return;
} }
_call_stack[_debug_call_stack_pos].stack = p_stack; _call_stack.levels[_call_stack.stack_pos].stack = p_stack;
_call_stack[_debug_call_stack_pos].instance = p_instance; _call_stack.levels[_call_stack.stack_pos].instance = p_instance;
_call_stack[_debug_call_stack_pos].function = p_function; _call_stack.levels[_call_stack.stack_pos].function = p_function;
_call_stack[_debug_call_stack_pos].ip = p_ip; _call_stack.levels[_call_stack.stack_pos].ip = p_ip;
_call_stack[_debug_call_stack_pos].line = p_line; _call_stack.levels[_call_stack.stack_pos].line = p_line;
_debug_call_stack_pos++; _call_stack.stack_pos++;
} }
_FORCE_INLINE_ void exit_function() { _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 (EngineDebugger::get_script_debugger()->get_lines_left() > 0 && EngineDebugger::get_script_debugger()->get_depth() >= 0) { if (EngineDebugger::get_script_debugger()->get_lines_left() > 0 && EngineDebugger::get_script_debugger()->get_depth() >= 0) {
EngineDebugger::get_script_debugger()->set_depth(EngineDebugger::get_script_debugger()->get_depth() - 1); EngineDebugger::get_script_debugger()->set_depth(EngineDebugger::get_script_debugger()->get_depth() - 1);
} }
if (_debug_call_stack_pos == 0) { if (_call_stack.stack_pos == 0) {
_debug_error = "Stack Underflow (Engine Bug)"; _debug_error = "Stack Underflow (Engine Bug)";
EngineDebugger::get_script_debugger()->debug(this); EngineDebugger::get_script_debugger()->debug(this);
return; return;
} }
_debug_call_stack_pos--; _call_stack.stack_pos--;
} }
virtual Vector<StackInfo> debug_get_current_stack_info() override { virtual Vector<StackInfo> debug_get_current_stack_info() override {
if (Thread::get_main_id() != Thread::get_caller_id()) {
return Vector<StackInfo>();
}
Vector<StackInfo> csi; Vector<StackInfo> csi;
csi.resize(_debug_call_stack_pos); csi.resize(_call_stack.stack_pos);
for (int i = 0; i < _debug_call_stack_pos; i++) { for (int i = 0; i < _call_stack.stack_pos; i++) {
csi.write[_debug_call_stack_pos - i - 1].line = _call_stack[i].line ? *_call_stack[i].line : 0; csi.write[_call_stack.stack_pos - i - 1].line = _call_stack.levels[i].line ? *_call_stack.levels[i].line : 0;
if (_call_stack[i].function) { if (_call_stack.levels[i].function) {
csi.write[_debug_call_stack_pos - i - 1].func = _call_stack[i].function->get_name(); csi.write[_call_stack.stack_pos - i - 1].func = _call_stack.levels[i].function->get_name();
csi.write[_debug_call_stack_pos - i - 1].file = _call_stack[i].function->get_script()->get_script_path(); csi.write[_call_stack.stack_pos - i - 1].file = _call_stack.levels[i].function->get_script()->get_script_path();
} }
} }
return csi; return csi;

View File

@ -233,6 +233,10 @@ Script *GDScriptLanguage::create_script() const {
/* DEBUGGER FUNCTIONS */ /* DEBUGGER FUNCTIONS */
thread_local int GDScriptLanguage::_debug_parse_err_line = -1;
thread_local String GDScriptLanguage::_debug_parse_err_file;
thread_local String GDScriptLanguage::_debug_error;
bool GDScriptLanguage::debug_break_parse(const String &p_file, int p_line, const String &p_error) { bool GDScriptLanguage::debug_break_parse(const String &p_file, int p_line, const String &p_error) {
// break because of parse error // break because of parse error
@ -241,6 +245,9 @@ bool GDScriptLanguage::debug_break_parse(const String &p_file, int p_line, const
_debug_parse_err_file = p_file; _debug_parse_err_file = p_file;
_debug_error = p_error; _debug_error = p_error;
EngineDebugger::get_script_debugger()->debug(this, false, true); EngineDebugger::get_script_debugger()->debug(this, false, true);
// Because this is thread local, clear the memory afterwards.
_debug_parse_err_file = String();
_debug_error = String();
return true; return true;
} else { } else {
return false; return false;
@ -248,12 +255,15 @@ 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) { bool GDScriptLanguage::debug_break(const String &p_error, bool p_allow_continue) {
if (EngineDebugger::is_active() && Thread::get_caller_id() == Thread::get_main_id()) { if (EngineDebugger::is_active()) {
_debug_parse_err_line = -1; _debug_parse_err_line = -1;
_debug_parse_err_file = ""; _debug_parse_err_file = "";
_debug_error = p_error; _debug_error = p_error;
bool is_error_breakpoint = p_error != "Breakpoint"; bool is_error_breakpoint = p_error != "Breakpoint";
EngineDebugger::get_script_debugger()->debug(this, p_allow_continue, is_error_breakpoint); EngineDebugger::get_script_debugger()->debug(this, p_allow_continue, is_error_breakpoint);
// Because this is thread local, clear the memory afterwards.
_debug_parse_err_file = String();
_debug_error = String();
return true; return true;
} else { } else {
return false; return false;
@ -269,7 +279,7 @@ int GDScriptLanguage::debug_get_stack_level_count() const {
return 1; return 1;
} }
return _debug_call_stack_pos; return _call_stack.stack_pos;
} }
int GDScriptLanguage::debug_get_stack_level_line(int p_level) const { int GDScriptLanguage::debug_get_stack_level_line(int p_level) const {
@ -277,11 +287,11 @@ int GDScriptLanguage::debug_get_stack_level_line(int p_level) const {
return _debug_parse_err_line; 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 { String GDScriptLanguage::debug_get_stack_level_function(int p_level) const {
@ -289,9 +299,9 @@ String GDScriptLanguage::debug_get_stack_level_function(int p_level) const {
return ""; return "";
} }
ERR_FAIL_INDEX_V(p_level, _debug_call_stack_pos, ""); ERR_FAIL_INDEX_V(p_level, _call_stack.stack_pos, "");
int l = _debug_call_stack_pos - p_level - 1; int l = _call_stack.stack_pos - p_level - 1;
return _call_stack[l].function->get_name(); return _call_stack.levels[l].function->get_name();
} }
String GDScriptLanguage::debug_get_stack_level_source(int p_level) const { String GDScriptLanguage::debug_get_stack_level_source(int p_level) const {
@ -299,9 +309,9 @@ String GDScriptLanguage::debug_get_stack_level_source(int p_level) const {
return _debug_parse_err_file; return _debug_parse_err_file;
} }
ERR_FAIL_INDEX_V(p_level, _debug_call_stack_pos, ""); ERR_FAIL_INDEX_V(p_level, _call_stack.stack_pos, "");
int l = _debug_call_stack_pos - p_level - 1; int l = _call_stack.stack_pos - p_level - 1;
return _call_stack[l].function->get_source(); 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) { 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) {
@ -309,17 +319,17 @@ void GDScriptLanguage::debug_get_stack_level_locals(int p_level, List<String> *p
return; return;
} }
ERR_FAIL_INDEX(p_level, _debug_call_stack_pos); ERR_FAIL_INDEX(p_level, _call_stack.stack_pos);
int l = _debug_call_stack_pos - p_level - 1; 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; 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 (const Pair<StringName, int> &E : locals) { for (const Pair<StringName, int> &E : locals) {
p_locals->push_back(E.first); p_locals->push_back(E.first);
p_values->push_back(_call_stack[l].stack[E.second]); p_values->push_back(_call_stack.levels[l].stack[E.second]);
} }
} }
@ -328,10 +338,10 @@ void GDScriptLanguage::debug_get_stack_level_members(int p_level, List<String> *
return; return;
} }
ERR_FAIL_INDEX(p_level, _debug_call_stack_pos); ERR_FAIL_INDEX(p_level, _call_stack.stack_pos);
int l = _debug_call_stack_pos - p_level - 1; int l = _call_stack.stack_pos - p_level - 1;
GDScriptInstance *instance = _call_stack[l].instance; GDScriptInstance *instance = _call_stack.levels[l].instance;
if (!instance) { if (!instance) {
return; return;
@ -353,10 +363,10 @@ ScriptInstance *GDScriptLanguage::debug_get_stack_level_instance(int p_level) {
return nullptr; 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; int l = _call_stack.stack_pos - p_level - 1;
ScriptInstance *instance = _call_stack[l].instance; ScriptInstance *instance = _call_stack.levels[l].instance;
return instance; return instance;
} }

View File

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

View File

@ -663,8 +663,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
if (GDScriptLanguage::get_singleton()->profiling) { if (GDScriptLanguage::get_singleton()->profiling) {
function_start_time = OS::get_singleton()->get_ticks_usec(); function_start_time = OS::get_singleton()->get_ticks_usec();
function_call_time = 0; function_call_time = 0;
profile.call_count++; profile.call_count.increment();
profile.frame_call_count++; profile.frame_call_count.increment();
} }
bool exit_ok = false; bool exit_ok = false;
bool awaited = false; bool awaited = false;
@ -3550,7 +3550,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
// line // line
bool do_break = false; bool do_break = false;
if (EngineDebugger::get_script_debugger()->get_lines_left() > 0) { if (unlikely(EngineDebugger::get_script_debugger()->get_lines_left() > 0)) {
if (EngineDebugger::get_script_debugger()->get_depth() <= 0) { if (EngineDebugger::get_script_debugger()->get_depth() <= 0) {
EngineDebugger::get_script_debugger()->set_lines_left(EngineDebugger::get_script_debugger()->get_lines_left() - 1); EngineDebugger::get_script_debugger()->set_lines_left(EngineDebugger::get_script_debugger()->get_lines_left() - 1);
} }
@ -3563,7 +3563,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
do_break = true; do_break = true;
} }
if (do_break) { if (unlikely(do_break)) {
GDScriptLanguage::get_singleton()->debug_break("Breakpoint", true); GDScriptLanguage::get_singleton()->debug_break("Breakpoint", true);
} }
@ -3630,11 +3630,13 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
#ifdef DEBUG_ENABLED #ifdef DEBUG_ENABLED
if (GDScriptLanguage::get_singleton()->profiling) { if (GDScriptLanguage::get_singleton()->profiling) {
uint64_t time_taken = OS::get_singleton()->get_ticks_usec() - function_start_time; uint64_t time_taken = OS::get_singleton()->get_ticks_usec() - function_start_time;
profile.total_time += time_taken; profile.total_time.add(time_taken);
profile.self_time += time_taken - function_call_time; profile.self_time.add(time_taken - function_call_time);
profile.frame_total_time += time_taken; profile.frame_total_time.add(time_taken);
profile.frame_self_time += time_taken - function_call_time; profile.frame_self_time.add(time_taken - function_call_time);
GDScriptLanguage::get_singleton()->script_frame_time += 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 not the last time it was interrupted by `await` or if it's the first time executing. // Check if this is not the last time it was interrupted by `await` or if it's the first time executing.