Forráskód Böngészése

Merge pull request #76582 from reduz/threaded-debugger

Support threads in the script debugger
Yuri Sizov 2 éve
szülő
commit
c4e582262f

+ 6 - 4
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;
 }
 

+ 1 - 0
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);

+ 0 - 8
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);

+ 7 - 1
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;

+ 90 - 19
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> &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<Message>());
 	}
 
+	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<RemoteDebuggerPeer> p_peer) {
 	eh.errfunc = _err_handler;
 	eh.userdata = this;
 	add_error_handler(&eh);
+
+	messages.insert(Thread::get_main_id(), List<Message>());
 }
 
 RemoteDebugger::~RemoteDebugger() {

+ 11 - 0
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<Thread::ID, List<Message>> 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;

+ 6 - 16
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::StackInfo> 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<StringName>();
@@ -66,13 +63,6 @@ void ScriptDebugger::remove_breakpoint(int p_line, const StringName &p_source) {
 	}
 }
 
-bool ScriptDebugger::is_breakpoint(int p_line, const StringName &p_source) const {
-	if (!breakpoints.has(p_line)) {
-		return false;
-	}
-	return breakpoints[p_line].has(p_source);
-}
-
 String ScriptDebugger::breakpoint_find_source(const String &p_source) const {
 	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<ScriptLanguage::StackInfo> ScriptDebugger::get_error_stack_info() const {

+ 16 - 7
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<int, HashSet<StringName>> breakpoints;
 
-	ScriptLanguage *break_lang = nullptr;
-	Vector<StackInfo> error_stack_info;
+	static thread_local int lines_left;
+	static thread_local int depth;
+	static thread_local ScriptLanguage *break_lang;
+	static thread_local Vector<StackInfo> 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<int, HashSet<StringName>> &get_breakpoints() const { return breakpoints; }
 

+ 0 - 1
doc/classes/Thread.xml

@@ -5,7 +5,6 @@
 	</brief_description>
 	<description>
 		A unit of execution in a process. Can run methods on [Object]s simultaneously. The use of synchronization via [Mutex] or [Semaphore] is advised if working with shared objects.
-		[b]Note:[/b] Breakpoints won't break on code if it's running in a thread. This is a current limitation of the GDScript debugger.
 		[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.

+ 1 - 1
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<EncodedObjectAsID>(v)->get_object_id();
 		h = PROPERTY_HINT_OBJECT_ID;
 		hs = "Object";

+ 138 - 69
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<uint64_t, ThreadDebugged> T : threads_debugged) {
+					if (T.value.debug_order < min_order) {
+						min_order = T.value.debug_order;
+						next_thread = T.key;
+					}
+				}
+
+				debugging_thread_id = next_thread;
+			}
 
-		profiler->set_enabled(true, false);
-		profiler->disable_seeking();
+			if (debugging_thread_id == Thread::UNASSIGNED_ID) {
+				// Nothing else to debug.
+				profiler->set_enabled(true, false);
+				profiler->disable_seeking();
 
-		visual_profiler->set_enabled(true);
+				visual_profiler->set_enabled(true);
+
+				_set_reason_text(TTR("Execution resumed."), MESSAGE_SUCCESS);
+				emit_signal(SNAME("breaked"), false, false, "", false);
+
+				_update_buttons_state();
+			} else {
+				_thread_debug_enter(debugging_thread_id);
+			}
+		} else {
+			_update_buttons_state();
+		}
 
 	} else if (p_msg == "set_pid") {
 		ERR_FAIL_COND(p_data.size() < 1);
@@ -379,7 +427,6 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da
 
 		vmem_total->set_tooltip_text(TTR("Bytes:") + " " + itos(total));
 		vmem_total->set_text(String::humanize_size(total));
-
 	} else if (p_msg == "servers:drawn") {
 		can_request_idle_draw = true;
 	} else if (p_msg == "stack_dump") {
@@ -414,11 +461,9 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da
 		inspector->clear_stack_variables();
 		ERR_FAIL_COND(p_data.size() != 1);
 		emit_signal(SNAME("stack_frame_vars"), p_data[0]);
-
 	} else if (p_msg == "stack_frame_var") {
 		inspector->add_stack_variable(p_data);
 		emit_signal(SNAME("stack_frame_var"), p_data);
-
 	} else if (p_msg == "output") {
 		ERR_FAIL_COND(p_data.size() != 2);
 
@@ -458,7 +503,6 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da
 			frame_data.write[i] = p_data[i];
 		}
 		performance_profiler->add_profile_frame(frame_data);
-
 	} else if (p_msg == "visual:profile_frame") {
 		ServersDebugger::VisualProfilerFrame frame;
 		frame.deserialize(p_data);
@@ -477,7 +521,6 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da
 			}
 		}
 		visual_profiler->add_frame_metric(metric);
