diff --git a/core/debugger/debugger_marshalls.cpp b/core/debugger/debugger_marshalls.cpp index 591b44869f57..3e6b7501c7ee 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 8ba93c309246..1b81623688ed 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 6c9293a2cfef..32dc060aa2b3 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 1bae71e37ac8..88d54907948b 100644 --- a/core/debugger/engine_debugger.h +++ b/core/debugger/engine_debugger.h @@ -126,7 +126,13 @@ class EngineDebugger { 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 b7471d7c82d2..b4d6fa417469 100644 --- a/core/debugger/remote_debugger.cpp +++ b/core/debugger/remote_debugger.cpp @@ -94,6 +94,7 @@ class RemoteDebugger::PerformanceProfiler : public EngineProfiler { 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 24283b0ed663..7c399178c66f 100644 --- a/core/debugger/remote_debugger.h +++ b/core/debugger/remote_debugger.h @@ -80,6 +80,17 @@ class RemoteDebugger : public EngineDebugger { 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 32725b76c1d7..e7d8654a0b97 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 edce089179b0..ee037b91fa9d 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 @@ class ScriptDebugger { 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 068f296c989e..dbf63c085226 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 e083e1746dd9..1e9b7c2c609b 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 8985387043e8..2c40f0e12067 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() != 3); - bool can_continue = p_data[0]; - String error = p_data[1]; - bool has_stackdump = p_data[2]; - breaked = true; - can_request_idle_draw = true; - can_debug = can_continue; - _update_buttons_state(); - _set_reason_text(error, MESSAGE_ERROR); - emit_signal(SNAME("breaked"), true, can_continue, error, has_stackdump); - if (is_move_to_foreground()) { - DisplayServer::get_singleton()->window_move_to_foreground(); - } - if (!error.is_empty()) { - tabs->set_current_tab(0); + ERR_FAIL_COND(p_data.size() != 4); + + ThreadDebugged td; + td.name = p_data[3]; + td.error = p_data[1]; + td.can_debug = p_data[0]; + td.has_stackdump = p_data[2]; + td.thread_id = p_thread_id; + static uint32_t order_inc = 0; + td.debug_order = order_inc++; + + threads_debugged.insert(p_thread_id, td); + + if (threads_debugged.size() == 1) { + // First thread that requests debug + debugging_thread_id = p_thread_id; + _thread_debug_enter(p_thread_id); + can_request_idle_draw = true; + if (is_move_to_foreground()) { + DisplayServer::get_singleton()->window_move_to_foreground(); + } + profiler->set_enabled(false, false); + visual_profiler->set_enabled(false); } - profiler->set_enabled(false, false); - visual_profiler->set_enabled(false); - inspector->clear_cache(); // Take a chance to force remote objects update. + _update_buttons_state(); } else if (p_msg == "debug_exit") { - breaked = false; - can_debug = false; - _clear_execution(); - _update_buttons_state(); - _set_reason_text(TTR("Execution resumed."), MESSAGE_SUCCESS); - emit_signal(SNAME("breaked"), false, false, "", false); + threads_debugged.erase(p_thread_id); + if (p_thread_id == debugging_thread_id) { + _clear_execution(); + if (threads_debugged.size() == 0) { + debugging_thread_id = Thread::UNASSIGNED_ID; + } else { + // Find next thread to debug. + uint32_t min_order = 0xFFFFFFFF; + uint64_t next_thread = Thread::UNASSIGNED_ID; + for (KeyValue T : threads_debugged) { + if (T.value.debug_order < min_order) { + min_order = T.value.debug_order; + next_thread = T.key; + } + } + + debugging_thread_id = next_thread; + } - profiler->set_enabled(true, false); - profiler->disable_seeking(); + if (debugging_thread_id == Thread::UNASSIGNED_ID) { + // Nothing else to debug. + profiler->set_enabled(true, false); + profiler->disable_seeking(); - visual_profiler->set_enabled(true); + visual_profiler->set_enabled(true); + + _set_reason_text(TTR("Execution resumed."), MESSAGE_SUCCESS); + emit_signal(SNAME("breaked"), false, false, "", false); + + _update_buttons_state(); + } else { + _thread_debug_enter(debugging_thread_id); + } + } else { + _update_buttons_state(); + } } else if (p_msg == "set_pid") { ERR_FAIL_COND(p_data.size() < 1); @@ -379,7 +427,6 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da vmem_total->set_tooltip_text(TTR("Bytes:") + " " + itos(total)); vmem_total->set_text(String::humanize_size(total)); - } else if (p_msg == "servers:drawn") { can_request_idle_draw = true; } else if (p_msg == "stack_dump") { @@ -414,11 +461,9 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da inspector->clear_stack_variables(); ERR_FAIL_COND(p_data.size() != 1); emit_signal(SNAME("stack_frame_vars"), p_data[0]); - } else if (p_msg == "stack_frame_var") { inspector->add_stack_variable(p_data); emit_signal(SNAME("stack_frame_var"), p_data); - } else if (p_msg == "output") { ERR_FAIL_COND(p_data.size() != 2); @@ -458,7 +503,6 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da frame_data.write[i] = p_data[i]; } performance_profiler->add_profile_frame(frame_data); - } else if (p_msg == "visual:profile_frame") { ServersDebugger::VisualProfilerFrame frame; frame.deserialize(p_data); @@ -477,7 +521,6 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da } } visual_profiler->add_frame_metric(metric); - } else if (p_msg == "error") { DebuggerMarshalls::OutputError oe; ERR_FAIL_COND_MSG(oe.deserialize(p_data) == false, "Failed to deserialize error message"); @@ -625,13 +668,11 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da } else { error_count++; } - } else if (p_msg == "servers:function_signature") { // Cache a profiler signature. ServersDebugger::ScriptFunctionSignature sig; sig.deserialize(p_data); profiler_signature[sig.id] = sig.name; - } else if (p_msg == "servers:profile_frame" || p_msg == "servers:profile_total") { EditorProfiler::Metric metric; ServersDebugger::ServersProfilerFrame frame; @@ -744,11 +785,9 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da } else { profiler->add_frame_metric(metric, true); } - } else if (p_msg == "request_quit") { emit_signal(SNAME("stop_requested")); _stop_and_notify(); - } else if (p_msg == "performance:profile_names") { Vector 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 336a11316344..7e9a7672735b 100644 --- a/editor/debugger/script_editor_debugger.h +++ b/editor/debugger/script_editor_debugger.h @@ -138,6 +138,7 @@ class ScriptEditorDebugger : public MarginContainer { Tree *stack_dump = nullptr; LineEdit *search = nullptr; + OptionButton *threads = nullptr; EditorDebuggerInspector *inspector = nullptr; SceneDebuggerTree *scene_tree = nullptr; @@ -152,19 +153,39 @@ class ScriptEditorDebugger : public MarginContainer { 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 @@ class ScriptEditorDebugger : public MarginContainer { 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 @@ class ScriptEditorDebugger : public MarginContainer { 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 @@ class ScriptEditorDebugger : public MarginContainer { 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 7117337827b9..0bf9f72a2cd9 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 d131ec6ab1fa..c41b1a0defd2 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 @@ class GDScriptLanguage : public ScriptLanguage { 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 2a7346940bf6..47ceee39432d 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 9bbfb14f3106..dfe66e66881d 100644 --- a/modules/gdscript/gdscript_function.h +++ b/modules/gdscript/gdscript_function.h @@ -539,12 +539,12 @@ class GDScriptFunction { 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 a8c9cfa5796d..44c4cb0fc393 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.