Browse Source

GDScript: Update `get_stack()`, `print_stack()`, and `print_debug()`

Danil Alexeev 4 months ago
parent
commit
2bacfc8b59

+ 8 - 7
modules/gdscript/doc_classes/@GDScript.xml

@@ -84,7 +84,7 @@
 		<method name="get_stack">
 		<method name="get_stack">
 			<return type="Array" />
 			<return type="Array" />
 			<description>
 			<description>
-				Returns an array of dictionaries representing the current call stack. See also [method print_stack].
+				Returns an array of dictionaries representing the current call stack.
 				[codeblock]
 				[codeblock]
 				func _ready():
 				func _ready():
 				    foo()
 				    foo()
@@ -99,8 +99,8 @@
 				[codeblock lang=text]
 				[codeblock lang=text]
 				[{function:bar, line:12, source:res://script.gd}, {function:foo, line:9, source:res://script.gd}, {function:_ready, line:6, source:res://script.gd}]
 				[{function:bar, line:12, source:res://script.gd}, {function:foo, line:9, source:res://script.gd}, {function:_ready, line:6, source:res://script.gd}]
 				[/codeblock]
 				[/codeblock]
-				[b]Note:[/b] This function only works if the running instance is connected to a debugging server (i.e. an editor instance). [method get_stack] will not work in projects exported in release mode, or in projects exported in debug mode if not connected to a debugging server.
-				[b]Note:[/b] Calling this function from a [Thread] is not supported. Doing so will return an empty array.
+				See also [method print_debug], [method print_stack], and [method Engine.capture_script_backtraces].
+				[b]Note:[/b] By default, backtraces are only available in editor builds and debug builds. To enable them for release builds as well, you need to enable [member ProjectSettings.debug/settings/gdscript/always_track_call_stacks].
 			</description>
 			</description>
 		</method>
 		</method>
 		<method name="inst_to_dict" deprecated="Consider using [method JSON.from_native] or [method Object.get_property_list] instead.">
 		<method name="inst_to_dict" deprecated="Consider using [method JSON.from_native] or [method Object.get_property_list] instead.">
@@ -197,19 +197,20 @@
 				Test print
 				Test print
 				At: res://test.gd:15:_process()
 				At: res://test.gd:15:_process()
 				[/codeblock]
 				[/codeblock]
-				[b]Note:[/b] Calling this function from a [Thread] is not supported. Doing so will instead print the thread ID.
+				See also [method print_stack], [method get_stack], and [method Engine.capture_script_backtraces].
+				[b]Note:[/b] By default, backtraces are only available in editor builds and debug builds. To enable them for release builds as well, you need to enable [member ProjectSettings.debug/settings/gdscript/always_track_call_stacks].
 			</description>
 			</description>
 		</method>
 		</method>
 		<method name="print_stack">
 		<method name="print_stack">
 			<return type="void" />
 			<return type="void" />
 			<description>
 			<description>
-				Prints a stack trace at the current code location. See also [method get_stack].
+				Prints a stack trace at the current code location.
 				The output in the console may look like the following:
 				The output in the console may look like the following:
 				[codeblock lang=text]
 				[codeblock lang=text]
 				Frame 0 - res://test.gd:16 in function '_process'
 				Frame 0 - res://test.gd:16 in function '_process'
 				[/codeblock]
 				[/codeblock]
-				[b]Note:[/b] This function only works if the running instance is connected to a debugging server (i.e. an editor instance). [method print_stack] will not work in projects exported in release mode, or in projects exported in debug mode if not connected to a debugging server.
-				[b]Note:[/b] Calling this function from a [Thread] is not supported. Doing so will instead print the thread ID.
+				See also [method print_debug], [method get_stack], and [method Engine.capture_script_backtraces].
+				[b]Note:[/b] By default, backtraces are only available in editor builds and debug builds. To enable them for release builds as well, you need to enable [member ProjectSettings.debug/settings/gdscript/always_track_call_stacks].
 			</description>
 			</description>
 		</method>
 		</method>
 		<method name="range" qualifiers="vararg" keywords="seq">
 		<method name="range" qualifiers="vararg" keywords="seq">

+ 3 - 17
modules/gdscript/gdscript_utility_functions.cpp

@@ -340,13 +340,9 @@ struct GDScriptUtilityFunctionsDefinitions {
 			s += p_args[i]->operator String();
 			s += p_args[i]->operator String();
 		}
 		}
 
 
-		if (Thread::get_caller_id() == Thread::get_main_id()) {
-			ScriptLanguage *script = GDScriptLanguage::get_singleton();
-			if (script->debug_get_stack_level_count() > 0) {
-				s += "\n   At: " + script->debug_get_stack_level_source(0) + ":" + itos(script->debug_get_stack_level_line(0)) + ":" + script->debug_get_stack_level_function(0) + "()";
-			}
-		} else {
-			s += "\n   At: Cannot retrieve debug info outside the main thread. Thread ID: " + itos(Thread::get_caller_id());
+		ScriptLanguage *script = GDScriptLanguage::get_singleton();
+		if (script->debug_get_stack_level_count() > 0) {
+			s += "\n   At: " + script->debug_get_stack_level_source(0) + ":" + itos(script->debug_get_stack_level_line(0)) + ":" + script->debug_get_stack_level_function(0) + "()";
 		}
 		}
 
 
 		print_line(s);
 		print_line(s);
@@ -356,11 +352,6 @@ struct GDScriptUtilityFunctionsDefinitions {
 	static inline void print_stack(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
 	static inline void print_stack(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
 		DEBUG_VALIDATE_ARG_COUNT(0, 0);
 		DEBUG_VALIDATE_ARG_COUNT(0, 0);
 
 
-		if (Thread::get_caller_id() != Thread::get_main_id()) {
-			print_line("Cannot retrieve debug info outside the main thread. Thread ID: " + itos(Thread::get_caller_id()));
-			return;
-		}
-
 		ScriptLanguage *script = GDScriptLanguage::get_singleton();
 		ScriptLanguage *script = GDScriptLanguage::get_singleton();
 		for (int i = 0; i < script->debug_get_stack_level_count(); i++) {
 		for (int i = 0; i < script->debug_get_stack_level_count(); i++) {
 			print_line("Frame " + itos(i) + " - " + script->debug_get_stack_level_source(i) + ":" + itos(script->debug_get_stack_level_line(i)) + " in function '" + script->debug_get_stack_level_function(i) + "'");
 			print_line("Frame " + itos(i) + " - " + script->debug_get_stack_level_source(i) + ":" + itos(script->debug_get_stack_level_line(i)) + " in function '" + script->debug_get_stack_level_function(i) + "'");
@@ -371,11 +362,6 @@ struct GDScriptUtilityFunctionsDefinitions {
 	static inline void get_stack(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
 	static inline void get_stack(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
 		DEBUG_VALIDATE_ARG_COUNT(0, 0);
 		DEBUG_VALIDATE_ARG_COUNT(0, 0);
 
 
-		if (Thread::get_caller_id() != Thread::get_main_id()) {
-			*r_ret = TypedArray<Dictionary>();
-			return;
-		}
-
 		ScriptLanguage *script = GDScriptLanguage::get_singleton();
 		ScriptLanguage *script = GDScriptLanguage::get_singleton();
 		TypedArray<Dictionary> ret;
 		TypedArray<Dictionary> ret;
 		for (int i = 0; i < script->debug_get_stack_level_count(); i++) {
 		for (int i = 0; i < script->debug_get_stack_level_count(); i++) {

+ 4 - 0
modules/gdscript/tests/scripts/project.godot

@@ -9,6 +9,10 @@ config_version=5
 
 
 config/name="GDScript Integration Test Suite"
 config/name="GDScript Integration Test Suite"
 
 
+[debug]
+
+settings/gdscript/always_track_call_stacks=true
+
 [input]
 [input]
 
 
 test_input_action={
 test_input_action={

+ 33 - 7
modules/gdscript/tests/scripts/utils.notest.gd

@@ -1,10 +1,28 @@
+@warning_ignore_start("unsafe_call_argument")
+
 class_name Utils
 class_name Utils
 
 
+
 # `assert()` is not evaluated in non-debug builds. Do not use `assert()`
 # `assert()` is not evaluated in non-debug builds. Do not use `assert()`
 # for anything other than testing the `assert()` itself.
 # for anything other than testing the `assert()` itself.
 static func check(condition: Variant) -> void:
 static func check(condition: Variant) -> void:
-	if not condition:
-		printerr("Check failed.")
+	if condition:
+		return
+
+	printerr("Check failed. Backtrace (most recent call first):")
+	for stack: ScriptBacktrace in Engine.capture_script_backtraces():
+		if stack.get_language_name() == "GDScript":
+			var dir: String
+			for i: int in stack.get_frame_count():
+				if i == 0:
+					dir = stack.get_frame_file(i).trim_suffix("utils.notest.gd")
+				else:
+					printerr("  %s:%d @ %s()" % [
+						stack.get_frame_file(i).trim_prefix(dir),
+						stack.get_frame_line(i),
+						stack.get_frame_function(i),
+					])
+			break
 
 
 
 
 static func get_type(property: Dictionary, is_return: bool = false) -> String:
 static func get_type(property: Dictionary, is_return: bool = false) -> String:
@@ -34,7 +52,11 @@ static func get_type(property: Dictionary, is_return: bool = false) -> String:
 	return type_string(property.type)
 	return type_string(property.type)
 
 
 
 
-static func get_property_signature(property: Dictionary, base: Object = null, is_static: bool = false) -> String:
+static func get_property_signature(
+		property: Dictionary,
+		base: Object = null,
+		is_static: bool = false,
+) -> String:
 	if property.usage & PROPERTY_USAGE_CATEGORY:
 	if property.usage & PROPERTY_USAGE_CATEGORY:
 		return '@export_category("%s")' % str(property.name).c_escape()
 		return '@export_category("%s")' % str(property.name).c_escape()
 	if property.usage & PROPERTY_USAGE_GROUP:
 	if property.usage & PROPERTY_USAGE_GROUP:
@@ -88,13 +110,17 @@ static func get_human_readable_hint_string(property: Dictionary) -> String:
 	return property.hint_string
 	return property.hint_string
 
 
 
 
-static func print_property_extended_info(property: Dictionary, base: Object = null, is_static: bool = false) -> void:
+static func print_property_extended_info(
+		property: Dictionary,
+		base: Object = null,
+		is_static: bool = false,
+) -> void:
 	print(get_property_signature(property, base, is_static))
 	print(get_property_signature(property, base, is_static))
 	print('  hint=%s hint_string="%s" usage=%s class_name=&"%s"' % [
 	print('  hint=%s hint_string="%s" usage=%s class_name=&"%s"' % [
 		get_property_hint_name(property.hint).trim_prefix("PROPERTY_HINT_"),
 		get_property_hint_name(property.hint).trim_prefix("PROPERTY_HINT_"),
 		get_human_readable_hint_string(property).c_escape(),
 		get_human_readable_hint_string(property).c_escape(),
 		get_property_usage_string(property.usage).replace("PROPERTY_USAGE_", ""),
 		get_property_usage_string(property.usage).replace("PROPERTY_USAGE_", ""),
-		property.class_name.c_escape(),
+		str(property.class_name).c_escape(),
 	])
 	])
 
 
 
 
@@ -107,7 +133,7 @@ static func get_method_signature(method: Dictionary, is_signal: bool = false) ->
 	var args: Array[Dictionary] = method.args
 	var args: Array[Dictionary] = method.args
 	var default_args: Array = method.default_args
 	var default_args: Array = method.default_args
 	var mandatory_argc: int = args.size() - default_args.size()
 	var mandatory_argc: int = args.size() - default_args.size()
-	for i in args.size():
+	for i: int in args.size():
 		if i > 0:
 		if i > 0:
 			result += ", "
 			result += ", "
 		var arg: Dictionary = args[i]
 		var arg: Dictionary = args[i]
@@ -253,7 +279,7 @@ static func get_property_usage_string(usage: int) -> String:
 		result += "PROPERTY_USAGE_DEFAULT|"
 		result += "PROPERTY_USAGE_DEFAULT|"
 		usage &= ~PROPERTY_USAGE_DEFAULT
 		usage &= ~PROPERTY_USAGE_DEFAULT
 
 
-	for flag in FLAGS:
+	for flag: Array in FLAGS:
 		if usage & flag[0]:
 		if usage & flag[0]:
 			result += flag[1] + "|"
 			result += flag[1] + "|"
 			usage &= ~flag[0]
 			usage &= ~flag[0]