diff options
Diffstat (limited to 'modules')
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'); |