summaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/gdscript/doc_classes/@GDScript.xml16
-rw-r--r--modules/gdscript/gdscript.cpp1
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp78
-rw-r--r--modules/gdscript/gdscript_analyzer.h11
-rw-r--r--modules/gdscript/gdscript_compiler.cpp3
-rw-r--r--modules/gdscript/gdscript_parser.cpp137
-rw-r--r--modules/gdscript/gdscript_tokenizer.cpp17
-rw-r--r--modules/gdscript/gdscript_vm.cpp32
-rw-r--r--modules/gdscript/language_server/gdscript_text_document.cpp1
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/gdscript_duplicate.gd5
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/gdscript_duplicate.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/gdscript_duplicate_class.notest.gd1
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/gdscript_duplicate.gd6
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/gdscript_duplicate_class.notest.gd1
-rw-r--r--modules/gdscript/tests/scripts/parser/features/allow_id_similar_to_keyword_in_ascii.gd3
-rw-r--r--modules/gdscript/tests/scripts/parser/features/allow_id_similar_to_keyword_in_ascii.out (renamed from modules/gdscript/tests/scripts/analyzer/features/gdscript_duplicate.out)1
-rw-r--r--modules/gdscript/tests/scripts/parser/features/allow_strings_as_comments.gd21
-rw-r--r--modules/gdscript/tests/scripts/parser/features/allow_strings_as_comments.out (renamed from modules/gdscript/tests/scripts/parser/features/super_class_check.out)1
-rw-r--r--modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.gd16
-rw-r--r--modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.out6
-rw-r--r--modules/gdscript/tests/scripts/parser/features/super_class_check.gd13
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/confusable_identifier.gd7
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/confusable_identifier.out6
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/standalone_expression.gd1
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/standalone_expression.out6
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/static_duplicate.gd19
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/static_duplicate.out9
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/static_duplicate_preload.notest.gd5
-rw-r--r--modules/gltf/gltf_document.cpp368
-rw-r--r--modules/gltf/gltf_document.h3
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/BuildInfo.cs13
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs4
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs68
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs22
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs16
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs50
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs7
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs4
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs6
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/VerbosityLevelId.cs11
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/GodotSynchronizationContext.cs35
-rw-r--r--modules/multiplayer/editor/editor_network_profiler.cpp6
-rw-r--r--modules/multiplayer/multiplayer_spawner.cpp18
-rw-r--r--modules/webp/SCsub1
-rw-r--r--modules/webxr/native/library_godot_webxr.js7
45 files changed, 749 insertions, 315 deletions
diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml
index e05b17168d..923b2fe30d 100644
--- a/modules/gdscript/doc_classes/@GDScript.xml
+++ b/modules/gdscript/doc_classes/@GDScript.xml
@@ -304,7 +304,7 @@
<return type="void" />
<param index="0" name="names" type="String" />
<description>
- Export an [int] or [String] property as an enumerated list of options. If the property is an [int], then the index of the value is stored, in the same order the values are provided. You can add specific identifiers for allowed values using a colon. If the property is a [String], then the value is stored.
+ Export an [int] or [String] property as an enumerated list of options. If the property is an [int], then the index of the value is stored, in the same order the values are provided. You can add explicit values using a colon. If the property is a [String], then the value is stored.
See also [constant PROPERTY_HINT_ENUM].
[codeblock]
@export_enum("Warrior", "Magician", "Thief") var character_class: int
@@ -357,6 +357,20 @@
[codeblock]
@export_flags("Fire", "Water", "Earth", "Wind") var spell_elements = 0
[/codeblock]
+ You can add explicit values using a colon:
+ [codeblock]
+ @export_flags("Self:4", "Allies:8", "Foes:16") var spell_targets = 0
+ [/codeblock]
+ You can also combine several flags:
+ [codeblock]
+ @export_flags("Self:4", "Allies:8", "Self and Allies:12", "Foes:16")
+ var spell_targets = 0
+ [/codeblock]
+ [b]Note:[/b] A flag value must be at least [code]1[/code] and at most [code]2 ** 32 - 1[/code].
+ [b]Note:[/b] Unlike [annotation @export_enum], the previous explicit value is not taken into account. In the following example, A is 16, B is 2, C is 4.
+ [codeblock]
+ @export_flags("A:16", "B", "C") var x
+ [/codeblock]
</description>
</annotation>
<annotation name="@export_flags_2d_navigation">
diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp
index a876229276..fe79f37454 100644
--- a/modules/gdscript/gdscript.cpp
+++ b/modules/gdscript/gdscript.cpp
@@ -2578,7 +2578,6 @@ GDScriptLanguage::GDScriptLanguage() {
#ifdef DEBUG_ENABLED
GLOBAL_DEF("debug/gdscript/warnings/enable", true);
- GLOBAL_DEF("debug/gdscript/warnings/treat_warnings_as_errors", false);
GLOBAL_DEF("debug/gdscript/warnings/exclude_addons", true);
for (int i = 0; i < (int)GDScriptWarning::WARNING_MAX; i++) {
GDScriptWarning::Code code = (GDScriptWarning::Code)i;
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index 78e437b42a..de0dacece3 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -42,6 +42,11 @@
#include "gdscript_utility_functions.h"
#include "scene/resources/packed_scene.h"
+#if defined(TOOLS_ENABLED) && !defined(DISABLE_DEPRECATED)
+#define SUGGEST_GODOT4_RENAMES
+#include "editor/renames_map_3_to_4.h"
+#endif
+
#define UNNAMED_ENUM "<anonymous enum>"
#define ENUM_SEPARATOR "::"
@@ -1576,11 +1581,8 @@ void GDScriptAnalyzer::resolve_function_body(GDScriptParser::FunctionNode *p_fun
resolve_suite(p_function->body);
- GDScriptParser::DataType return_type = p_function->body->get_datatype();
-
- if (!p_function->get_datatype().is_hard_type() && return_type.is_set()) {
+ if (!p_function->get_datatype().is_hard_type() && p_function->body->get_datatype().is_set()) {
// Use the suite inferred type if return isn't explicitly set.
- return_type.type_source = GDScriptParser::DataType::INFERRED;
p_function->set_datatype(p_function->body->get_datatype());
} else if (p_function->get_datatype().is_hard_type() && (p_function->get_datatype().kind != GDScriptParser::DataType::BUILTIN || p_function->get_datatype().builtin_type != Variant::NIL)) {
if (!p_function->body->has_return && (p_is_lambda || p_function->identifier->name != GDScriptLanguage::get_singleton()->strings._init)) {
@@ -2604,9 +2606,8 @@ void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_o
p_binary_op->set_datatype(result);
}
-#ifdef TOOLS_ENABLED
-#ifndef DISABLE_DEPRECATED
-const char *GDScriptAnalyzer::get_rename_from_map(const char *map[][2], String key) {
+#ifdef SUGGEST_GODOT4_RENAMES
+const char *get_rename_from_map(const char *map[][2], String key) {
for (int index = 0; map[index][0]; index++) {
if (map[index][0] == key) {
return map[index][1];
@@ -2617,39 +2618,39 @@ const char *GDScriptAnalyzer::get_rename_from_map(const char *map[][2], String k
// Checks if an identifier/function name has been renamed in Godot 4, uses ProjectConverter3To4 for rename map.
// Returns the new name if found, nullptr otherwise.
-const char *GDScriptAnalyzer::check_for_renamed_identifier(String identifier, GDScriptParser::Node::Type type) {
+const char *check_for_renamed_identifier(String identifier, GDScriptParser::Node::Type type) {
switch (type) {
case GDScriptParser::Node::IDENTIFIER: {
// Check properties
- const char *result = get_rename_from_map(ProjectConverter3To4::gdscript_properties_renames, identifier);
+ const char *result = get_rename_from_map(RenamesMap3To4::gdscript_properties_renames, identifier);
if (result) {
return result;
}
// Check enum values
- result = get_rename_from_map(ProjectConverter3To4::enum_renames, identifier);
+ result = get_rename_from_map(RenamesMap3To4::enum_renames, identifier);
if (result) {
return result;
}
// Check color constants
- result = get_rename_from_map(ProjectConverter3To4::color_renames, identifier);
+ result = get_rename_from_map(RenamesMap3To4::color_renames, identifier);
if (result) {
return result;
}
// Check type names
- result = get_rename_from_map(ProjectConverter3To4::class_renames, identifier);
+ result = get_rename_from_map(RenamesMap3To4::class_renames, identifier);
if (result) {
return result;
}
- return get_rename_from_map(ProjectConverter3To4::builtin_types_renames, identifier);
+ return get_rename_from_map(RenamesMap3To4::builtin_types_renames, identifier);
}
case GDScriptParser::Node::CALL: {
- const char *result = get_rename_from_map(ProjectConverter3To4::gdscript_function_renames, identifier);
+ const char *result = get_rename_from_map(RenamesMap3To4::gdscript_function_renames, identifier);
if (result) {
return result;
}
// Built-in Types are mistaken for function calls when the built-in type is not found.
// Check built-in types if function rename not found
- return get_rename_from_map(ProjectConverter3To4::builtin_types_renames, identifier);
+ return get_rename_from_map(RenamesMap3To4::builtin_types_renames, identifier);
}
// Signal references don't get parsed through the GDScriptAnalyzer. No support for signal rename hints.
default:
@@ -2657,8 +2658,7 @@ const char *GDScriptAnalyzer::check_for_renamed_identifier(String identifier, GD
return nullptr;
}
}
-#endif // DISABLE_DEPRECATED
-#endif // TOOLS_ENABLED
+#endif // SUGGEST_GODOT4_RENAMES
void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_await, bool p_is_root) {
bool all_is_constant = true;
@@ -3017,7 +3017,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
push_error(vformat(R"*(Cannot call non-static function "%s()" from static function "%s()".)*", p_call->function_name, parent_function->identifier->name), p_call);
} else if (!is_self && base_type.is_meta_type && !is_static) {
base_type.is_meta_type = false; // For `to_string()`.
- push_error(vformat(R"*(Cannot call non-static function "%s()" on a class directly. Make an instance instead.)*", p_call->function_name), p_call);
+ push_error(vformat(R"*(Cannot call non-static function "%s()" on the class "%s" directly. Make an instance instead.)*", p_call->function_name, base_type.to_string()), p_call);
} else if (is_self && !is_static) {
mark_lambda_use_self();
}
@@ -3081,8 +3081,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
}
if (!found && (is_self || (base_type.is_hard_type() && base_type.kind == GDScriptParser::DataType::BUILTIN))) {
String base_name = is_self && !p_call->is_super ? "self" : base_type.to_string();
-#ifdef TOOLS_ENABLED
-#ifndef DISABLE_DEPRECATED
+#ifdef SUGGEST_GODOT4_RENAMES
String rename_hint = String();
if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::Code::RENAMED_IN_GD4_HINT)).booleanize()) {
const char *renamed_function_name = check_for_renamed_identifier(p_call->function_name, p_call->type);
@@ -3091,12 +3090,9 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
}
}
push_error(vformat(R"*(Function "%s()" not found in base %s.%s)*", p_call->function_name, base_name, rename_hint), p_call->is_super ? p_call : p_call->callee);
-#else // !DISABLE_DEPRECATED
- push_error(vformat(R"*(Function "%s()" not found in base %s.)*", p_call->function_name, base_name), p_call->is_super ? p_call : p_call->callee);
-#endif // DISABLE_DEPRECATED
#else
push_error(vformat(R"*(Function "%s()" not found in base %s.)*", p_call->function_name, base_name), p_call->is_super ? p_call : p_call->callee);
-#endif
+#endif // SUGGEST_GODOT4_RENAMES
} else if (!found && (!p_call->is_super && base_type.is_hard_type() && base_type.kind == GDScriptParser::DataType::NATIVE && base_type.is_meta_type)) {
push_error(vformat(R"*(Static function "%s()" not found in base "%s".)*", p_call->function_name, base_type.native_type), p_call);
}
@@ -3286,8 +3282,7 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
p_identifier->reduced_value = result;
p_identifier->set_datatype(type_from_variant(result, p_identifier));
} else if (base.is_hard_type()) {
-#ifdef TOOLS_ENABLED
-#ifndef DISABLE_DEPRECATED
+#ifdef SUGGEST_GODOT4_RENAMES
String rename_hint = String();
if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::Code::RENAMED_IN_GD4_HINT)).booleanize()) {
const char *renamed_identifier_name = check_for_renamed_identifier(name, p_identifier->type);
@@ -3296,12 +3291,9 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
}
}
push_error(vformat(R"(Cannot find constant "%s" on base "%s".%s)", name, base.to_string(), rename_hint), p_identifier);
-#else // !DISABLE_DEPRECATED
- push_error(vformat(R"(Cannot find constant "%s" on base "%s".)", name, base.to_string()), p_identifier);
-#endif // DISABLE_DEPRECATED
#else
push_error(vformat(R"(Cannot find constant "%s" on base "%s".)", name, base.to_string()), p_identifier);
-#endif
+#endif // SUGGEST_GODOT4_RENAMES
}
} else {
switch (base.builtin_type) {
@@ -3330,8 +3322,7 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
}
}
if (base.is_hard_type()) {
-#ifdef TOOLS_ENABLED
-#ifndef DISABLE_DEPRECATED
+#ifdef SUGGEST_GODOT4_RENAMES
String rename_hint = String();
if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::Code::RENAMED_IN_GD4_HINT)).booleanize()) {
const char *renamed_identifier_name = check_for_renamed_identifier(name, p_identifier->type);
@@ -3340,12 +3331,9 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
}
}
push_error(vformat(R"(Cannot find property "%s" on base "%s".%s)", name, base.to_string(), rename_hint), p_identifier);
-#else // !DISABLE_DEPRECATED
- push_error(vformat(R"(Cannot find property "%s" on base "%s".)", name, base.to_string()), p_identifier);
-#endif // DISABLE_DEPRECATED
#else
push_error(vformat(R"(Cannot find property "%s" on base "%s".)", name, base.to_string()), p_identifier);
-#endif
+#endif // SUGGEST_GODOT4_RENAMES
}
}
}
@@ -3685,8 +3673,7 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident
if (GDScriptUtilityFunctions::function_exists(name)) {
push_error(vformat(R"(Built-in function "%s" cannot be used as an identifier.)", name), p_identifier);
} else {
-#ifdef TOOLS_ENABLED
-#ifndef DISABLE_DEPRECATED
+#ifdef SUGGEST_GODOT4_RENAMES
String rename_hint = String();
if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::Code::RENAMED_IN_GD4_HINT)).booleanize()) {
const char *renamed_identifier_name = check_for_renamed_identifier(name, p_identifier->type);
@@ -3695,12 +3682,9 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident
}
}
push_error(vformat(R"(Identifier "%s" not declared in the current scope.%s)", name, rename_hint), p_identifier);
-#else // !DISABLE_DEPRECATED
- push_error(vformat(R"(Identifier "%s" not declared in the current scope.)", name), p_identifier);
-#endif // DISABLE_DEPRECATED
#else
push_error(vformat(R"(Identifier "%s" not declared in the current scope.)", name), p_identifier);
-#endif
+#endif // SUGGEST_GODOT4_RENAMES
}
GDScriptParser::DataType dummy;
dummy.kind = GDScriptParser::DataType::VARIANT;
@@ -4564,6 +4548,16 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, bo
base_script = base_script->get_base_script();
}
+ // If the base is a script, it might be trying to access members of the Script class itself.
+ if (p_base_type.is_meta_type && !p_is_constructor && (p_base_type.kind == GDScriptParser::DataType::SCRIPT || p_base_type.kind == GDScriptParser::DataType::CLASS)) {
+ MethodInfo info;
+ StringName script_class = p_base_type.kind == GDScriptParser::DataType::SCRIPT ? p_base_type.script_type->get_class_name() : StringName(GDScript::get_class_static());
+
+ if (ClassDB::get_method_info(script_class, function_name, &info)) {
+ return function_signature_from_info(info, r_return_type, r_par_types, r_default_arg_count, r_static, r_vararg);
+ }
+ }
+
if (p_is_constructor) {
// Native types always have a default constructor.
r_return_type = p_base_type;
diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h
index a4c84db6b9..cdeba374c7 100644
--- a/modules/gdscript/gdscript_analyzer.h
+++ b/modules/gdscript/gdscript_analyzer.h
@@ -37,10 +37,6 @@
#include "gdscript_cache.h"
#include "gdscript_parser.h"
-#ifdef TOOLS_ENABLED
-#include "editor/project_converter_3_to_4.h"
-#endif
-
class GDScriptAnalyzer {
GDScriptParser *parser = nullptr;
HashMap<String, Ref<GDScriptParserRef>> depended_parsers;
@@ -137,13 +133,6 @@ class GDScriptAnalyzer {
bool is_shadowing(GDScriptParser::IdentifierNode *p_local, const String &p_context);
#endif
-#ifdef TOOLS_ENABLED
-#ifndef DISABLE_DEPRECATED
- const char *get_rename_from_map(const char *map[][2], String key);
- const char *check_for_renamed_identifier(String identifier, GDScriptParser::Node::Type type);
-#endif // DISABLE_DEPRECATED
-#endif // TOOLS_ENABLED
-
public:
Error resolve_inheritance();
Error resolve_interface();
diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp
index 210550a674..46cd4b0d55 100644
--- a/modules/gdscript/gdscript_compiler.cpp
+++ b/modules/gdscript/gdscript_compiler.cpp
@@ -591,6 +591,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
}
} else if (callee->type == GDScriptParser::Node::SUBSCRIPT) {
const GDScriptParser::SubscriptNode *subscript = static_cast<const GDScriptParser::SubscriptNode *>(call->callee);
+
if (subscript->is_attribute) {
// May be static built-in method call.
if (!call->is_super && subscript->base->type == GDScriptParser::Node::IDENTIFIER && GDScriptParser::get_builtin_type(static_cast<GDScriptParser::IdentifierNode *>(subscript->base)->name) < Variant::VARIANT_MAX) {
@@ -614,7 +615,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
} else {
class_name = base.type.native_type == StringName() ? base.type.script_type->get_instance_base_type() : base.type.native_type;
}
- if (!subscript->base->is_constant && ClassDB::class_exists(class_name) && ClassDB::has_method(class_name, call->function_name)) {
+ if (ClassDB::class_exists(class_name) && ClassDB::has_method(class_name, call->function_name)) {
MethodBind *method = ClassDB::get_method(class_name, call->function_name);
if (_can_use_ptrcall(method, arguments)) {
// Exact arguments, use ptrcall.
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index 0a1a64cb59..b5cb5a4680 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -175,7 +175,7 @@ void GDScriptParser::push_warning(const Node *p_source, GDScriptWarning::Code p_
warning.leftmost_column = p_source->leftmost_column;
warning.rightmost_column = p_source->rightmost_column;
- if (warn_level == GDScriptWarning::WarnLevel::ERROR || bool(GLOBAL_GET("debug/gdscript/warnings/treat_warnings_as_errors"))) {
+ if (warn_level == GDScriptWarning::WarnLevel::ERROR) {
push_error(warning.get_message() + String(" (Warning treated as error.)"), p_source);
return;
}
@@ -483,24 +483,34 @@ void GDScriptParser::parse_program() {
current_class = head;
bool can_have_class_or_extends = true;
- while (match(GDScriptTokenizer::Token::ANNOTATION)) {
- AnnotationNode *annotation = parse_annotation(AnnotationInfo::SCRIPT | AnnotationInfo::STANDALONE | AnnotationInfo::CLASS_LEVEL);
- if (annotation != nullptr) {
- if (annotation->applies_to(AnnotationInfo::SCRIPT)) {
- // `@icon` needs to be applied in the parser. See GH-72444.
- if (annotation->name == SNAME("@icon")) {
- annotation->apply(this, head);
+ while (!check(GDScriptTokenizer::Token::TK_EOF)) {
+ if (match(GDScriptTokenizer::Token::ANNOTATION)) {
+ AnnotationNode *annotation = parse_annotation(AnnotationInfo::SCRIPT | AnnotationInfo::STANDALONE | AnnotationInfo::CLASS_LEVEL);
+ if (annotation != nullptr) {
+ if (annotation->applies_to(AnnotationInfo::SCRIPT)) {
+ // `@icon` needs to be applied in the parser. See GH-72444.
+ if (annotation->name == SNAME("@icon")) {
+ annotation->apply(this, head);
+ } else {
+ head->annotations.push_back(annotation);
+ }
} else {
- head->annotations.push_back(annotation);
+ annotation_stack.push_back(annotation);
+ // This annotation must appear after script-level annotations
+ // and class_name/extends (ex: could be @onready or @export),
+ // so we stop looking for script-level stuff.
+ can_have_class_or_extends = false;
+ break;
}
- } else {
- annotation_stack.push_back(annotation);
- // This annotation must appear after script-level annotations
- // and class_name/extends (ex: could be @onready or @export),
- // so we stop looking for script-level stuff.
- can_have_class_or_extends = false;
- break;
}
+ } else if (check(GDScriptTokenizer::Token::LITERAL) && current.literal.get_type() == Variant::STRING) {
+ // Allow strings in class body as multiline comments.
+ advance();
+ if (!match(GDScriptTokenizer::Token::NEWLINE)) {
+ push_error("Expected newline after comment string.");
+ }
+ } else {
+ break;
}
}
@@ -524,6 +534,16 @@ void GDScriptParser::parse_program() {
end_statement("superclass");
}
break;
+ case GDScriptTokenizer::Token::LITERAL:
+ if (current.literal.get_type() == Variant::STRING) {
+ // Allow strings in class body as multiline comments.
+ advance();
+ if (!match(GDScriptTokenizer::Token::NEWLINE)) {
+ push_error("Expected newline after comment string.");
+ }
+ break;
+ }
+ [[fallthrough]];
default:
// No tokens are allowed between script annotations and class/extends.
can_have_class_or_extends = false;
@@ -829,6 +849,16 @@ void GDScriptParser::parse_class_body(bool p_is_multiline) {
case GDScriptTokenizer::Token::DEDENT:
class_end = true;
break;
+ case GDScriptTokenizer::Token::LITERAL:
+ if (current.literal.get_type() == Variant::STRING) {
+ // Allow strings in class body as multiline comments.
+ advance();
+ if (!match(GDScriptTokenizer::Token::NEWLINE)) {
+ push_error("Expected newline after comment string.");
+ }
+ break;
+ }
+ [[fallthrough]];
default:
// Display a completion with identifiers.
make_completion_context(COMPLETION_IDENTIFIER, nullptr);
@@ -1675,6 +1705,12 @@ GDScriptParser::Node *GDScriptParser::parse_statement() {
// Standalone lambdas can't be used, so make this an error.
push_error("Standalone lambdas cannot be accessed. Consider assigning it to a variable.", expression);
break;
+ case Node::LITERAL:
+ if (static_cast<GDScriptParser::LiteralNode *>(expression)->value.get_type() == Variant::STRING) {
+ // Allow strings as multiline comments.
+ break;
+ }
+ [[fallthrough]];
default:
push_warning(expression, GDScriptWarning::STANDALONE_EXPRESSION);
}
@@ -2145,7 +2181,12 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_precedence(Precedence p_pr
make_completion_context(COMPLETION_IDENTIFIER, nullptr);
GDScriptTokenizer::Token token = current;
- ParseFunction prefix_rule = get_rule(token.type)->prefix;
+ GDScriptTokenizer::Token::Type token_type = token.type;
+ if (token.is_identifier()) {
+ // Allow keywords that can be treated as identifiers.
+ token_type = GDScriptTokenizer::Token::IDENTIFIER;
+ }
+ ParseFunction prefix_rule = get_rule(token_type)->prefix;
if (prefix_rule == nullptr) {
// Expected expression. Let the caller give the proper error message.
@@ -3010,7 +3051,14 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_get_node(ExpressionNode *p
path_state = PATH_STATE_NODE_NAME;
} else if (current.is_node_name()) {
advance();
- get_node->full_path += previous.get_identifier();
+ String identifier = previous.get_identifier();
+#ifdef DEBUG_ENABLED
+ // Check spoofing.
+ if (TS->has_feature(TextServer::FEATURE_UNICODE_SECURITY) && TS->spoof_check(identifier)) {
+ push_warning(get_node, GDScriptWarning::CONFUSABLE_IDENTIFIER, identifier);
+ }
+#endif
+ get_node->full_path += identifier;
path_state = PATH_STATE_NODE_NAME;
} else if (!check(GDScriptTokenizer::Token::SLASH) && !check(GDScriptTokenizer::Token::PERCENT)) {
@@ -3637,15 +3685,6 @@ template <PropertyHint t_hint, Variant::Type t_type>
bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node *p_node) {
ERR_FAIL_COND_V_MSG(p_node->type != Node::VARIABLE, false, vformat(R"("%s" annotation can only be applied to variables.)", p_annotation->name));
- {
- const int max_flags = 32;
-
- if (t_hint == PropertyHint::PROPERTY_HINT_FLAGS && p_annotation->resolved_arguments.size() > max_flags) {
- push_error(vformat(R"(The argument count limit for "@export_flags" is exceeded (%d/%d).)", p_annotation->resolved_arguments.size(), max_flags), p_annotation);
- return false;
- }
- }
-
VariableNode *variable = static_cast<VariableNode *>(p_node);
if (variable->exported) {
push_error(vformat(R"(Annotation "%s" cannot be used with another "@export" annotation.)", p_annotation->name), p_annotation);
@@ -3659,14 +3698,50 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
String hint_string;
for (int i = 0; i < p_annotation->resolved_arguments.size(); i++) {
- if (p_annotation->name != SNAME("@export_placeholder") && String(p_annotation->resolved_arguments[i]).contains(",")) {
- push_error(vformat(R"(Argument %d of annotation "%s" contains a comma. Use separate arguments instead.)", i + 1, p_annotation->name), p_annotation->arguments[i]);
- return false;
+ String arg_string = String(p_annotation->resolved_arguments[i]);
+
+ if (p_annotation->name != SNAME("@export_placeholder")) {
+ if (arg_string.is_empty()) {
+ push_error(vformat(R"(Argument %d of annotation "%s" is empty.)", i + 1, p_annotation->name), p_annotation->arguments[i]);
+ return false;
+ }
+ if (arg_string.contains(",")) {
+ push_error(vformat(R"(Argument %d of annotation "%s" contains a comma. Use separate arguments instead.)", i + 1, p_annotation->name), p_annotation->arguments[i]);
+ return false;
+ }
+ }
+
+ if (p_annotation->name == SNAME("@export_flags")) {
+ const int64_t max_flags = 32;
+ Vector<String> t = arg_string.split(":", true, 1);
+ if (t[0].is_empty()) {
+ push_error(vformat(R"(Invalid argument %d of annotation "@export_flags": Expected flag name.)", i + 1), p_annotation->arguments[i]);
+ return false;
+ }
+ if (t.size() == 2) {
+ if (t[1].is_empty()) {
+ push_error(vformat(R"(Invalid argument %d of annotation "@export_flags": Expected flag value.)", i + 1), p_annotation->arguments[i]);
+ return false;
+ }
+ if (!t[1].is_valid_int()) {
+ push_error(vformat(R"(Invalid argument %d of annotation "@export_flags": The flag value must be a valid integer.)", i + 1), p_annotation->arguments[i]);
+ return false;
+ }
+ int64_t value = t[1].to_int();
+ if (value < 1 || value >= (1LL << max_flags)) {
+ push_error(vformat(R"(Invalid argument %d of annotation "@export_flags": The flag value must be at least 1 and at most 2 ** %d - 1.)", i + 1, max_flags), p_annotation->arguments[i]);
+ return false;
+ }
+ } else if (i >= max_flags) {
+ push_error(vformat(R"(Invalid argument %d of annotation "@export_flags": Starting from argument %d, the flag value must be specified explicitly.)", i + 1, max_flags + 1), p_annotation->arguments[i]);
+ return false;
+ }
}
+
if (i > 0) {
hint_string += ",";
}
- hint_string += String(p_annotation->resolved_arguments[i]);
+ hint_string += arg_string;
}
variable->export_info.hint_string = hint_string;
diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp
index d7f1114fd3..d586380c41 100644
--- a/modules/gdscript/gdscript_tokenizer.cpp
+++ b/modules/gdscript/gdscript_tokenizer.cpp
@@ -168,7 +168,11 @@ bool GDScriptTokenizer::Token::is_identifier() const {
switch (type) {
case IDENTIFIER:
case MATCH: // Used in String.match().
- case CONST_INF: // Used in Vector{2,3,4}.INF
+ // Allow constants to be treated as regular identifiers.
+ case CONST_PI:
+ case CONST_INF:
+ case CONST_NAN:
+ case CONST_TAU:
return true;
default:
return false;
@@ -188,6 +192,10 @@ bool GDScriptTokenizer::Token::is_node_name() const {
case CLASS_NAME:
case CLASS:
case CONST:
+ case CONST_PI:
+ case CONST_INF:
+ case CONST_NAN:
+ case CONST_TAU:
case CONTINUE:
case ELIF:
case ELSE:
@@ -530,9 +538,12 @@ void GDScriptTokenizer::make_keyword_list() {
#endif // DEBUG_ENABLED
GDScriptTokenizer::Token GDScriptTokenizer::potential_identifier() {
+ bool only_ascii = _peek(-1) < 128;
+
// Consume all identifier characters.
while (is_unicode_identifier_continue(_peek())) {
- _advance();
+ char32_t c = _advance();
+ only_ascii = only_ascii && c < 128;
}
int len = _current - _start;
@@ -587,7 +598,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::potential_identifier() {
#ifdef DEBUG_ENABLED
// Additional checks for identifiers but only in debug and if it's available in TextServer.
- if (TS->has_feature(TextServer::FEATURE_UNICODE_SECURITY)) {
+ if (!only_ascii && TS->has_feature(TextServer::FEATURE_UNICODE_SECURITY)) {
int64_t confusable = TS->is_confusable(name, keyword_list);
if (confusable >= 0) {
push_error(vformat(R"(Identifier "%s" is visually similar to the GDScript keyword "%s" and thus not allowed.)", name, keyword_list[confusable]));
diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp
index b99f5d2685..7a11ea52f0 100644
--- a/modules/gdscript/gdscript_vm.cpp
+++ b/modules/gdscript/gdscript_vm.cpp
@@ -447,6 +447,9 @@ void (*type_init_function_table[])(Variant *) = {
#define OP_GET_BASIS get_basis
#define OP_GET_RID get_rid
+#define METHOD_CALL_ON_NULL_VALUE_ERROR(method_pointer) "Cannot call method '" + (method_pointer)->get_name() + "' on a null value."
+#define METHOD_CALL_ON_FREED_INSTANCE_ERROR(method_pointer) "Cannot call method '" + (method_pointer)->get_name() + "' on a previously freed instance."
+
Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_args, int p_argcount, Callable::CallError &r_err, CallState *p_state) {
OPCODES_TABLE;
@@ -1675,10 +1678,10 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
bool freed = false;
Object *base_obj = base->get_validated_object_with_check(freed);
if (freed) {
- err_text = "Trying to call a function on a previously freed instance.";
+ err_text = METHOD_CALL_ON_FREED_INSTANCE_ERROR(method);
OPCODE_BREAK;
} else if (!base_obj) {
- err_text = "Trying to call a function on a null value.";
+ err_text = METHOD_CALL_ON_NULL_VALUE_ERROR(method);
OPCODE_BREAK;
}
#else
@@ -1839,10 +1842,10 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
bool freed = false; \
Object *base_obj = base->get_validated_object_with_check(freed); \
if (freed) { \
- err_text = "Trying to call a function on a previously freed instance."; \
+ err_text = METHOD_CALL_ON_FREED_INSTANCE_ERROR(method); \
OPCODE_BREAK; \
} else if (!base_obj) { \
- err_text = "Trying to call a function on a null value."; \
+ err_text = METHOD_CALL_ON_NULL_VALUE_ERROR(method); \
OPCODE_BREAK; \
} \
const void **argptrs = call_args_ptr; \
@@ -1941,10 +1944,10 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
bool freed = false;
Object *base_obj = base->get_validated_object_with_check(freed);
if (freed) {
- err_text = "Trying to call a function on a previously freed instance.";
+ err_text = METHOD_CALL_ON_FREED_INSTANCE_ERROR(method);
OPCODE_BREAK;
} else if (!base_obj) {
- err_text = "Trying to call a function on a null value.";
+ err_text = METHOD_CALL_ON_NULL_VALUE_ERROR(method);
OPCODE_BREAK;
}
#else
@@ -1969,7 +1972,13 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
VariantInternal::initialize(ret, Variant::OBJECT);
Object **ret_opaque = VariantInternal::get_object(ret);
method->ptrcall(base_obj, argptrs, ret_opaque);
- VariantInternal::update_object_id(ret);
+ if (method->is_return_type_raw_object_ptr()) {
+ // The Variant has to participate in the ref count since the method returns a raw Object *.
+ VariantInternal::object_assign(ret, *ret_opaque);
+ } else {
+ // The method, in case it returns something, returns an already encapsulated object.
+ VariantInternal::update_object_id(ret);
+ }
#ifdef DEBUG_ENABLED
if (GDScriptLanguage::get_singleton()->profiling) {
@@ -1996,10 +2005,10 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
bool freed = false;
Object *base_obj = base->get_validated_object_with_check(freed);
if (freed) {
- err_text = "Trying to call a function on a previously freed instance.";
+ err_text = METHOD_CALL_ON_FREED_INSTANCE_ERROR(method);
OPCODE_BREAK;
} else if (!base_obj) {
- err_text = "Trying to call a function on a null value.";
+ err_text = METHOD_CALL_ON_NULL_VALUE_ERROR(method);
OPCODE_BREAK;
}
#else
@@ -3427,7 +3436,10 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
String message_str;
if (_code_ptr[ip + 2] != 0) {
GET_VARIANT_PTR(message, 1);
- message_str = *message;
+ Variant message_var = *message;
+ if (message->get_type() != Variant::NIL) {
+ message_str = message_var;
+ }
}
if (message_str.is_empty()) {
err_text = "Assertion failed.";
diff --git a/modules/gdscript/language_server/gdscript_text_document.cpp b/modules/gdscript/language_server/gdscript_text_document.cpp
index b9e6921034..35fbdca949 100644
--- a/modules/gdscript/language_server/gdscript_text_document.cpp
+++ b/modules/gdscript/language_server/gdscript_text_document.cpp
@@ -108,6 +108,7 @@ void GDScriptTextDocument::didSave(const Variant &p_param) {
scr->reload(true);
}
scr->update_exports();
+ ScriptEditor::get_singleton()->reload_scripts(true);
ScriptEditor::get_singleton()->update_docs_from_script(scr);
}
}
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/gdscript_duplicate.gd b/modules/gdscript/tests/scripts/analyzer/errors/gdscript_duplicate.gd
deleted file mode 100644
index 966d2b0aa2..0000000000
--- a/modules/gdscript/tests/scripts/analyzer/errors/gdscript_duplicate.gd
+++ /dev/null
@@ -1,5 +0,0 @@
-const TestClass = preload("gdscript_duplicate_class.notest.gd")
-
-func test():
- # (TestClass as GDScript).duplicate() exists
- TestClass.duplicate()
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/gdscript_duplicate.out b/modules/gdscript/tests/scripts/analyzer/errors/gdscript_duplicate.out
deleted file mode 100644
index b2c7fec86e..0000000000
--- a/modules/gdscript/tests/scripts/analyzer/errors/gdscript_duplicate.out
+++ /dev/null
@@ -1,2 +0,0 @@
-GDTEST_ANALYZER_ERROR
-Cannot call non-static function "duplicate()" on a class directly. Make an instance instead.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/gdscript_duplicate_class.notest.gd b/modules/gdscript/tests/scripts/analyzer/errors/gdscript_duplicate_class.notest.gd
deleted file mode 100644
index 61510e14cd..0000000000
--- a/modules/gdscript/tests/scripts/analyzer/errors/gdscript_duplicate_class.notest.gd
+++ /dev/null
@@ -1 +0,0 @@
-extends Node
diff --git a/modules/gdscript/tests/scripts/analyzer/features/gdscript_duplicate.gd b/modules/gdscript/tests/scripts/analyzer/features/gdscript_duplicate.gd
deleted file mode 100644
index 030daf502c..0000000000
--- a/modules/gdscript/tests/scripts/analyzer/features/gdscript_duplicate.gd
+++ /dev/null
@@ -1,6 +0,0 @@
-const TestClass = preload("gdscript_duplicate_class.notest.gd")
-
-func test():
- # TestClass.duplicate() fails
- @warning_ignore("return_value_discarded")
- (TestClass as GDScript).duplicate()
diff --git a/modules/gdscript/tests/scripts/analyzer/features/gdscript_duplicate_class.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/gdscript_duplicate_class.notest.gd
deleted file mode 100644
index 61510e14cd..0000000000
--- a/modules/gdscript/tests/scripts/analyzer/features/gdscript_duplicate_class.notest.gd
+++ /dev/null
@@ -1 +0,0 @@
-extends Node
diff --git a/modules/gdscript/tests/scripts/parser/features/allow_id_similar_to_keyword_in_ascii.gd b/modules/gdscript/tests/scripts/parser/features/allow_id_similar_to_keyword_in_ascii.gd
new file mode 100644
index 0000000000..390d314b94
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/allow_id_similar_to_keyword_in_ascii.gd
@@ -0,0 +1,3 @@
+func test():
+ var P1 = "ok" # Technically it is visually similar to keyword "PI" but allowed since it's in ASCII range.
+ print(P1)
diff --git a/modules/gdscript/tests/scripts/analyzer/features/gdscript_duplicate.out b/modules/gdscript/tests/scripts/parser/features/allow_id_similar_to_keyword_in_ascii.out
index d73c5eb7cd..1b47ed10dc 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/gdscript_duplicate.out
+++ b/modules/gdscript/tests/scripts/parser/features/allow_id_similar_to_keyword_in_ascii.out
@@ -1 +1,2 @@
GDTEST_OK
+ok
diff --git a/modules/gdscript/tests/scripts/parser/features/allow_strings_as_comments.gd b/modules/gdscript/tests/scripts/parser/features/allow_strings_as_comments.gd
new file mode 100644
index 0000000000..3ecd65ad9c
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/allow_strings_as_comments.gd
@@ -0,0 +1,21 @@
+"""
+This is a comment.
+"""
+
+@tool
+
+"""
+This is also a comment.
+"""
+
+extends RefCounted
+
+'''
+This is a comment too.
+'''
+
+func test():
+ """
+ This too is a comment.
+ """
+ print("ok")
diff --git a/modules/gdscript/tests/scripts/parser/features/super_class_check.out b/modules/gdscript/tests/scripts/parser/features/allow_strings_as_comments.out
index d73c5eb7cd..1b47ed10dc 100644
--- a/modules/gdscript/tests/scripts/parser/features/super_class_check.out
+++ b/modules/gdscript/tests/scripts/parser/features/allow_strings_as_comments.out
@@ -1 +1,2 @@
GDTEST_OK
+ok
diff --git a/modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.gd b/modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.gd
new file mode 100644
index 0000000000..7e1982597c
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.gd
@@ -0,0 +1,16 @@
+func test():
+ # The following keywords are allowed as identifiers:
+ var match = "match"
+ print(match)
+
+ var PI = "PI"
+ print(PI)
+
+ var INF = "INF"
+ print(INF)
+
+ var NAN = "NAN"
+ print(NAN)
+
+ var TAU = "TAU"
+ print(TAU)
diff --git a/modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.out b/modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.out
new file mode 100644
index 0000000000..aae2ae13d5
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.out
@@ -0,0 +1,6 @@
+GDTEST_OK
+match
+PI
+INF
+NAN
+TAU
diff --git a/modules/gdscript/tests/scripts/parser/features/super_class_check.gd b/modules/gdscript/tests/scripts/parser/features/super_class_check.gd
deleted file mode 100644
index edfc45a8d8..0000000000
--- a/modules/gdscript/tests/scripts/parser/features/super_class_check.gd
+++ /dev/null
@@ -1,13 +0,0 @@
-# https://github.com/godotengine/godot/issues/71994
-
-func test():
- pass
-
-class A extends RefCounted:
- pass
-
-class B extends A:
- # Parsing `duplicate()` here would throw this error:
- # Parse Error: The function signature doesn't match the parent. Parent signature is "duplicate(bool = default) -> Resource".
- func duplicate():
- pass
diff --git a/modules/gdscript/tests/scripts/parser/warnings/confusable_identifier.gd b/modules/gdscript/tests/scripts/parser/warnings/confusable_identifier.gd
index e2caac8ffd..41b38c4bba 100644
--- a/modules/gdscript/tests/scripts/parser/warnings/confusable_identifier.gd
+++ b/modules/gdscript/tests/scripts/parser/warnings/confusable_identifier.gd
@@ -1,5 +1,12 @@
+extends Node
+
func test():
var port = 0 # Only latin characters.
var pοrt = 1 # The "ο" is Greek omicron.
prints(port, pοrt)
+
+# Do not call this since nodes aren't in the tree. It is just a parser check.
+func nodes():
+ var _node1 = $port # Only latin characters.
+ var _node2 = $pοrt # The "ο" is Greek omicron.
diff --git a/modules/gdscript/tests/scripts/parser/warnings/confusable_identifier.out b/modules/gdscript/tests/scripts/parser/warnings/confusable_identifier.out
index c483396443..c189204285 100644
--- a/modules/gdscript/tests/scripts/parser/warnings/confusable_identifier.out
+++ b/modules/gdscript/tests/scripts/parser/warnings/confusable_identifier.out
@@ -1,6 +1,10 @@
GDTEST_OK
>> WARNING
->> Line: 3
+>> Line: 5
+>> CONFUSABLE_IDENTIFIER
+>> The identifier "pοrt" has misleading characters and might be confused with something else.
+>> WARNING
+>> Line: 12
>> CONFUSABLE_IDENTIFIER
>> The identifier "pοrt" has misleading characters and might be confused with something else.
0 1
diff --git a/modules/gdscript/tests/scripts/parser/warnings/standalone_expression.gd b/modules/gdscript/tests/scripts/parser/warnings/standalone_expression.gd
index 18ea260fa2..dc4223ec2d 100644
--- a/modules/gdscript/tests/scripts/parser/warnings/standalone_expression.gd
+++ b/modules/gdscript/tests/scripts/parser/warnings/standalone_expression.gd
@@ -1,6 +1,5 @@
func test():
# The following statements should all be reported as standalone expressions:
- "This is a standalone expression"
1234
0.0 + 0.0
Color(1, 1, 1)
diff --git a/modules/gdscript/tests/scripts/parser/warnings/standalone_expression.out b/modules/gdscript/tests/scripts/parser/warnings/standalone_expression.out
index 99ec87438e..a2c67a6e51 100644
--- a/modules/gdscript/tests/scripts/parser/warnings/standalone_expression.out
+++ b/modules/gdscript/tests/scripts/parser/warnings/standalone_expression.out
@@ -8,14 +8,10 @@ GDTEST_OK
>> STANDALONE_EXPRESSION
>> Standalone expression (the line has no effect).
>> WARNING
->> Line: 5
+>> Line: 6
>> STANDALONE_EXPRESSION
>> Standalone expression (the line has no effect).
>> WARNING
>> Line: 7
>> STANDALONE_EXPRESSION
>> Standalone expression (the line has no effect).
->> WARNING
->> Line: 8
->> STANDALONE_EXPRESSION
->> Standalone expression (the line has no effect).
diff --git a/modules/gdscript/tests/scripts/runtime/features/static_duplicate.gd b/modules/gdscript/tests/scripts/runtime/features/static_duplicate.gd
deleted file mode 100644
index 418501dcc5..0000000000
--- a/modules/gdscript/tests/scripts/runtime/features/static_duplicate.gd
+++ /dev/null
@@ -1,19 +0,0 @@
-const PreloadClass = preload("static_duplicate_preload.notest.gd")
-const PreloadClassAlias = PreloadClass
-
-func test():
- var dup_preload_one = PreloadClass.duplicate()
- print(dup_preload_one == Vector2.ONE)
-
- var dup_preload_two = (PreloadClass as GDScript).duplicate()
- print(dup_preload_two is GDScript)
-
- var dup_preload_alias_one = PreloadClassAlias.duplicate()
- print(dup_preload_alias_one == Vector2.ONE)
-
- var dup_preload_alias_two = (PreloadClassAlias as GDScript).duplicate()
- print(dup_preload_alias_two is GDScript)
-
- var PreloadClassAsGDScript = PreloadClass as GDScript
- var dup_preload_class_as_gdscript_one = PreloadClassAsGDScript.duplicate()
- print(dup_preload_class_as_gdscript_one is GDScript)
diff --git a/modules/gdscript/tests/scripts/runtime/features/static_duplicate.out b/modules/gdscript/tests/scripts/runtime/features/static_duplicate.out
deleted file mode 100644
index 34cd5c7652..0000000000
--- a/modules/gdscript/tests/scripts/runtime/features/static_duplicate.out
+++ /dev/null
@@ -1,9 +0,0 @@
-GDTEST_OK
-preload duplicate
-true
-true
-preload duplicate
-true
-true
-preload duplicate
-false
diff --git a/modules/gdscript/tests/scripts/runtime/features/static_duplicate_preload.notest.gd b/modules/gdscript/tests/scripts/runtime/features/static_duplicate_preload.notest.gd
deleted file mode 100644
index 291ffc2c0b..0000000000
--- a/modules/gdscript/tests/scripts/runtime/features/static_duplicate_preload.notest.gd
+++ /dev/null
@@ -1,5 +0,0 @@
-extends RefCounted
-
-static func duplicate() -> Vector2:
- print("preload duplicate")
- return Vector2.ONE
diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp
index bd3ee1881f..028028a103 100644
--- a/modules/gltf/gltf_document.cpp
+++ b/modules/gltf/gltf_document.cpp
@@ -4894,13 +4894,11 @@ Error GLTFDocument::_serialize_animations(Ref<GLTFState> p_state) {
return OK;
}
for (int32_t player_i = 0; player_i < p_state->animation_players.size(); player_i++) {
- List<StringName> animation_names;
AnimationPlayer *animation_player = p_state->animation_players[player_i];
- animation_player->get_animation_list(&animation_names);
- if (animation_names.size()) {
- for (int animation_name_i = 0; animation_name_i < animation_names.size(); animation_name_i++) {
- _convert_animation(p_state, animation_player, animation_names[animation_name_i]);
- }
+ List<StringName> animations;
+ animation_player->get_animation_list(&animations);
+ for (StringName animation_name : animations) {
+ _convert_animation(p_state, animation_player, animation_name);
}
}
Array animations;
@@ -6028,7 +6026,7 @@ void GLTFDocument::_import_animation(Ref<GLTFState> p_state, AnimationPlayer *p_
Node *root = p_animation_player->get_parent();
ERR_FAIL_COND(root == nullptr);
HashMap<GLTFNodeIndex, Node *>::Iterator node_element = p_state->scene_nodes.find(node_index);
- ERR_CONTINUE_MSG(!node_element, vformat("Unable to find node %d for animation", node_index));
+ ERR_CONTINUE_MSG(!node_element, vformat("Unable to find node %d for animation.", node_index));
node_path = root->get_path_to(node_element->value);
HashMap<GLTFNodeIndex, ImporterMeshInstance3D *>::Iterator mesh_instance_element = p_state->scene_mesh_instances.find(node_index);
if (mesh_instance_element) {
@@ -6450,58 +6448,179 @@ GLTFAnimation::Track GLTFDocument::_convert_animation_track(Ref<GLTFState> p_sta
for (int32_t key_i = 0; key_i < key_count; key_i++) {
times.write[key_i] = p_animation->track_get_key_time(p_track_i, key_i);
}
+ double anim_end = p_animation->get_length();
if (track_type == Animation::TYPE_SCALE_3D) {
- p_track.scale_track.times = times;
- p_track.scale_track.interpolation = gltf_interpolation;
- p_track.scale_track.values.resize(key_count);
- for (int32_t key_i = 0; key_i < key_count; key_i++) {
- Vector3 scale;
- Error err = p_animation->scale_track_get_key(p_track_i, key_i, &scale);
- ERR_CONTINUE(err != OK);
- p_track.scale_track.values.write[key_i] = scale;
+ if (gltf_interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE) {
+ gltf_interpolation = GLTFAnimation::INTERP_LINEAR;
+ p_track.scale_track.times.clear();
+ p_track.scale_track.values.clear();
+ // CATMULLROMSPLINE or CUBIC_SPLINE have to be baked, apologies.
+ const double increment = 1.0 / BAKE_FPS;
+ double time = 0.0;
+ bool last = false;
+ while (true) {
+ Vector3 scale;
+ Error err = p_animation->scale_track_interpolate(p_track_i, time, &scale);
+ ERR_CONTINUE(err != OK);
+ p_track.scale_track.values.push_back(scale);
+ p_track.scale_track.times.push_back(time);
+ if (last) {
+ break;
+ }
+ time += increment;
+ if (time >= anim_end) {
+ last = true;
+ time = anim_end;
+ }
+ }
+ } else {
+ p_track.scale_track.times = times;
+ p_track.scale_track.interpolation = gltf_interpolation;
+ p_track.scale_track.values.resize(key_count);
+ for (int32_t key_i = 0; key_i < key_count; key_i++) {
+ Vector3 scale;
+ Error err = p_animation->scale_track_get_key(p_track_i, key_i, &scale);
+ ERR_CONTINUE(err != OK);
+ p_track.scale_track.values.write[key_i] = scale;
+ }
}
} else if (track_type == Animation::TYPE_POSITION_3D) {
- p_track.position_track.times = times;
- p_track.position_track.values.resize(key_count);
- p_track.position_track.interpolation = gltf_interpolation;
- for (int32_t key_i = 0; key_i < key_count; key_i++) {
- Vector3 position;
- Error err = p_animation->position_track_get_key(p_track_i, key_i, &position);
- ERR_CONTINUE(err != OK);
- p_track.position_track.values.write[key_i] = position;
+ if (gltf_interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE) {
+ gltf_interpolation = GLTFAnimation::INTERP_LINEAR;
+ p_track.position_track.times.clear();
+ p_track.position_track.values.clear();
+ // CATMULLROMSPLINE or CUBIC_SPLINE have to be baked, apologies.
+ const double increment = 1.0 / BAKE_FPS;
+ double time = 0.0;
+ bool last = false;
+ while (true) {
+ Vector3 scale;
+ Error err = p_animation->position_track_interpolate(p_track_i, time, &scale);
+ ERR_CONTINUE(err != OK);
+ p_track.position_track.values.push_back(scale);
+ p_track.position_track.times.push_back(time);
+ if (last) {
+ break;
+ }
+ time += increment;
+ if (time >= anim_end) {
+ last = true;
+ time = anim_end;
+ }
+ }
+ } else {
+ p_track.position_track.times = times;
+ p_track.position_track.values.resize(key_count);
+ p_track.position_track.interpolation = gltf_interpolation;
+ for (int32_t key_i = 0; key_i < key_count; key_i++) {
+ Vector3 position;
+ Error err = p_animation->position_track_get_key(p_track_i, key_i, &position);
+ ERR_CONTINUE(err != OK);
+ p_track.position_track.values.write[key_i] = position;
+ }
}
} else if (track_type == Animation::TYPE_ROTATION_3D) {
- p_track.rotation_track.times = times;
- p_track.rotation_track.interpolation = gltf_interpolation;
- p_track.rotation_track.values.resize(key_count);
- for (int32_t key_i = 0; key_i < key_count; key_i++) {
- Quaternion rotation;
- Error err = p_animation->rotation_track_get_key(p_track_i, key_i, &rotation);
- ERR_CONTINUE(err != OK);
- p_track.rotation_track.values.write[key_i] = rotation;
+ if (gltf_interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE) {
+ gltf_interpolation = GLTFAnimation::INTERP_LINEAR;
+ p_track.rotation_track.times.clear();
+ p_track.rotation_track.values.clear();
+ // CATMULLROMSPLINE or CUBIC_SPLINE have to be baked, apologies.
+ const double increment = 1.0 / BAKE_FPS;
+ double time = 0.0;
+ bool last = false;
+ while (true) {
+ Quaternion rotation;
+ Error err = p_animation->rotation_track_interpolate(p_track_i, time, &rotation);
+ ERR_CONTINUE(err != OK);
+ p_track.rotation_track.values.push_back(rotation);
+ p_track.rotation_track.times.push_back(time);
+ if (last) {
+ break;
+ }
+ time += increment;
+ if (time >= anim_end) {
+ last = true;
+ time = anim_end;
+ }
+ }
+ } else {
+ p_track.rotation_track.times = times;
+ p_track.rotation_track.values.resize(key_count);
+ p_track.rotation_track.interpolation = gltf_interpolation;
+ for (int32_t key_i = 0; key_i < key_count; key_i++) {
+ Quaternion rotation;
+ Error err = p_animation->rotation_track_get_key(p_track_i, key_i, &rotation);
+ ERR_CONTINUE(err != OK);
+ p_track.rotation_track.values.write[key_i] = rotation;
+ }
}
} else if (track_type == Animation::TYPE_VALUE) {
if (path.contains(":position")) {
- p_track.position_track.times = times;
p_track.position_track.interpolation = gltf_interpolation;
-
+ p_track.position_track.times = times;
p_track.position_track.values.resize(key_count);
- p_track.position_track.interpolation = gltf_interpolation;
- for (int32_t key_i = 0; key_i < key_count; key_i++) {
- Vector3 position = p_animation->track_get_key_value(p_track_i, key_i);
- p_track.position_track.values.write[key_i] = position;
+ if (gltf_interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE) {
+ gltf_interpolation = GLTFAnimation::INTERP_LINEAR;
+ p_track.position_track.times.clear();
+ p_track.position_track.values.clear();
+ // CATMULLROMSPLINE or CUBIC_SPLINE have to be baked, apologies.
+ const double increment = 1.0 / BAKE_FPS;
+ double time = 0.0;
+ bool last = false;
+ while (true) {
+ Vector3 position;
+ Error err = p_animation->position_track_interpolate(p_track_i, time, &position);
+ ERR_CONTINUE(err != OK);
+ p_track.position_track.values.push_back(position);
+ p_track.position_track.times.push_back(time);
+ if (last) {
+ break;
+ }
+ time += increment;
+ if (time >= anim_end) {
+ last = true;
+ time = anim_end;
+ }
+ }
+ } else {
+ for (int32_t key_i = 0; key_i < key_count; key_i++) {
+ Vector3 position = p_animation->track_get_key_value(p_track_i, key_i);
+ p_track.position_track.values.write[key_i] = position;
+ }
}
} else if (path.contains(":rotation")) {
- p_track.rotation_track.times = times;
p_track.rotation_track.interpolation = gltf_interpolation;
-
+ p_track.rotation_track.times = times;
p_track.rotation_track.values.resize(key_count);
- p_track.rotation_track.interpolation = gltf_interpolation;
-
- for (int32_t key_i = 0; key_i < key_count; key_i++) {
- Vector3 rotation_radian = p_animation->track_get_key_value(p_track_i, key_i);
- p_track.rotation_track.values.write[key_i] = Quaternion::from_euler(rotation_radian);
+ if (gltf_interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE) {
+ gltf_interpolation = GLTFAnimation::INTERP_LINEAR;
+ p_track.rotation_track.times.clear();
+ p_track.rotation_track.values.clear();
+ // CATMULLROMSPLINE or CUBIC_SPLINE have to be baked, apologies.
+ const double increment = 1.0 / BAKE_FPS;
+ double time = 0.0;
+ bool last = false;
+ while (true) {
+ Quaternion rotation;
+ Error err = p_animation->rotation_track_interpolate(p_track_i, time, &rotation);
+ ERR_CONTINUE(err != OK);
+ p_track.rotation_track.values.push_back(rotation);
+ p_track.rotation_track.times.push_back(time);
+ if (last) {
+ break;
+ }
+ time += increment;
+ if (time >= anim_end) {
+ last = true;
+ time = anim_end;
+ }
+ }
+ } else {
+ for (int32_t key_i = 0; key_i < key_count; key_i++) {
+ Vector3 rotation_radian = p_animation->track_get_key_value(p_track_i, key_i);
+ p_track.rotation_track.values.write[key_i] = Quaternion::from_euler(rotation_radian);
+ }
}
} else if (path.contains(":scale")) {
p_track.scale_track.times = times;
@@ -6510,68 +6629,115 @@ GLTFAnimation::Track GLTFDocument::_convert_animation_track(Ref<GLTFState> p_sta
p_track.scale_track.values.resize(key_count);
p_track.scale_track.interpolation = gltf_interpolation;
- for (int32_t key_i = 0; key_i < key_count; key_i++) {
- Vector3 scale_track = p_animation->track_get_key_value(p_track_i, key_i);
- p_track.scale_track.values.write[key_i] = scale_track;
+ if (gltf_interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE) {
+ gltf_interpolation = GLTFAnimation::INTERP_LINEAR;
+ p_track.scale_track.times.clear();
+ p_track.scale_track.values.clear();
+ // CATMULLROMSPLINE or CUBIC_SPLINE have to be baked, apologies.
+ const double increment = 1.0 / BAKE_FPS;
+ double time = 0.0;
+ bool last = false;
+ while (true) {
+ Vector3 scale;
+ Error err = p_animation->scale_track_interpolate(p_track_i, time, &scale);
+ ERR_CONTINUE(err != OK);
+ p_track.scale_track.values.push_back(scale);
+ p_track.scale_track.times.push_back(time);
+ if (last) {
+ break;
+ }
+ time += increment;
+ if (time >= anim_end) {
+ last = true;
+ time = anim_end;
+ }
+ }
+ } else {
+ for (int32_t key_i = 0; key_i < key_count; key_i++) {
+ Vector3 scale_track = p_animation->track_get_key_value(p_track_i, key_i);
+ p_track.scale_track.values.write[key_i] = scale_track;
+ }
}
}
} else if (track_type == Animation::TYPE_BEZIER) {
- if (path.contains("/scale")) {
- const int32_t keys = p_animation->track_get_key_time(p_track_i, key_count - 1) * BAKE_FPS;
+ const int32_t keys = anim_end * BAKE_FPS;
+ if (path.contains(":scale")) {
if (!p_track.scale_track.times.size()) {
+ p_track.scale_track.interpolation = gltf_interpolation;
Vector<real_t> new_times;
new_times.resize(keys);
for (int32_t key_i = 0; key_i < keys; key_i++) {
new_times.write[key_i] = key_i / BAKE_FPS;
}
p_track.scale_track.times = new_times;
- p_track.scale_track.interpolation = gltf_interpolation;
p_track.scale_track.values.resize(keys);
for (int32_t key_i = 0; key_i < keys; key_i++) {
p_track.scale_track.values.write[key_i] = Vector3(1.0f, 1.0f, 1.0f);
}
- p_track.scale_track.interpolation = gltf_interpolation;
- }
- for (int32_t key_i = 0; key_i < keys; key_i++) {
- Vector3 bezier_track = p_track.scale_track.values[key_i];
- if (path.contains("/scale:x")) {
- bezier_track.x = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS);
- } else if (path.contains("/scale:y")) {
- bezier_track.y = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS);
- } else if (path.contains("/scale:z")) {
- bezier_track.z = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS);
+ for (int32_t key_i = 0; key_i < keys; key_i++) {
+ Vector3 bezier_track = p_track.scale_track.values[key_i];
+ if (path.contains(":scale:x")) {
+ bezier_track.x = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS);
+ } else if (path.contains(":scale:y")) {
+ bezier_track.y = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS);
+ } else if (path.contains(":scale:z")) {
+ bezier_track.z = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS);
+ }
+ p_track.scale_track.values.write[key_i] = bezier_track;
}
- p_track.scale_track.values.write[key_i] = bezier_track;
}
- } else if (path.contains("/position")) {
- const int32_t keys = p_animation->track_get_key_time(p_track_i, key_count - 1) * BAKE_FPS;
+ } else if (path.contains(":position")) {
if (!p_track.position_track.times.size()) {
+ p_track.position_track.interpolation = gltf_interpolation;
Vector<real_t> new_times;
new_times.resize(keys);
for (int32_t key_i = 0; key_i < keys; key_i++) {
new_times.write[key_i] = key_i / BAKE_FPS;
}
p_track.position_track.times = new_times;
- p_track.position_track.interpolation = gltf_interpolation;
p_track.position_track.values.resize(keys);
- p_track.position_track.interpolation = gltf_interpolation;
}
for (int32_t key_i = 0; key_i < keys; key_i++) {
Vector3 bezier_track = p_track.position_track.values[key_i];
- if (path.contains("/position:x")) {
+ if (path.contains(":position:x")) {
bezier_track.x = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS);
- } else if (path.contains("/position:y")) {
+ } else if (path.contains(":position:y")) {
bezier_track.y = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS);
- } else if (path.contains("/position:z")) {
+ } else if (path.contains(":position:z")) {
bezier_track.z = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS);
}
p_track.position_track.values.write[key_i] = bezier_track;
}
+ } else if (path.contains(":rotation")) {
+ if (!p_track.rotation_track.times.size()) {
+ p_track.rotation_track.interpolation = gltf_interpolation;
+ Vector<real_t> new_times;
+ new_times.resize(keys);
+ for (int32_t key_i = 0; key_i < keys; key_i++) {
+ new_times.write[key_i] = key_i / BAKE_FPS;
+ }
+ p_track.rotation_track.times = new_times;
+
+ p_track.rotation_track.values.resize(keys);
+ }
+ for (int32_t key_i = 0; key_i < keys; key_i++) {
+ Quaternion bezier_track = p_track.rotation_track.values[key_i];
+ if (path.contains(":rotation:x")) {
+ bezier_track.x = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS);
+ } else if (path.contains(":rotation:y")) {
+ bezier_track.y = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS);
+ } else if (path.contains(":rotation:z")) {
+ bezier_track.z = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS);
+ } else if (path.contains(":rotation:w")) {
+ bezier_track.w = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS);
+ }
+ p_track.rotation_track.values.write[key_i] = bezier_track;
+ }
}
}
return p_track;
@@ -6582,16 +6748,18 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> p_state, AnimationPlayer *p
Ref<GLTFAnimation> gltf_animation;
gltf_animation.instantiate();
gltf_animation->set_name(_gen_unique_name(p_state, p_animation_track_name));
-
for (int32_t track_i = 0; track_i < animation->get_track_count(); track_i++) {
if (!animation->track_is_enabled(track_i)) {
continue;
}
- String orig_track_path = animation->track_get_path(track_i);
- if (String(orig_track_path).contains(":position")) {
- const Vector<String> node_suffix = String(orig_track_path).split(":position");
+ String final_track_path = animation->track_get_path(track_i);
+ Node *animation_base_node = p_animation_player->get_parent();
+ ERR_CONTINUE_MSG(!animation_base_node, "Cannot get the parent of the animation player.");
+ if (String(final_track_path).contains(":position")) {
+ const Vector<String> node_suffix = String(final_track_path).split(":position");
const NodePath path = node_suffix[0];
- const Node *node = p_animation_player->get_parent()->get_node_or_null(path);
+ const Node *node = animation_base_node->get_node_or_null(path);
+ ERR_CONTINUE_MSG(!node, "Cannot get the node from a position path.");
for (const KeyValue<GLTFNodeIndex, Node *> &position_scene_node_i : p_state->scene_nodes) {
if (position_scene_node_i.value == node) {
GLTFNodeIndex node_index = position_scene_node_i.key;
@@ -6604,10 +6772,11 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> p_state, AnimationPlayer *p
gltf_animation->get_tracks().insert(node_index, track);
}
}
- } else if (String(orig_track_path).contains(":rotation_degrees")) {
- const Vector<String> node_suffix = String(orig_track_path).split(":rotation_degrees");
+ } else if (String(final_track_path).contains(":rotation_degrees")) {
+ const Vector<String> node_suffix = String(final_track_path).split(":rotation_degrees");
const NodePath path = node_suffix[0];
- const Node *node = p_animation_player->get_parent()->get_node_or_null(path);
+ const Node *node = animation_base_node->get_node_or_null(path);
+ ERR_CONTINUE_MSG(!node, "Cannot get the node from a rotation degrees path.");
for (const KeyValue<GLTFNodeIndex, Node *> &rotation_degree_scene_node_i : p_state->scene_nodes) {
if (rotation_degree_scene_node_i.value == node) {
GLTFNodeIndex node_index = rotation_degree_scene_node_i.key;
@@ -6620,10 +6789,11 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> p_state, AnimationPlayer *p
gltf_animation->get_tracks().insert(node_index, track);
}
}
- } else if (String(orig_track_path).contains(":scale")) {
- const Vector<String> node_suffix = String(orig_track_path).split(":scale");
+ } else if (String(final_track_path).contains(":scale")) {
+ const Vector<String> node_suffix = String(final_track_path).split(":scale");
const NodePath path = node_suffix[0];
- const Node *node = p_animation_player->get_parent()->get_node_or_null(path);
+ const Node *node = animation_base_node->get_node_or_null(path);
+ ERR_CONTINUE_MSG(!node, "Cannot get the node from a scale path.");
for (const KeyValue<GLTFNodeIndex, Node *> &scale_scene_node_i : p_state->scene_nodes) {
if (scale_scene_node_i.value == node) {
GLTFNodeIndex node_index = scale_scene_node_i.key;
@@ -6636,10 +6806,11 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> p_state, AnimationPlayer *p
gltf_animation->get_tracks().insert(node_index, track);
}
}
- } else if (String(orig_track_path).contains(":transform")) {
- const Vector<String> node_suffix = String(orig_track_path).split(":transform");
+ } else if (String(final_track_path).contains(":transform")) {
+ const Vector<String> node_suffix = String(final_track_path).split(":transform");
const NodePath path = node_suffix[0];
- const Node *node = p_animation_player->get_parent()->get_node_or_null(path);
+ const Node *node = animation_base_node->get_node_or_null(path);
+ ERR_CONTINUE_MSG(!node, "Cannot get the node from a transform path.");
for (const KeyValue<GLTFNodeIndex, Node *> &transform_track_i : p_state->scene_nodes) {
if (transform_track_i.value == node) {
GLTFAnimation::Track track;
@@ -6647,12 +6818,16 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> p_state, AnimationPlayer *p
gltf_animation->get_tracks().insert(transform_track_i.key, track);
}
}
- } else if (String(orig_track_path).contains(":") && animation->track_get_type(track_i) == Animation::TYPE_BLEND_SHAPE) {
- const Vector<String> node_suffix = String(orig_track_path).split(":");
+ } else if (String(final_track_path).contains(":") && animation->track_get_type(track_i) == Animation::TYPE_BLEND_SHAPE) {
+ const Vector<String> node_suffix = String(final_track_path).split(":");
const NodePath path = node_suffix[0];
const String suffix = node_suffix[1];
- Node *node = p_animation_player->get_parent()->get_node_or_null(path);
+ Node *node = animation_base_node->get_node_or_null(path);
+ ERR_CONTINUE_MSG(!node, "Cannot get the node from a blend shape path.");
MeshInstance3D *mi = cast_to<MeshInstance3D>(node);
+ if (!mi) {
+ continue;
+ }
Ref<Mesh> mesh = mi->get_mesh();
ERR_CONTINUE(mesh.is_null());
int32_t mesh_index = -1;
@@ -6703,14 +6878,20 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> p_state, AnimationPlayer *p
}
tracks[mesh_index] = track;
}
- } else if (String(orig_track_path).contains(":")) {
+ } else if (String(final_track_path).contains(":")) {
//Process skeleton
- const Vector<String> node_suffix = String(orig_track_path).split(":");
+ const Vector<String> node_suffix = String(final_track_path).split(":");
const String node = node_suffix[0];
const NodePath node_path = node;
const String suffix = node_suffix[1];
- Node *godot_node = p_animation_player->get_parent()->get_node_or_null(node_path);
- Skeleton3D *skeleton = nullptr;
+ Node *godot_node = animation_base_node->get_node_or_null(node_path);
+ if (!godot_node) {
+ continue;
+ }
+ Skeleton3D *skeleton = cast_to<Skeleton3D>(animation_base_node->get_node_or_null(node));
+ if (!skeleton) {
+ continue;
+ }
GLTFSkeletonIndex skeleton_gltf_i = -1;
for (GLTFSkeletonIndex skeleton_i = 0; skeleton_i < p_state->skeletons.size(); skeleton_i++) {
if (p_state->skeletons[skeleton_i]->godot_skeleton == cast_to<Skeleton3D>(godot_node)) {
@@ -6719,7 +6900,7 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> p_state, AnimationPlayer *p
ERR_CONTINUE(!skeleton);
Ref<GLTFSkeleton> skeleton_gltf = p_state->skeletons[skeleton_gltf_i];
int32_t bone = skeleton->find_bone(suffix);
- ERR_CONTINUE(bone == -1);
+ ERR_CONTINUE_MSG(bone == -1, vformat("Cannot find the bone %s.", suffix));
if (!skeleton_gltf->godot_bone_node.has(bone)) {
continue;
}
@@ -6733,9 +6914,10 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> p_state, AnimationPlayer *p
gltf_animation->get_tracks()[node_i] = track;
}
}
- } else if (!String(orig_track_path).contains(":")) {
- ERR_CONTINUE(!p_animation_player->get_parent());
- Node *godot_node = p_animation_player->get_parent()->get_node_or_null(orig_track_path);
+ } else if (!String(final_track_path).contains(":")) {
+ ERR_CONTINUE(!animation_base_node);
+ Node *godot_node = animation_base_node->get_node_or_null(final_track_path);
+ ERR_CONTINUE_MSG(!godot_node, vformat("Cannot get the node from a skeleton path %s.", final_track_path));
for (const KeyValue<GLTFNodeIndex, Node *> &scene_node_i : p_state->scene_nodes) {
if (scene_node_i.value == godot_node) {
GLTFNodeIndex node_i = scene_node_i.key;
diff --git a/modules/gltf/gltf_document.h b/modules/gltf/gltf_document.h
index b8b989bf89..ae19f67390 100644
--- a/modules/gltf/gltf_document.h
+++ b/modules/gltf/gltf_document.h
@@ -364,8 +364,7 @@ public:
Ref<GLTFNode> p_gltf_node);
GLTFMeshIndex _convert_mesh_to_gltf(Ref<GLTFState> p_state,
MeshInstance3D *p_mesh_instance);
- void _convert_animation(Ref<GLTFState> p_state, AnimationPlayer *p_animation_player,
- String p_animation_track_name);
+ void _convert_animation(Ref<GLTFState> p_state, AnimationPlayer *p_animation_player, String p_animation_track_name);
Error _serialize(Ref<GLTFState> p_state, const String &p_path);
Error _parse(Ref<GLTFState> p_state, String p_path, Ref<FileAccess> p_file);
};
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildInfo.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildInfo.cs
index a4d5e1a569..7c02f29606 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildInfo.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildInfo.cs
@@ -12,6 +12,7 @@ namespace GodotTools.Build
public sealed partial class BuildInfo : RefCounted // TODO Remove RefCounted once we have proper serialization
{
public string Solution { get; private set; }
+ public string Project { get; private set; }
public string Configuration { get; private set; }
public string? RuntimeIdentifier { get; private set; }
public string? PublishOutputDir { get; private set; }
@@ -22,13 +23,13 @@ namespace GodotTools.Build
// TODO Use List once we have proper serialization
public Godot.Collections.Array CustomProperties { get; private set; } = new();
- public string LogsDirPath =>
- Path.Combine(GodotSharpDirs.BuildLogsDirs, $"{Solution.Md5Text()}_{Configuration}");
+ public string LogsDirPath => GodotSharpDirs.LogsDirPathFor(Solution, Configuration);
public override bool Equals(object? obj)
{
return obj is BuildInfo other &&
other.Solution == Solution &&
+ other.Project == Project &&
other.Configuration == Configuration && other.RuntimeIdentifier == RuntimeIdentifier &&
other.PublishOutputDir == PublishOutputDir && other.Restore == Restore &&
other.Rebuild == Rebuild && other.OnlyClean == OnlyClean &&
@@ -42,6 +43,7 @@ namespace GodotTools.Build
{
int hash = 17;
hash = (hash * 29) + Solution.GetHashCode();
+ hash = (hash * 29) + Project.GetHashCode();
hash = (hash * 29) + Configuration.GetHashCode();
hash = (hash * 29) + (RuntimeIdentifier?.GetHashCode() ?? 0);
hash = (hash * 29) + (PublishOutputDir?.GetHashCode() ?? 0);
@@ -58,22 +60,25 @@ namespace GodotTools.Build
private BuildInfo()
{
Solution = string.Empty;
+ Project = string.Empty;
Configuration = string.Empty;
}
- public BuildInfo(string solution, string configuration, bool restore, bool rebuild, bool onlyClean)
+ public BuildInfo(string solution, string project, string configuration, bool restore, bool rebuild, bool onlyClean)
{
Solution = solution;
+ Project = project;
Configuration = configuration;
Restore = restore;
Rebuild = rebuild;
OnlyClean = onlyClean;
}
- public BuildInfo(string solution, string configuration, string runtimeIdentifier,
+ public BuildInfo(string solution, string project, string configuration, string runtimeIdentifier,
string publishOutputDir, bool restore, bool rebuild, bool onlyClean)
{
Solution = solution;
+ Project = project;
Configuration = configuration;
RuntimeIdentifier = runtimeIdentifier;
PublishOutputDir = publishOutputDir;
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs
index 349f9d0cb8..ed3a4c6e26 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs
@@ -262,7 +262,7 @@ namespace GodotTools.Build
bool onlyClean = false
)
{
- var buildInfo = new BuildInfo(GodotSharpDirs.ProjectSlnPath, configuration,
+ var buildInfo = new BuildInfo(GodotSharpDirs.ProjectSlnPath, GodotSharpDirs.ProjectCsProjPath, configuration,
restore: true, rebuild, onlyClean);
// If a platform was not specified, try determining the current one. If that fails, let MSBuild auto-detect it.
@@ -282,7 +282,7 @@ namespace GodotTools.Build
[DisallowNull] string publishOutputDir
)
{
- var buildInfo = new BuildInfo(GodotSharpDirs.ProjectSlnPath, configuration,
+ var buildInfo = new BuildInfo(GodotSharpDirs.ProjectSlnPath, GodotSharpDirs.ProjectCsProjPath, configuration,
runtimeIdentifier, publishOutputDir, restore: true, rebuild: false, onlyClean: false);
buildInfo.CustomProperties.Add($"GodotTargetPlatform={platform}");
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs
index ae0ffaf4cb..d550c36b82 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs
@@ -7,6 +7,7 @@ using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
+using Godot;
using GodotTools.BuildLogger;
using GodotTools.Utils;
@@ -22,9 +23,11 @@ namespace GodotTools.Build
if (dotnetPath == null)
throw new FileNotFoundException("Cannot find the dotnet executable.");
+ var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings();
+
var startInfo = new ProcessStartInfo(dotnetPath);
- BuildArguments(buildInfo, startInfo.ArgumentList);
+ BuildArguments(buildInfo, startInfo.ArgumentList, editorSettings);
string launchMessage = startInfo.GetCommandLineDisplay(new StringBuilder("Running: ")).ToString();
stdOutHandler?.Invoke(launchMessage);
@@ -35,6 +38,8 @@ namespace GodotTools.Build
startInfo.RedirectStandardError = true;
startInfo.UseShellExecute = false;
startInfo.CreateNoWindow = true;
+ startInfo.EnvironmentVariables["DOTNET_CLI_UI_LANGUAGE"]
+ = ((string)editorSettings.GetSetting("interface/editor/editor_language")).Replace('_', '-');
// Needed when running from Developer Command Prompt for VS
RemovePlatformVariable(startInfo.EnvironmentVariables);
@@ -83,9 +88,11 @@ namespace GodotTools.Build
if (dotnetPath == null)
throw new FileNotFoundException("Cannot find the dotnet executable.");
+ var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings();
+
var startInfo = new ProcessStartInfo(dotnetPath);
- BuildPublishArguments(buildInfo, startInfo.ArgumentList);
+ BuildPublishArguments(buildInfo, startInfo.ArgumentList, editorSettings);
string launchMessage = startInfo.GetCommandLineDisplay(new StringBuilder("Running: ")).ToString();
stdOutHandler?.Invoke(launchMessage);
@@ -95,6 +102,8 @@ namespace GodotTools.Build
startInfo.RedirectStandardOutput = true;
startInfo.RedirectStandardError = true;
startInfo.UseShellExecute = false;
+ startInfo.EnvironmentVariables["DOTNET_CLI_UI_LANGUAGE"]
+ = ((string)editorSettings.GetSetting("interface/editor/editor_language")).Replace('_', '-');
// Needed when running from Developer Command Prompt for VS
RemovePlatformVariable(startInfo.EnvironmentVariables);
@@ -124,13 +133,14 @@ namespace GodotTools.Build
}
}
- private static void BuildArguments(BuildInfo buildInfo, Collection<string> arguments)
+ private static void BuildArguments(BuildInfo buildInfo, Collection<string> arguments,
+ EditorSettings editorSettings)
{
// `dotnet clean` / `dotnet build` commands
arguments.Add(buildInfo.OnlyClean ? "clean" : "build");
- // Solution
- arguments.Add(buildInfo.Solution);
+ // C# Project
+ arguments.Add(buildInfo.Project);
// `dotnet clean` doesn't recognize these options
if (!buildInfo.OnlyClean)
@@ -150,12 +160,14 @@ namespace GodotTools.Build
arguments.Add(buildInfo.Configuration);
// Verbosity
- arguments.Add("-v");
- arguments.Add("normal");
+ AddVerbosityArguments(buildInfo, arguments, editorSettings);
// Logger
AddLoggerArgument(buildInfo, arguments);
+ // Binary log
+ AddBinaryLogArgument(buildInfo, arguments, editorSettings);
+
// Custom properties
foreach (var customProperty in buildInfo.CustomProperties)
{
@@ -163,12 +175,13 @@ namespace GodotTools.Build
}
}
- private static void BuildPublishArguments(BuildInfo buildInfo, Collection<string> arguments)
+ private static void BuildPublishArguments(BuildInfo buildInfo, Collection<string> arguments,
+ EditorSettings editorSettings)
{
arguments.Add("publish"); // `dotnet publish` command
- // Solution
- arguments.Add(buildInfo.Solution);
+ // C# Project
+ arguments.Add(buildInfo.Project);
// Restore
// `dotnet publish` restores by default, unless requested not to
@@ -193,12 +206,14 @@ namespace GodotTools.Build
arguments.Add("true");
// Verbosity
- arguments.Add("-v");
- arguments.Add("normal");
+ AddVerbosityArguments(buildInfo, arguments, editorSettings);
// Logger
AddLoggerArgument(buildInfo, arguments);
+ // Binary log
+ AddBinaryLogArgument(buildInfo, arguments, editorSettings);
+
// Custom properties
foreach (var customProperty in buildInfo.CustomProperties)
{
@@ -213,6 +228,25 @@ namespace GodotTools.Build
}
}
+ private static void AddVerbosityArguments(BuildInfo buildInfo, Collection<string> arguments,
+ EditorSettings editorSettings)
+ {
+ var verbosityLevel =
+ editorSettings.GetSetting(GodotSharpEditor.Settings.VerbosityLevel).As<VerbosityLevelId>();
+ arguments.Add("-v");
+ arguments.Add(verbosityLevel switch
+ {
+ VerbosityLevelId.Quiet => "quiet",
+ VerbosityLevelId.Minimal => "minimal",
+ VerbosityLevelId.Detailed => "detailed",
+ VerbosityLevelId.Diagnostic => "diagnostic",
+ _ => "normal",
+ });
+
+ if ((bool)editorSettings.GetSetting(GodotSharpEditor.Settings.NoConsoleLogging))
+ arguments.Add("-noconlog");
+ }
+
private static void AddLoggerArgument(BuildInfo buildInfo, Collection<string> arguments)
{
string buildLoggerPath = Path.Combine(Internals.GodotSharpDirs.DataEditorToolsDir,
@@ -222,6 +256,16 @@ namespace GodotTools.Build
$"-l:{typeof(GodotBuildLogger).FullName},{buildLoggerPath};{buildInfo.LogsDirPath}");
}
+ private static void AddBinaryLogArgument(BuildInfo buildInfo, Collection<string> arguments,
+ EditorSettings editorSettings)
+ {
+ if (!(bool)editorSettings.GetSetting(GodotSharpEditor.Settings.CreateBinaryLog))
+ return;
+
+ arguments.Add($"-bl:{Path.Combine(buildInfo.LogsDirPath, "msbuild.binlog")}");
+ arguments.Add("-ds:False"); // Honestly never understood why -bl also switches -ds on.
+ }
+
private static void RemovePlatformVariable(StringDictionary environmentVariables)
{
// EnvironmentVariables is case sensitive? Seriously?
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs b/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs
index 262de024ca..cf1b84e37f 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs
@@ -1,4 +1,5 @@
using System;
+using System.IO;
using Godot;
using GodotTools.Internals;
using static GodotTools.Internals.Globals;
@@ -14,6 +15,7 @@ namespace GodotTools.Build
private Button _errorsBtn;
private Button _warningsBtn;
private Button _viewLogBtn;
+ private Button _openLogsFolderBtn;
private void WarningsToggled(bool pressed)
{
@@ -93,6 +95,10 @@ namespace GodotTools.Build
private void ViewLogToggled(bool pressed) => BuildOutputView.LogVisible = pressed;
+ private void OpenLogsFolderPressed() => OS.ShellOpen(
+ $"file://{GodotSharpDirs.LogsDirPathFor("Debug")}"
+ );
+
private void BuildMenuOptionPressed(long id)
{
switch ((BuildMenuOptions)id)
@@ -171,6 +177,22 @@ namespace GodotTools.Build
_viewLogBtn.Toggled += ViewLogToggled;
toolBarHBox.AddChild(_viewLogBtn);
+ // Horizontal spacer, push everything to the right.
+ toolBarHBox.AddChild(new Control
+ {
+ SizeFlagsHorizontal = SizeFlags.ExpandFill,
+ });
+
+ _openLogsFolderBtn = new Button
+ {
+ Text = "Show Logs in File Manager".TTR(),
+ Icon = GetThemeIcon("Filesystem", "EditorIcons"),
+ ExpandIcon = false,
+ FocusMode = FocusModeEnum.None,
+ };
+ _openLogsFolderBtn.Pressed += OpenLogsFolderPressed;
+ toolBarHBox.AddChild(_openLogsFolderBtn);
+
BuildOutputView = new BuildOutputView();
AddChild(BuildOutputView);
}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
index 70b48b0e3a..a284451a35 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
@@ -24,19 +24,7 @@ namespace GodotTools.Export
public void RegisterExportSettings()
{
// TODO: These would be better as export preset options, but that doesn't seem to be supported yet
-
- GlobalDef("mono/export/include_scripts_content", false);
-
- GlobalDef("mono/export/aot/enabled", false);
- GlobalDef("mono/export/aot/full_aot", false);
- GlobalDef("mono/export/aot/use_interpreter", true);
-
- // --aot or --aot=opt1,opt2 (use 'mono --aot=help AuxAssembly.dll' to list AOT options)
- GlobalDef("mono/export/aot/extra_aot_options", Array.Empty<string>());
- // --optimize/-O=opt1,opt2 (use 'mono --list-opt'' to list optimize options)
- GlobalDef("mono/export/aot/extra_optimizer_options", Array.Empty<string>());
-
- GlobalDef("mono/export/aot/android_toolchain_path", "");
+ GlobalDef("dotnet/export/include_scripts_content", false);
}
private string _maybeLastExportError;
@@ -56,7 +44,7 @@ namespace GodotTools.Export
// TODO What if the source file is not part of the game's C# project
- bool includeScriptsContent = (bool)ProjectSettings.GetSetting("mono/export/include_scripts_content");
+ bool includeScriptsContent = (bool)ProjectSettings.GetSetting("dotnet/export/include_scripts_content");
if (!includeScriptsContent)
{
diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
index 5abbe8752c..43ead4af69 100644
--- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
@@ -22,6 +22,14 @@ namespace GodotTools
{
public partial class GodotSharpEditor : EditorPlugin, ISerializationListener
{
+ public static class Settings
+ {
+ public const string ExternalEditor = "dotnet/editor/external_editor";
+ public const string VerbosityLevel = "dotnet/build/verbosity_level";
+ public const string NoConsoleLogging = "dotnet/build/no_console_logging";
+ public const string CreateBinaryLog = "dotnet/build/create_binary_log";
+ }
+
private EditorSettings _editorSettings;
private PopupMenu _menuPopup;
@@ -171,7 +179,7 @@ namespace GodotTools
[UsedImplicitly]
public Error OpenInExternalEditor(Script script, int line, int col)
{
- var editorId = (ExternalEditorId)(int)_editorSettings.GetSetting("mono/editor/external_editor");
+ var editorId = _editorSettings.GetSetting(Settings.ExternalEditor).As<ExternalEditorId>();
switch (editorId)
{
@@ -323,8 +331,7 @@ namespace GodotTools
[UsedImplicitly]
public bool OverridesExternalEditor()
{
- return (ExternalEditorId)(int)_editorSettings.GetSetting("mono/editor/external_editor") !=
- ExternalEditorId.None;
+ return _editorSettings.GetSetting(Settings.ExternalEditor).As<ExternalEditorId>() != ExternalEditorId.None;
}
public override bool _Build()
@@ -453,7 +460,10 @@ namespace GodotTools
_menuPopup.IdPressed += _MenuOptionPressed;
// External editor settings
- EditorDef("mono/editor/external_editor", Variant.From(ExternalEditorId.None));
+ EditorDef(Settings.ExternalEditor, Variant.From(ExternalEditorId.None));
+ EditorDef(Settings.VerbosityLevel, Variant.From(VerbosityLevelId.Normal));
+ EditorDef(Settings.NoConsoleLogging, false);
+ EditorDef(Settings.CreateBinaryLog, false);
string settingsHintStr = "Disabled";
@@ -481,11 +491,23 @@ namespace GodotTools
_editorSettings.AddPropertyInfo(new Godot.Collections.Dictionary
{
["type"] = (int)Variant.Type.Int,
- ["name"] = "mono/editor/external_editor",
+ ["name"] = Settings.ExternalEditor,
["hint"] = (int)PropertyHint.Enum,
["hint_string"] = settingsHintStr
});
+ var verbosityLevels = Enum.GetValues<VerbosityLevelId>().Select(level => $"{Enum.GetName(level)}:{(int)level}");
+ _editorSettings.AddPropertyInfo(new Godot.Collections.Dictionary
+ {
+ ["type"] = (int)Variant.Type.Int,
+ ["name"] = Settings.VerbosityLevel,
+ ["hint"] = (int)PropertyHint.Enum,
+ ["hint_string"] = string.Join(",", verbosityLevels),
+ });
+
+ OnSettingsChanged();
+ _editorSettings.SettingsChanged += OnSettingsChanged;
+
// Export plugin
var exportPlugin = new ExportPlugin();
AddExportPlugin(exportPlugin);
@@ -510,6 +532,24 @@ namespace GodotTools
AddChild(GodotIdeManager);
}
+ public override void _DisablePlugin()
+ {
+ base._DisablePlugin();
+
+ _editorSettings.SettingsChanged -= OnSettingsChanged;
+ }
+
+ private void OnSettingsChanged()
+ {
+ // We want to force NoConsoleLogging to true when the VerbosityLevel is at Detailed or above.
+ // At that point, there's so much info logged that it doesn't make sense to display it in
+ // the tiny editor window, and it'd make the editor hang or crash anyway.
+ var verbosityLevel = _editorSettings.GetSetting(Settings.VerbosityLevel).As<VerbosityLevelId>();
+ var hideConsoleLog = (bool)_editorSettings.GetSetting(Settings.NoConsoleLogging);
+ if (verbosityLevel >= VerbosityLevelId.Detailed && !hideConsoleLog)
+ _editorSettings.SetSetting(Settings.NoConsoleLogging, Variant.From(true));
+ }
+
protected override void Dispose(bool disposing)
{
if (disposing)
diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs
index 5de2c9833b..83621ce5af 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs
@@ -21,7 +21,8 @@ namespace GodotTools.Ides
return _messagingServer;
_messagingServer?.Dispose();
- _messagingServer = new MessagingServer(OS.GetExecutablePath(), ProjectSettings.GlobalizePath(GodotSharpDirs.ResMetadataDir), new GodotLogger());
+ _messagingServer = new MessagingServer(OS.GetExecutablePath(),
+ ProjectSettings.GlobalizePath(GodotSharpDirs.ResMetadataDir), new GodotLogger());
_ = _messagingServer.Listen();
@@ -76,8 +77,8 @@ namespace GodotTools.Ides
public async Task<EditorPick?> LaunchIdeAsync(int millisecondsTimeout = 10000)
{
- var editorId = (ExternalEditorId)(int)GodotSharpEditor.Instance.GetEditorInterface()
- .GetEditorSettings().GetSetting("mono/editor/external_editor");
+ var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings();
+ var editorId = editorSettings.GetSetting(GodotSharpEditor.Settings.ExternalEditor).As<ExternalEditorId>();
string editorIdentity = GetExternalEditorIdentity(editorId);
var runningServer = GetRunningOrNewServer();
diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs
index 60602a5847..f55ca4c7d7 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs
@@ -9,7 +9,7 @@ namespace GodotTools.Ides.Rider
{
public static class RiderPathManager
{
- public static readonly string EditorPathSettingName = "mono/editor/editor_path_optional";
+ public static readonly string EditorPathSettingName = "dotnet/editor/editor_path_optional";
private static string GetRiderPathFromSettings()
{
@@ -22,7 +22,7 @@ namespace GodotTools.Ides.Rider
public static void Initialize()
{
var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings();
- var editor = (ExternalEditorId)(int)editorSettings.GetSetting("mono/editor/external_editor");
+ var editor = editorSettings.GetSetting(GodotSharpEditor.Settings.ExternalEditor).As<ExternalEditorId>();
if (editor == ExternalEditorId.Rider)
{
if (!editorSettings.HasSetting(EditorPathSettingName))
diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs
index 7624989092..fb68fcbae6 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs
@@ -115,5 +115,11 @@ namespace GodotTools.Internals
return _projectCsProjPath;
}
}
+
+ public static string LogsDirPathFor(string solution, string configuration)
+ => Path.Combine(BuildLogsDirs, $"{solution.Md5Text()}_{configuration}");
+
+ public static string LogsDirPathFor(string configuration)
+ => LogsDirPathFor(ProjectSlnPath, configuration);
}
}
diff --git a/modules/mono/editor/GodotTools/GodotTools/VerbosityLevelId.cs b/modules/mono/editor/GodotTools/GodotTools/VerbosityLevelId.cs
new file mode 100644
index 0000000000..0e1afe6bbf
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/VerbosityLevelId.cs
@@ -0,0 +1,11 @@
+namespace GodotTools
+{
+ public enum VerbosityLevelId : long
+ {
+ Quiet,
+ Minimal,
+ Normal,
+ Detailed,
+ Diagnostic,
+ }
+}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotSynchronizationContext.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotSynchronizationContext.cs
index 027eab30fc..79030c79cc 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotSynchronizationContext.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotSynchronizationContext.cs
@@ -1,17 +1,44 @@
using System;
using System.Collections.Concurrent;
-using System.Collections.Generic;
using System.Threading;
+using System.Threading.Tasks;
namespace Godot
{
public sealed class GodotSynchronizationContext : SynchronizationContext, IDisposable
{
- private readonly BlockingCollection<KeyValuePair<SendOrPostCallback, object>> _queue = new();
+ private readonly BlockingCollection<(SendOrPostCallback Callback, object State)> _queue = new();
+
+ public override void Send(SendOrPostCallback d, object state)
+ {
+ // Shortcut if we're already on this context
+ // Also necessary to avoid a deadlock, since Send is blocking
+ if (Current == this)
+ {
+ d(state);
+ return;
+ }
+
+ var source = new TaskCompletionSource();
+
+ _queue.Add((st =>
+ {
+ try
+ {
+ d(st);
+ }
+ finally
+ {
+ source.SetResult();
+ }
+ }, state));
+
+ source.Task.Wait();
+ }
public override void Post(SendOrPostCallback d, object state)
{
- _queue.Add(new KeyValuePair<SendOrPostCallback, object>(d, state));
+ _queue.Add((d, state));
}
/// <summary>
@@ -21,7 +48,7 @@ namespace Godot
{
while (_queue.TryTake(out var workItem))
{
- workItem.Key(workItem.Value);
+ workItem.Callback(workItem.State);
}
}
diff --git a/modules/multiplayer/editor/editor_network_profiler.cpp b/modules/multiplayer/editor/editor_network_profiler.cpp
index e320657ab5..f8e75d5ef5 100644
--- a/modules/multiplayer/editor/editor_network_profiler.cpp
+++ b/modules/multiplayer/editor/editor_network_profiler.cpp
@@ -253,7 +253,8 @@ EditorNetworkProfiler::EditorNetworkProfiler() {
hb->add_spacer();
Label *lb = memnew(Label);
- lb->set_text(TTR("Down"));
+ // TRANSLATORS: This is the label for the network profiler's incoming bandwidth.
+ lb->set_text(TTR("Down", "Network"));
hb->add_child(lb);
incoming_bandwidth_text = memnew(LineEdit);
@@ -267,7 +268,8 @@ EditorNetworkProfiler::EditorNetworkProfiler() {
hb->add_child(down_up_spacer);
lb = memnew(Label);
- lb->set_text(TTR("Up"));
+ // TRANSLATORS: This is the label for the network profiler's outgoing bandwidth.
+ lb->set_text(TTR("Up", "Network"));
hb->add_child(lb);
outgoing_bandwidth_text = memnew(LineEdit);
diff --git a/modules/multiplayer/multiplayer_spawner.cpp b/modules/multiplayer/multiplayer_spawner.cpp
index 0aa54b69f9..4b1b6b541d 100644
--- a/modules/multiplayer/multiplayer_spawner.cpp
+++ b/modules/multiplayer/multiplayer_spawner.cpp
@@ -103,6 +103,15 @@ void MultiplayerSpawner::add_spawnable_scene(const String &p_path) {
ERR_FAIL_COND(!FileAccess::exists(p_path));
}
spawnable_scenes.push_back(sc);
+#ifdef TOOLS_ENABLED
+ if (Engine::get_singleton()->is_editor_hint()) {
+ return;
+ }
+#endif
+ Node *node = get_spawn_node();
+ if (spawnable_scenes.size() == 1 && node && !node->is_connected("child_entered_tree", callable_mp(this, &MultiplayerSpawner::_node_added))) {
+ node->connect("child_entered_tree", callable_mp(this, &MultiplayerSpawner::_node_added));
+ }
}
int MultiplayerSpawner::get_spawnable_scene_count() const {
@@ -116,6 +125,15 @@ String MultiplayerSpawner::get_spawnable_scene(int p_idx) const {
void MultiplayerSpawner::clear_spawnable_scenes() {
spawnable_scenes.clear();
+#ifdef TOOLS_ENABLED
+ if (Engine::get_singleton()->is_editor_hint()) {
+ return;
+ }
+#endif
+ Node *node = get_spawn_node();
+ if (node && node->is_connected("child_entered_tree", callable_mp(this, &MultiplayerSpawner::_node_added))) {
+ node->disconnect("child_entered_tree", callable_mp(this, &MultiplayerSpawner::_node_added));
+ }
}
Vector<String> MultiplayerSpawner::_get_spawnable_scenes() const {
diff --git a/modules/webp/SCsub b/modules/webp/SCsub
index 72ad1ea5e4..e78236a60b 100644
--- a/modules/webp/SCsub
+++ b/modules/webp/SCsub
@@ -13,6 +13,7 @@ if env["builtin_libwebp"]:
thirdparty_dir = "#thirdparty/libwebp/"
thirdparty_sources = [
"sharpyuv/sharpyuv.c",
+ "sharpyuv/sharpyuv_cpu.c",
"sharpyuv/sharpyuv_csp.c",
"sharpyuv/sharpyuv_dsp.c",
"sharpyuv/sharpyuv_gamma.c",
diff --git a/modules/webxr/native/library_godot_webxr.js b/modules/webxr/native/library_godot_webxr.js
index 1c00ebebb4..5c01d88a30 100644
--- a/modules/webxr/native/library_godot_webxr.js
+++ b/modules/webxr/native/library_godot_webxr.js
@@ -584,12 +584,11 @@ const GodotWebXR = {
}
const buf = GodotRuntime.malloc(point_count * 3 * 4);
- GodotRuntime.setHeapValue(buf, point_count, 'i32');
for (let i = 0; i < point_count; i++) {
const point = GodotWebXR.space.boundsGeometry[i];
- GodotRuntime.setHeapValue(buf + ((i * 3) + 1) * 4, point.x, 'float');
- GodotRuntime.setHeapValue(buf + ((i * 3) + 2) * 4, point.y, 'float');
- GodotRuntime.setHeapValue(buf + ((i * 3) + 3) * 4, point.z, 'float');
+ GodotRuntime.setHeapValue(buf + ((i * 3) + 0) * 4, point.x, 'float');
+ GodotRuntime.setHeapValue(buf + ((i * 3) + 1) * 4, point.y, 'float');
+ GodotRuntime.setHeapValue(buf + ((i * 3) + 2) * 4, point.z, 'float');
}
GodotRuntime.setHeapValue(r_points, buf, 'i32');