From 5e512b705e66ecc86025e56bebd7632b8a392390 Mon Sep 17 00:00:00 2001 From: Juan Linietsky Date: Sat, 29 Apr 2023 17:20:38 +0200 Subject: [PATCH] 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. --- core/debugger/debugger_marshalls.cpp | 10 +- core/debugger/debugger_marshalls.h | 1 + core/debugger/engine_debugger.cpp | 8 - core/debugger/engine_debugger.h | 8 +- core/debugger/remote_debugger.cpp | 109 ++++++++-- core/debugger/remote_debugger.h | 11 + core/debugger/script_debugger.cpp | 22 +- core/debugger/script_debugger.h | 23 +- doc/classes/Thread.xml | 1 - editor/debugger/editor_debugger_inspector.cpp | 2 +- editor/debugger/script_editor_debugger.cpp | 205 ++++++++++++------ editor/debugger/script_editor_debugger.h | 37 +++- modules/gdscript/gdscript.cpp | 41 ++-- modules/gdscript/gdscript.h | 68 +++--- modules/gdscript/gdscript_editor.cpp | 54 +++-- modules/gdscript/gdscript_function.h | 12 +- modules/gdscript/gdscript_vm.cpp | 20 +- 17 files changed, 409 insertions(+), 223 deletions(-) diff --git a/core/debugger/debugger_marshalls.cpp b/core/debugger/debugger_marshalls.cpp index 591b44869f5..3e6b7501c7e 100644 --- a/core/debugger/debugger_marshalls.cpp +++ b/core/debugger/debugger_marshalls.cpp @@ -67,6 +67,7 @@ Array DebuggerMarshalls::ScriptStackVariable::serialize(int max_size) { Array arr; arr.push_back(name); arr.push_back(type); + arr.push_back(value.get_type()); Variant var = value; 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; - Error err = encode_variant(var, nullptr, len, true); + Error err = encode_variant(var, nullptr, len, false); if (err != OK) { 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) { - CHECK_SIZE(p_arr, 3, "ScriptStackVariable"); + CHECK_SIZE(p_arr, 4, "ScriptStackVariable"); name = p_arr[0]; type = p_arr[1]; - value = p_arr[2]; - CHECK_END(p_arr, 3, "ScriptStackVariable"); + var_type = p_arr[2]; + value = p_arr[3]; + CHECK_END(p_arr, 4, "ScriptStackVariable"); return true; } diff --git a/core/debugger/debugger_marshalls.h b/core/debugger/debugger_marshalls.h index 8ba93c30924..1b81623688e 100644 --- a/core/debugger/debugger_marshalls.h +++ b/core/debugger/debugger_marshalls.h @@ -38,6 +38,7 @@ struct DebuggerMarshalls { String name; Variant value; int type = -1; + int var_type = -1; Array serialize(int max_size = 1 << 20); // 1 MiB default. bool deserialize(const Array &p_arr); diff --git a/core/debugger/engine_debugger.cpp b/core/debugger/engine_debugger.cpp index 6c9293a2cfe..32dc060aa2b 100644 --- a/core/debugger/engine_debugger.cpp +++ b/core/debugger/engine_debugger.cpp @@ -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); } -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) { frame_time = USEC_TO_SEC(p_frame_ticks); process_time = USEC_TO_SEC(p_process_ticks); diff --git a/core/debugger/engine_debugger.h b/core/debugger/engine_debugger.h index 1bae71e37ac..88d54907948 100644 --- a/core/debugger/engine_debugger.h +++ b/core/debugger/engine_debugger.h @@ -126,7 +126,13 @@ public: 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); - 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 send_message(const String &p_msg, const Array &p_data) = 0; diff --git a/core/debugger/remote_debugger.cpp b/core/debugger/remote_debugger.cpp index b7471d7c82d..b4d6fa41746 100644 --- a/core/debugger/remote_debugger.cpp +++ b/core/debugger/remote_debugger.cpp @@ -94,6 +94,7 @@ public: Error RemoteDebugger::_put_msg(String p_message, Array p_data) { Array msg; msg.push_back(p_message); + msg.push_back(Thread::get_caller_id()); msg.push_back(p_data); Error err = peer->put_message(msg); if (err != OK) { @@ -185,9 +186,9 @@ RemoteDebugger::ErrorMessage RemoteDebugger::_create_overflow_error(const String } void RemoteDebugger::flush_output() { + MutexLock lock(mutex); flush_thread = Thread::get_caller_id(); flushing = true; - MutexLock lock(mutex); if (!is_peer_connected()) { 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); } +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_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) { //this function is called when there is a debugger break (bug on script) //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()) { - return; // Peer does not support blocking IO. We could at least send the error though. + if (!peer->can_block()) { + return; // Peer does not support blocking IO. We could at least send the error though. + } } 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); ERR_FAIL_COND(!script_lang); 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) { allow_focus_steal_fn(); } send_message("debug_enter", msg); - 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); + Input::MouseMode 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()); } + mutex.lock(); while (is_peer_connected()) { + mutex.unlock(); flush_output(); - peer->poll(); - if (peer->has_message()) { - Array cmd = peer->get_message(); + _poll_messages(); + + if (_has_messages()) { + Array cmd = _get_message(); ERR_CONTINUE(cmd.size() != 2); 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 { 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()); - if (mouse_mode != Input::MOUSE_MODE_VISIBLE) { - Input::get_singleton()->set_mouse_mode(mouse_mode); + if (Thread::get_caller_id() == Thread::get_main_id()) { + 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(); - peer->poll(); - while (peer->has_message()) { - Array arr = peer->get_message(); + + _poll_messages(); + + while (_has_messages()) { + Array arr = _get_message(); ERR_CONTINUE(arr.size() != 2); ERR_CONTINUE(arr[0].get_type() != Variant::STRING); @@ -604,6 +673,8 @@ RemoteDebugger::RemoteDebugger(Ref p_peer) { eh.errfunc = _err_handler; eh.userdata = this; add_error_handler(&eh); + + messages.insert(Thread::get_main_id(), List()); } RemoteDebugger::~RemoteDebugger() { diff --git a/core/debugger/remote_debugger.h b/core/debugger/remote_debugger.h index 24283b0ed66..7c399178c66 100644 --- a/core/debugger/remote_debugger.h +++ b/core/debugger/remote_debugger.h @@ -80,6 +80,17 @@ private: bool flushing = false; Thread::ID flush_thread = 0; + struct Message { + String message; + Array data; + }; + + HashMap> messages; + + void _poll_messages(); + bool _has_messages(); + Array _get_message(); + PrintHandlerList phl; static void _print_handler(void *p_this, const String &p_string, bool p_error, bool p_rich); ErrorHandlerList eh; diff --git a/core/debugger/script_debugger.cpp b/core/debugger/script_debugger.cpp index 32725b76c1d..e7d8654a0b9 100644 --- a/core/debugger/script_debugger.cpp +++ b/core/debugger/script_debugger.cpp @@ -32,22 +32,19 @@ #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::error_stack_info; + void ScriptDebugger::set_lines_left(int p_left) { lines_left = p_left; } -int ScriptDebugger::get_lines_left() const { - return lines_left; -} - void ScriptDebugger::set_depth(int p_depth) { depth = p_depth; } -int ScriptDebugger::get_depth() const { - return depth; -} - void ScriptDebugger::insert_breakpoint(int p_line, const StringName &p_source) { if (!breakpoints.has(p_line)) { breakpoints[p_line] = HashSet(); @@ -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 { 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. 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); - error_stack_info.clear(); + error_stack_info.clear(); // Clear because this is thread local } Vector ScriptDebugger::get_error_stack_info() const { diff --git a/core/debugger/script_debugger.h b/core/debugger/script_debugger.h index edce089179b..ee037b91fa9 100644 --- a/core/debugger/script_debugger.h +++ b/core/debugger/script_debugger.h @@ -40,21 +40,25 @@ class ScriptDebugger { typedef ScriptLanguage::StackInfo StackInfo; - int lines_left = -1; - int depth = -1; bool skip_breakpoints = false; HashMap> breakpoints; - ScriptLanguage *break_lang = nullptr; - Vector error_stack_info; + static thread_local int lines_left; + static thread_local int depth; + static thread_local ScriptLanguage *break_lang; + static thread_local Vector error_stack_info; public: 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); - int get_depth() const; + _ALWAYS_INLINE_ int get_depth() const { + return depth; + } String breakpoint_find_source(const String &p_source) const; void set_break_language(ScriptLanguage *p_lang) { break_lang = p_lang; } @@ -63,7 +67,12 @@ public: bool is_skipping_breakpoints(); void insert_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(); const HashMap> &get_breakpoints() const { return breakpoints; } diff --git a/doc/classes/Thread.xml b/doc/classes/Thread.xml index 068f296c989..dbf63c08522 100644 --- a/doc/classes/Thread.xml +++ b/doc/classes/Thread.xml @@ -5,7 +5,6 @@ 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] 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. diff --git a/editor/debugger/editor_debugger_inspector.cpp b/editor/debugger/editor_debugger_inspector.cpp index e083e1746dd..1e9b7c2c609 100644 --- a/editor/debugger/editor_debugger_inspector.cpp +++ b/editor/debugger/editor_debugger_inspector.cpp @@ -232,7 +232,7 @@ void EditorDebuggerInspector::add_stack_variable(const Array &p_array) { PropertyHint h = PROPERTY_HINT_NONE; String hs; - if (v.get_type() == Variant::OBJECT) { + if (var.var_type == Variant::OBJECT) { v = Object::cast_to(v)->get_object_id(); h = PROPERTY_HINT_OBJECT_ID; hs = "Object"; diff --git a/editor/debugger/script_editor_debugger.cpp b/editor/debugger/script_editor_debugger.cpp index 8985387043e..2c40f0e1206 100644 --- a/editor/debugger/script_editor_debugger.cpp +++ b/editor/debugger/script_editor_debugger.cpp @@ -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() != 4); - 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; + 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); + } _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") { - 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 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); - profiler->disable_seeking(); + debugging_thread_id = next_thread; + } - 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") { 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 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 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 threadss; + for (KeyValue &I : threads_debugged) { + threadss.push_back(&I.value); + } + + threadss.sort_custom(); + 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); diff --git a/editor/debugger/script_editor_debugger.h b/editor/debugger/script_editor_debugger.h index 336a1131634..7e9a7672735 100644 --- a/editor/debugger/script_editor_debugger.h +++ b/editor/debugger/script_editor_debugger.h @@ -138,6 +138,7 @@ private: Tree *stack_dump = nullptr; LineEdit *search = nullptr; + OptionButton *threads = nullptr; EditorDebuggerInspector *inspector = nullptr; SceneDebuggerTree *scene_tree = nullptr; @@ -152,19 +153,39 @@ private: EditorPerformanceProfiler *performance_profiler = nullptr; OS::ProcessID remote_pid = 0; - bool breaked = false; - bool can_debug = false; bool move_to_foreground = true; bool can_request_idle_draw = false; 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 threads_debugged; + bool thread_list_updating = false; + + void _select_thread(int p_index); + EditorDebuggerNode::CameraOverride camera_override; void _stack_dump_frame_selected(); 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 _update_buttons_state(); void _remote_object_selected(ObjectID p_object); @@ -200,7 +221,7 @@ private: void _item_menu_id_pressed(int p_option); 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 _clear_execution(); @@ -213,6 +234,8 @@ private: String _format_frame_text(const ScriptLanguage::StackInfo *info); + void _thread_debug_enter(uint64_t p_thread_id); + protected: void _notification(int p_what); static void _bind_methods(); @@ -238,9 +261,9 @@ public: void debug_step(); void debug_break(); void debug_continue(); - bool is_breaked() const { return breaked; } - bool is_debuggable() const { return can_debug; } - bool is_session_active() { return peer.is_valid() && peer->is_peer_connected(); }; + 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() { return peer.is_valid() && peer->is_peer_connected(); } int get_remote_pid() const { return remote_pid; } bool is_move_to_foreground() const; diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index 7117337827b..0bf9f72a2cd 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -2094,10 +2094,7 @@ String GDScriptLanguage::get_extension() const { } void GDScriptLanguage::finish() { - if (_call_stack) { - memdelete_arr(_call_stack); - _call_stack = nullptr; - } + _call_stack.free(); // Clear the cache before parsing the script_list GDScriptCache::clear(); @@ -2140,12 +2137,12 @@ void GDScriptLanguage::profiling_start() { SelfList *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; @@ -2175,9 +2172,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++; @@ -2395,12 +2392,12 @@ void GDScriptLanguage::frame() { SelfList *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(); } } @@ -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(); } +thread_local GDScriptLanguage::CallStack GDScriptLanguage::_call_stack; + GDScriptLanguage::GDScriptLanguage() { calls = 0; ERR_FAIL_COND(singleton); @@ -2626,18 +2625,14 @@ GDScriptLanguage::GDScriptLanguage() { profiling = false; 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); if (EngineDebugger::is_active()) { //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 diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h index d131ec6ab1f..c41b1a0defd 100644 --- a/modules/gdscript/gdscript.h +++ b/modules/gdscript/gdscript.h @@ -364,12 +364,26 @@ class GDScriptLanguage : public ScriptLanguage { int *line = nullptr; }; - int _debug_parse_err_line; - String _debug_parse_err_file; - String _debug_error; - int _debug_call_stack_pos; - int _debug_max_call_stack; - CallLevel *_call_stack = nullptr; + static thread_local int _debug_parse_err_line; + static thread_local String _debug_parse_err_file; + static thread_local String _debug_error; + struct CallStack { + CallLevel *levels = 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); @@ -395,59 +409,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 (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); } - if (_debug_call_stack_pos >= _debug_max_call_stack) { + if (_call_stack.stack_pos >= _debug_max_call_stack) { //stack overflow _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); 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 (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); } - if (_debug_call_stack_pos == 0) { + if (_call_stack.stack_pos == 0) { _debug_error = "Stack Underflow (Engine Bug)"; EngineDebugger::get_script_debugger()->debug(this); return; } - _debug_call_stack_pos--; + _call_stack.stack_pos--; } virtual Vector debug_get_current_stack_info() override { - if (Thread::get_main_id() != Thread::get_caller_id()) { - return Vector(); - } - Vector 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_script_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_script_path(); } } return csi; diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index 2a7346940bf..47ceee39432 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -233,6 +233,10 @@ Script *GDScriptLanguage::create_script() const { /* 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) { // 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_error = p_error; 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; } else { 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) { - if (EngineDebugger::is_active() && Thread::get_caller_id() == Thread::get_main_id()) { + if (EngineDebugger::is_active()) { _debug_parse_err_line = -1; _debug_parse_err_file = ""; _debug_error = p_error; bool is_error_breakpoint = p_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; } else { return false; @@ -269,7 +279,7 @@ 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 { @@ -277,11 +287,11 @@ int GDScriptLanguage::debug_get_stack_level_line(int p_level) const { 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 { @@ -289,9 +299,9 @@ String GDScriptLanguage::debug_get_stack_level_function(int p_level) const { 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 { @@ -299,9 +309,9 @@ String GDScriptLanguage::debug_get_stack_level_source(int p_level) const { 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 *p_locals, List *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 *p 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> 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 &E : locals) { 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 * 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; @@ -353,10 +363,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; } diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h index 9bbfb14f310..dfe66e66881 100644 --- a/modules/gdscript/gdscript_function.h +++ b/modules/gdscript/gdscript_function.h @@ -539,12 +539,12 @@ private: struct Profile { StringName signature; - uint64_t call_count = 0; - uint64_t self_time = 0; - uint64_t total_time = 0; - uint64_t frame_call_count = 0; - uint64_t frame_self_time = 0; - uint64_t frame_total_time = 0; + SafeNumeric call_count; + SafeNumeric self_time; + SafeNumeric total_time; + SafeNumeric frame_call_count; + SafeNumeric frame_self_time; + SafeNumeric frame_total_time; uint64_t last_frame_call_count = 0; uint64_t last_frame_self_time = 0; uint64_t last_frame_total_time = 0; diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp index a8c9cfa5796..44c4cb0fc39 100644 --- a/modules/gdscript/gdscript_vm.cpp +++ b/modules/gdscript/gdscript_vm.cpp @@ -663,8 +663,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 awaited = false; @@ -3550,7 +3550,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a // line 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) { 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; } - if (do_break) { + if (unlikely(do_break)) { GDScriptLanguage::get_singleton()->debug_break("Breakpoint", true); } @@ -3630,11 +3630,13 @@ 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 not the last time it was interrupted by `await` or if it's the first time executing.