-
 	} else if (p_msg == "error") {
 		DebuggerMarshalls::OutputError oe;
 		ERR_FAIL_COND_MSG(oe.deserialize(p_data) == false, "Failed to deserialize error message");
@@ -625,13 +668,11 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da
 		} else {
 			error_count++;
 		}
-
 	} else if (p_msg == "servers:function_signature") {
 		// Cache a profiler signature.
 		ServersDebugger::ScriptFunctionSignature sig;
 		sig.deserialize(p_data);
 		profiler_signature[sig.id] = sig.name;
-
 	} else if (p_msg == "servers:profile_frame" || p_msg == "servers:profile_total") {
 		EditorProfiler::Metric metric;
 		ServersDebugger::ServersProfilerFrame frame;
@@ -744,11 +785,9 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da
 		} else {
 			profiler->add_frame_metric(metric, true);
 		}
-
 	} else if (p_msg == "request_quit") {
 		emit_signal(SNAME("stop_requested"));
 		_stop_and_notify();
-
 	} else if (p_msg == "performance:profile_names") {
 		Vector<StringName> monitors;
 		monitors.resize(p_data.size());
@@ -757,13 +796,11 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da
 			monitors.set(i, p_data[i]);
 		}
 		performance_profiler->update_monitors(monitors);
-
 	} else if (p_msg == "filesystem:update_file") {
 		ERR_FAIL_COND(p_data.size() < 1);
 		if (EditorFileSystem::get_singleton()) {
 			EditorFileSystem::get_singleton()->update_file(p_data[0]);
 		}
-
 	} else {
 		int colon_index = p_msg.find_char(':');
 		ERR_FAIL_COND_MSG(colon_index < 1, "Invalid message received");
@@ -878,7 +915,7 @@ void ScriptEditorDebugger::_notification(int p_what) {
 					msg.push_back(cam->get_far());
 					_put_msg("scene:override_camera_3D:transform", msg);
 				}
-				if (breaked && can_request_idle_draw) {
+				if (is_breaked() && can_request_idle_draw) {
 					_put_msg("servers:draw", Array());
 					can_request_idle_draw = false;
 				}
@@ -888,11 +925,12 @@ void ScriptEditorDebugger::_notification(int p_what) {
 
 			while (peer.is_valid() && peer->has_message()) {
 				Array arr = peer->get_message();
-				if (arr.size() != 2 || arr[0].get_type() != Variant::STRING || arr[1].get_type() != Variant::ARRAY) {
+				if (arr.size() != 3 || arr[0].get_type() != Variant::STRING || arr[1].get_type() != Variant::INT || arr[2].get_type() != Variant::ARRAY) {
 					_stop_and_notify();
 					ERR_FAIL_MSG("Invalid message format received from peer");
 				}
-				_parse_message(arr[0], arr[1]);
+
+				_parse_message(arr[0], arr[1], arr[2]);
 
 				if (OS::get_singleton()->get_ticks_msec() > until) {
 					break;
@@ -959,8 +997,6 @@ void ScriptEditorDebugger::start(Ref<RemoteDebuggerPeer> p_peer) {
 	performance_profiler->reset();
 
 	set_process(true);
-	breaked = false;
-	can_debug = true;
 	camera_override = CameraOverride::OVERRIDE_NONE;
 
 	tabs->set_current_tab(0);
@@ -973,13 +1009,35 @@ void ScriptEditorDebugger::_update_buttons_state() {
 	const bool active = is_session_active();
 	const bool has_editor_tree = active && editor_remote_tree && editor_remote_tree->get_selected();
 	vmem_refresh->set_disabled(!active);
-	step->set_disabled(!active || !breaked || !can_debug);
-	next->set_disabled(!active || !breaked || !can_debug);
-	copy->set_disabled(!active || !breaked);
-	docontinue->set_disabled(!active || !breaked);
-	dobreak->set_disabled(!active || breaked);
+	step->set_disabled(!active || !is_breaked() || !is_debuggable());
+	next->set_disabled(!active || !is_breaked() || !is_debuggable());
+	copy->set_disabled(!active || !is_breaked());
+	docontinue->set_disabled(!active || !is_breaked());
+	dobreak->set_disabled(!active || is_breaked());
 	le_clear->set_disabled(!active);
 	le_set->set_disabled(!has_editor_tree);
+
+	thread_list_updating = true;
+	LocalVector<ThreadDebugged *> threadss;
+	for (KeyValue<uint64_t, ThreadDebugged> &I : threads_debugged) {
+		threadss.push_back(&I.value);
+	}
+
+	threadss.sort_custom<ThreadSort>();
+	threads->clear();
+	int32_t selected_index = -1;
+	for (uint32_t i = 0; i < threadss.size(); i++) {
+		if (debugging_thread_id == threadss[i]->thread_id) {
+			selected_index = i;
+		}
+		threads->add_item(threadss[i]->name);
+		threads->set_item_metadata(threads->get_item_count() - 1, threadss[i]->thread_id);
+	}
+	if (selected_index != -1) {
+		threads->select(selected_index);
+	}
+
+	thread_list_updating = false;
 }
 
 void ScriptEditorDebugger::_stop_and_notify() {
@@ -990,8 +1048,8 @@ void ScriptEditorDebugger::_stop_and_notify() {
 
 void ScriptEditorDebugger::stop() {
 	set_process(false);
-	breaked = false;
-	can_debug = false;
+	threads_debugged.clear();
+	debugging_thread_id = Thread::UNASSIGNED_ID;
 	remote_pid = 0;
 	_clear_execution();
 
@@ -1043,7 +1101,7 @@ void ScriptEditorDebugger::_profiler_activate(bool p_enable, int p_type) {
 }
 
 void ScriptEditorDebugger::_profiler_seeked() {
-	if (breaked) {
+	if (is_breaked()) {
 		return;
 	}
 	debug_break();
@@ -1067,7 +1125,7 @@ void ScriptEditorDebugger::_export_csv() {
 }
 
 String ScriptEditorDebugger::get_var_value(const String &p_var) const {
-	if (!breaked) {
+	if (!is_breaked()) {
 		return String();
 	}
 	return inspector->get_stack_variable(p_var);
@@ -1255,7 +1313,7 @@ bool ScriptEditorDebugger::request_stack_dump(const int &p_frame) {
 
 	Array msg;
 	msg.push_back(p_frame);
-	_put_msg("get_stack_frame_vars", msg);
+	_put_msg("get_stack_frame_vars", msg, debugging_thread_id);
 	return true;
 }
 
@@ -1407,7 +1465,7 @@ void ScriptEditorDebugger::set_breakpoint(const String &p_path, int p_line, bool
 	msg.push_back(p_path);
 	msg.push_back(p_line);
 	msg.push_back(p_enabled);
-	_put_msg("breakpoint", msg);
+	_put_msg("breakpoint", msg, debugging_thread_id != Thread::UNASSIGNED_ID ? debugging_thread_id : Thread::MAIN_ID);
 
 	TreeItem *path_item = breakpoints_tree->search_item_text(p_path);
 	if (path_item == nullptr) {
@@ -1450,7 +1508,7 @@ void ScriptEditorDebugger::set_breakpoint(const String &p_path, int p_line, bool
 }
 
 void ScriptEditorDebugger::reload_scripts() {
-	_put_msg("reload_scripts", Array());
+	_put_msg("reload_scripts", Array(), debugging_thread_id != Thread::UNASSIGNED_ID ? debugging_thread_id : Thread::MAIN_ID);
 }
 
 bool ScriptEditorDebugger::is_skip_breakpoints() {
@@ -1804,15 +1862,26 @@ ScriptEditorDebugger::ScriptEditorDebugger() {
 		sc->set_h_size_flags(SIZE_EXPAND_FILL);
 		parent_sc->add_child(sc);
 
+		VBoxContainer *stack_vb = memnew(VBoxContainer);
+		stack_vb->set_h_size_flags(SIZE_EXPAND_FILL);
+		sc->add_child(stack_vb);
+		HBoxContainer *thread_hb = memnew(HBoxContainer);
+		stack_vb->add_child(thread_hb);
+		thread_hb->add_child(memnew(Label(TTR("Thread:"))));
+		threads = memnew(OptionButton);
+		thread_hb->add_child(threads);
+		threads->set_h_size_flags(SIZE_EXPAND_FILL);
+		threads->connect("item_selected", callable_mp(this, &ScriptEditorDebugger::_select_thread));
+
 		stack_dump = memnew(Tree);
 		stack_dump->set_allow_reselect(true);
 		stack_dump->set_columns(1);
 		stack_dump->set_column_titles_visible(true);
 		stack_dump->set_column_title(0, TTR("Stack Frames"));
-		stack_dump->set_h_size_flags(SIZE_EXPAND_FILL);
 		stack_dump->set_hide_root(true);
+		stack_dump->set_v_size_flags(SIZE_EXPAND_FILL);
 		stack_dump->connect("cell_selected", callable_mp(this, &ScriptEditorDebugger::_stack_dump_frame_selected));
-		sc->add_child(stack_dump);
+		stack_vb->add_child(stack_dump);
 
 		VBoxContainer *inspector_vbox = memnew(VBoxContainer);
 		inspector_vbox->set_h_size_flags(SIZE_EXPAND_FILL);

+ 30 - 7
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<uint64_t, ThreadDebugged> 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;

+ 18 - 23
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<GDScriptFunction> *elem = function_list.first();
 	while (elem) {
-		elem->self()->profile.call_count = 0;
-		elem->self()->profile.self_time = 0;
-		elem->self()->profile.total_time = 0;
-		elem->self()->profile.frame_call_count = 0;
-		elem->self()->profile.frame_self_time = 0;
-		elem->self()->profile.frame_total_time = 0;
+		elem->self()->profile.call_count.set(0);
+		elem->self()->profile.self_time.set(0);
+		elem->self()->profile.total_time.set(0);
+		elem->self()->profile.frame_call_count.set(0);
+		elem->self()->profile.frame_self_time.set(0);
+		elem->self()->profile.frame_total_time.set(0);
 		elem->self()->profile.last_frame_call_count = 0;
 		elem->self()->profile.last_frame_self_time = 0;
 		elem->self()->profile.last_frame_total_time = 0;
@@ -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<GDScriptFunction> *elem = function_list.first();
 		while (elem) {
-			elem->self()->profile.last_frame_call_count = elem->self()->profile.frame_call_count;
-			elem->self()->profile.last_frame_self_time = elem->self()->profile.frame_self_time;
-			elem->self()->profile.last_frame_total_time = elem->self()->profile.frame_total_time;
-			elem->self()->profile.frame_call_count = 0;
-			elem->self()->profile.frame_self_time = 0;
-			elem->self()->profile.frame_total_time = 0;
+			elem->self()->profile.last_frame_call_count = elem->self()->profile.frame_call_count.get();
+			elem->self()->profile.last_frame_self_time = elem->self()->profile.frame_self_time.get();
+			elem->self()->profile.last_frame_total_time = elem->self()->profile.frame_total_time.get();
+			elem->self()->profile.frame_call_count.set(0);
+			elem->self()->profile.frame_self_time.set(0);
+			elem->self()->profile.frame_total_time.set(0);
 			elem = elem->next();
 		}
 	}
@@ -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

+ 37 - 31
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<StackInfo> debug_get_current_stack_info() override {
-		if (Thread::get_main_id() != Thread::get_caller_id()) {
-			return Vector<StackInfo>();
-		}
-
 		Vector<StackInfo> csi;
-		csi.resize(_debug_call_stack_pos);
-		for (int i = 0; i < _debug_call_stack_pos; i++) {
-			csi.write[_debug_call_stack_pos - i - 1].line = _call_stack[i].line ? *_call_stack[i].line : 0;
-			if (_call_stack[i].function) {
-				csi.write[_debug_call_stack_pos - i - 1].func = _call_stack[i].function->get_name();
-				csi.write[_debug_call_stack_pos - i - 1].file = _call_stack[i].function->get_script()->get_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;

+ 32 - 22
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<String> *p_locals, List<Variant> *p_values, int p_max_subitems, int p_max_depth) {
@@ -309,17 +319,17 @@ void GDScriptLanguage::debug_get_stack_level_locals(int p_level, List<String> *p
 		return;
 	}
 
-	ERR_FAIL_INDEX(p_level, _debug_call_stack_pos);
-	int l = _debug_call_stack_pos - p_level - 1;
+	ERR_FAIL_INDEX(p_level, _call_stack.stack_pos);
+	int l = _call_stack.stack_pos - p_level - 1;
 
-	GDScriptFunction *f = _call_stack[l].function;
+	GDScriptFunction *f = _call_stack.levels[l].function;
 
 	List<Pair<StringName, int>> locals;
 
-	f->debug_get_stack_member_state(*_call_stack[l].line, &locals);
+	f->debug_get_stack_member_state(*_call_stack.levels[l].line, &locals);
 	for (const Pair<StringName, int> &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<String> *
 		return;
 	}
 
-	ERR_FAIL_INDEX(p_level, _debug_call_stack_pos);
-	int l = _debug_call_stack_pos - p_level - 1;
+	ERR_FAIL_INDEX(p_level, _call_stack.stack_pos);
+	int l = _call_stack.stack_pos - p_level - 1;
 
-	GDScriptInstance *instance = _call_stack[l].instance;
+	GDScriptInstance *instance = _call_stack.levels[l].instance;
 
 	if (!instance) {
 		return;
@@ -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;
 }

+ 6 - 6
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<uint64_t> call_count;
+		SafeNumeric<uint64_t> self_time;
+		SafeNumeric<uint64_t> total_time;
+		SafeNumeric<uint64_t> frame_call_count;
+		SafeNumeric<uint64_t> frame_self_time;
+		SafeNumeric<uint64_t> frame_total_time;
 		uint64_t last_frame_call_count = 0;
 		uint64_t last_frame_self_time = 0;
 		uint64_t last_frame_total_time = 0;

+ 11 - 9
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.