diff options
Diffstat (limited to 'modules')
65 files changed, 1600 insertions, 584 deletions
diff --git a/modules/csg/csg_shape.cpp b/modules/csg/csg_shape.cpp index 9cc3d0413d..13c7a8202c 100644 --- a/modules/csg/csg_shape.cpp +++ b/modules/csg/csg_shape.cpp @@ -558,7 +558,7 @@ void CSGShape3D::_notification(int p_what) { set_collision_layer(collision_layer); set_collision_mask(collision_mask); set_collision_priority(collision_priority); - _update_collision_faces(); + _make_dirty(); } } break; @@ -1763,7 +1763,7 @@ CSGBrush *CSGPolygon3D::_build_brush() { } } - if (!path) { + if (!path || !path->is_inside_tree()) { return new_brush; } diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml index 7f9d4ae253..32acad76aa 100644 --- a/modules/gdscript/doc_classes/@GDScript.xml +++ b/modules/gdscript/doc_classes/@GDScript.xml @@ -227,18 +227,6 @@ [/codeblock] </description> </method> - <method name="str" qualifiers="vararg"> - <return type="String" /> - <description> - Converts one or more arguments to a [String] in the best way possible. - [codeblock] - var a = [10, 20, 30] - var b = str(a); - len(a) # Returns 3 - len(b) # Returns 12 - [/codeblock] - </description> - </method> <method name="type_exists"> <return type="bool" /> <param index="0" name="type" type="StringName" /> @@ -563,6 +551,7 @@ [/codeblock] [b]Note:[/b] Only the script can have a custom icon. Inner classes are not supported. [b]Note:[/b] As annotations describe their subject, the [code]@icon[/code] annotation must be placed before the class definition and inheritance. + [b]Note:[/b] Unlike other annotations, the argument of the [code]@icon[/code] annotation must be a string literal (constant expressions are not supported). </description> </annotation> <annotation name="@onready"> diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index d9b8a540c0..8324cb0fe0 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -706,11 +706,7 @@ bool GDScript::_update_exports(bool *r_err, bool p_recursive_call, PlaceHolderSc } members_cache.push_back(member.variable->export_info); - Variant default_value; - if (member.variable->initializer && member.variable->initializer->is_constant) { - default_value = member.variable->initializer->reduced_value; - GDScriptCompiler::convert_to_initializer_type(default_value, member.variable); - } + Variant default_value = analyzer.make_variable_default_value(member.variable); member_default_values_cache[member.variable->identifier->name] = default_value; } break; case GDScriptParser::ClassNode::Member::SIGNAL: { @@ -1525,41 +1521,24 @@ bool GDScriptInstance::set(const StringName &p_name, const Variant &p_value) { HashMap<StringName, GDScript::MemberInfo>::Iterator E = script->member_indices.find(p_name); if (E) { const GDScript::MemberInfo *member = &E->value; - if (member->setter) { - const Variant *val = &p_value; + Variant value = p_value; + if (member->data_type.has_type && !member->data_type.is_type(value)) { + const Variant *args = &p_value; Callable::CallError err; - callp(member->setter, &val, 1, err); - if (err.error == Callable::CallError::CALL_OK) { - return true; //function exists, call was successful - } else { + Variant::construct(member->data_type.builtin_type, value, &args, 1, err); + if (err.error != Callable::CallError::CALL_OK || !member->data_type.is_type(value)) { return false; } + } + if (member->setter) { + const Variant *args = &value; + Callable::CallError err; + callp(member->setter, &args, 1, err); + return err.error == Callable::CallError::CALL_OK; } else { - if (member->data_type.has_type) { - if (member->data_type.builtin_type == Variant::ARRAY && member->data_type.has_container_element_type()) { - // Typed array. - if (p_value.get_type() == Variant::ARRAY) { - return VariantInternal::get_array(&members.write[member->index])->typed_assign(p_value); - } else { - return false; - } - } else if (!member->data_type.is_type(p_value)) { - // Try conversion - Callable::CallError ce; - const Variant *value = &p_value; - Variant converted; - Variant::construct(member->data_type.builtin_type, converted, &value, 1, ce); - if (ce.error == Callable::CallError::CALL_OK) { - members.write[member->index] = converted; - return true; - } else { - return false; - } - } - } - members.write[member->index] = p_value; + members.write[member->index] = value; + return true; } - return true; } } @@ -2458,21 +2437,25 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b GDScriptParser parser; err = parser.parse(source, p_path, false); - if (err) { - return String(); - } - - GDScriptAnalyzer analyzer(&parser); - err = analyzer.resolve_inheritance(); - if (err) { - return String(); - } const GDScriptParser::ClassNode *c = parser.get_tree(); - - if (r_base_type) { - *r_base_type = c->get_datatype().native_type; - } + if (!c) { + return String(); // No class parsed. + } + + /* **WARNING** + * + * This function is written with the goal to be *extremely* error tolerant, as such + * it should meet the following requirements: + * + * - It must not rely on the analyzer (in fact, the analyzer must not be used here), + * because at the time global classes are parsed, the dependencies may not be present + * yet, hence the function will fail (which is unintended). + * - It must not fail even if the parsing fails, because even if the file is broken, + * it should attempt its best to retrieve the inheritance metadata. + * + * Before changing this function, please ask the current maintainer of EditorFileSystem. + */ if (r_icon_path) { if (c->icon_path.is_empty() || c->icon_path.is_absolute_path()) { @@ -2481,7 +2464,73 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b *r_icon_path = p_path.get_base_dir().path_join(c->icon_path).simplify_path(); } } + if (r_base_type) { + const GDScriptParser::ClassNode *subclass = c; + String path = p_path; + GDScriptParser subparser; + while (subclass) { + if (subclass->extends_used) { + if (!subclass->extends_path.is_empty()) { + if (subclass->extends.size() == 0) { + get_global_class_name(subclass->extends_path, r_base_type); + subclass = nullptr; + break; + } else { + Vector<StringName> extend_classes = subclass->extends; + + Ref<FileAccess> subfile = FileAccess::open(subclass->extends_path, FileAccess::READ); + if (subfile.is_null()) { + break; + } + String subsource = subfile->get_as_utf8_string(); + + if (subsource.is_empty()) { + break; + } + String subpath = subclass->extends_path; + if (subpath.is_relative_path()) { + subpath = path.get_base_dir().path_join(subpath).simplify_path(); + } + if (OK != subparser.parse(subsource, subpath, false)) { + break; + } + path = subpath; + subclass = subparser.get_tree(); + + while (extend_classes.size() > 0) { + bool found = false; + for (int i = 0; i < subclass->members.size(); i++) { + if (subclass->members[i].type != GDScriptParser::ClassNode::Member::CLASS) { + continue; + } + + const GDScriptParser::ClassNode *inner_class = subclass->members[i].m_class; + if (inner_class->identifier->name == extend_classes[0]) { + extend_classes.remove_at(0); + found = true; + subclass = inner_class; + break; + } + } + if (!found) { + subclass = nullptr; + break; + } + } + } + } else if (subclass->extends.size() == 1) { + *r_base_type = subclass->extends[0]; + subclass = nullptr; + } else { + break; + } + } else { + *r_base_type = "RefCounted"; + subclass = nullptr; + } + } + } return c->identifier != nullptr ? String(c->identifier->name) : String(); } diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 8dd65a700a..1c2b743909 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -580,6 +580,7 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type if (result.builtin_type == Variant::ARRAY) { GDScriptParser::DataType container_type = type_from_metatype(resolve_datatype(p_type->container_type)); if (container_type.kind != GDScriptParser::DataType::VARIANT) { + container_type.is_constant = false; result.set_container_element_type(container_type); } } @@ -1571,18 +1572,18 @@ void GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode *p_assi if (p_assignable->initializer->type == GDScriptParser::Node::ARRAY) { GDScriptParser::ArrayNode *array = static_cast<GDScriptParser::ArrayNode *>(p_assignable->initializer); - if ((p_assignable->infer_datatype && array->elements.size() > 0) || (has_specified_type && specified_type.has_container_element_type())) { - update_array_literal_element_type(specified_type, array); + if (has_specified_type && specified_type.has_container_element_type()) { + update_array_literal_element_type(array, specified_type.get_container_element_type()); } } - if (is_constant) { - if (p_assignable->initializer->type == GDScriptParser::Node::ARRAY) { - const_fold_array(static_cast<GDScriptParser::ArrayNode *>(p_assignable->initializer), true); - } else if (p_assignable->initializer->type == GDScriptParser::Node::DICTIONARY) { - const_fold_dictionary(static_cast<GDScriptParser::DictionaryNode *>(p_assignable->initializer), true); - } - if (!p_assignable->initializer->is_constant) { + if (is_constant && !p_assignable->initializer->is_constant) { + bool is_initializer_value_reduced = false; + Variant initializer_value = make_expression_reduced_value(p_assignable->initializer, is_initializer_value_reduced); + if (is_initializer_value_reduced) { + p_assignable->initializer->is_constant = true; + p_assignable->initializer->reduced_value = initializer_value; + } else { push_error(vformat(R"(Assigned value for %s "%s" isn't a constant expression.)", p_kind, p_assignable->identifier->name), p_assignable->initializer); } } @@ -1630,6 +1631,8 @@ void GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode *p_assi } else { push_error(vformat(R"(Cannot assign a value of type %s to %s "%s" with specified type %s.)", initializer_type.to_string(), p_kind, p_assignable->identifier->name, specified_type.to_string()), p_assignable->initializer); } + } else if (specified_type.has_container_element_type() && !initializer_type.has_container_element_type()) { + mark_node_unsafe(p_assignable->initializer); #ifdef DEBUG_ENABLED } else if (specified_type.builtin_type == Variant::INT && initializer_type.builtin_type == Variant::FLOAT) { parser->push_warning(p_assignable->initializer, GDScriptWarning::NARROWING_CONVERSION); @@ -1969,20 +1972,40 @@ void GDScriptAnalyzer::resolve_return(GDScriptParser::ReturnNode *p_return) { } if (p_return->return_value != nullptr) { - reduce_expression(p_return->return_value); - if (p_return->return_value->type == GDScriptParser::Node::ARRAY) { - // Check if assigned value is an array literal, so we can make it a typed array too if appropriate. - if (has_expected_type && expected_type.has_container_element_type() && p_return->return_value->type == GDScriptParser::Node::ARRAY) { - update_array_literal_element_type(expected_type, static_cast<GDScriptParser::ArrayNode *>(p_return->return_value)); + bool is_void_function = has_expected_type && expected_type.is_hard_type() && expected_type.kind == GDScriptParser::DataType::BUILTIN && expected_type.builtin_type == Variant::NIL; + bool is_call = p_return->return_value->type == GDScriptParser::Node::CALL; + if (is_void_function && is_call) { + // Pretend the call is a root expression to allow those that are "void". + reduce_call(static_cast<GDScriptParser::CallNode *>(p_return->return_value), false, true); + } else { + reduce_expression(p_return->return_value); + } + if (is_void_function) { + p_return->void_return = true; + const GDScriptParser::DataType &return_type = p_return->return_value->datatype; + if (is_call && !return_type.is_hard_type()) { + String function_name = parser->current_function->identifier ? parser->current_function->identifier->name.operator String() : String("<anonymous function>"); + String called_function_name = static_cast<GDScriptParser::CallNode *>(p_return->return_value)->function_name.operator String(); +#ifdef DEBUG_ENABLED + parser->push_warning(p_return, GDScriptWarning::UNSAFE_VOID_RETURN, function_name, called_function_name); +#endif + mark_node_unsafe(p_return); + } else if (!is_call) { + push_error("A void function cannot return a value.", p_return); } + result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + result.kind = GDScriptParser::DataType::BUILTIN; + result.builtin_type = Variant::NIL; + result.is_constant = true; + } else { + if (p_return->return_value->type == GDScriptParser::Node::ARRAY && has_expected_type && expected_type.has_container_element_type()) { + update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_return->return_value), expected_type.get_container_element_type()); + } + if (has_expected_type && expected_type.is_hard_type() && p_return->return_value->is_constant) { + update_const_expression_builtin_type(p_return->return_value, expected_type, "return"); + } + result = p_return->return_value->get_datatype(); } - if (has_expected_type && expected_type.is_hard_type() && expected_type.kind == GDScriptParser::DataType::BUILTIN && expected_type.builtin_type == Variant::NIL) { - push_error("A void function cannot return a value.", p_return); - } - if (has_expected_type && expected_type.is_hard_type() && p_return->return_value->is_constant) { - update_const_expression_builtin_type(p_return->return_value, expected_type, "return"); - } - result = p_return->return_value->get_datatype(); } else { // Return type is null by default. result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; @@ -2183,49 +2206,26 @@ void GDScriptAnalyzer::update_const_expression_builtin_type(GDScriptParser::Expr // When an array literal is stored (or passed as function argument) to a typed context, we then assume the array is typed. // This function determines which type is that (if any). -void GDScriptAnalyzer::update_array_literal_element_type(const GDScriptParser::DataType &p_base_type, GDScriptParser::ArrayNode *p_array_literal) { - GDScriptParser::DataType array_type = p_array_literal->get_datatype(); - if (p_array_literal->elements.size() == 0) { - // Empty array literal, just make the same type as the storage. - array_type.set_container_element_type(p_base_type.get_container_element_type()); - } else { - // Check if elements match. - bool all_same_type = true; - bool all_have_type = true; - - GDScriptParser::DataType element_type; - for (int i = 0; i < p_array_literal->elements.size(); i++) { - if (i == 0) { - element_type = p_array_literal->elements[0]->get_datatype(); - } else { - GDScriptParser::DataType this_element_type = p_array_literal->elements[i]->get_datatype(); - if (this_element_type.has_no_type()) { - all_same_type = false; - all_have_type = false; - break; - } else if (element_type != this_element_type) { - if (!is_type_compatible(element_type, this_element_type, false)) { - if (is_type_compatible(this_element_type, element_type, false)) { - // This element is a super-type to the previous type, so we use the super-type. - element_type = this_element_type; - } else { - // It's incompatible. - all_same_type = false; - break; - } - } - } - } +void GDScriptAnalyzer::update_array_literal_element_type(GDScriptParser::ArrayNode *p_array, const GDScriptParser::DataType &p_element_type) { + for (int i = 0; i < p_array->elements.size(); i++) { + GDScriptParser::ExpressionNode *element_node = p_array->elements[i]; + if (element_node->is_constant) { + update_const_expression_builtin_type(element_node, p_element_type, "include"); } - if (all_same_type) { - element_type.is_constant = false; - array_type.set_container_element_type(element_type); - } else if (all_have_type) { - push_error(vformat(R"(Variant array is not compatible with an array of type "%s".)", p_base_type.get_container_element_type().to_string()), p_array_literal); + const GDScriptParser::DataType &element_type = element_node->get_datatype(); + if (element_type.has_no_type() || element_type.is_variant() || !element_type.is_hard_type()) { + mark_node_unsafe(element_node); + continue; + } + if (!is_type_compatible(p_element_type, element_type, true, p_array)) { + push_error(vformat(R"(Cannot have an element of type "%s" in an array of type "Array[%s]".)", element_type.to_string(), p_element_type.to_string()), element_node); + return; } } - // Update the type on the value itself. - p_array_literal->set_datatype(array_type); + + GDScriptParser::DataType array_type = p_array->get_datatype(); + array_type.set_container_element_type(p_element_type); + p_array->set_datatype(array_type); } void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assignment) { @@ -2243,8 +2243,8 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig } // Check if assigned value is an array literal, so we can make it a typed array too if appropriate. - if (assignee_type.has_container_element_type() && p_assignment->assigned_value->type == GDScriptParser::Node::ARRAY) { - update_array_literal_element_type(assignee_type, static_cast<GDScriptParser::ArrayNode *>(p_assignment->assigned_value)); + if (p_assignment->assigned_value->type == GDScriptParser::Node::ARRAY && assignee_type.has_container_element_type()) { + update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_assignment->assigned_value), assignee_type.get_container_element_type()); } if (p_assignment->operation == GDScriptParser::AssignmentNode::OP_NONE && assignee_type.is_hard_type() && p_assignment->assigned_value->is_constant) { @@ -2322,6 +2322,9 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig // weak non-variant assignee and incompatible result downgrades_assignee = true; } + } else if (assignee_type.has_container_element_type() && !op_type.has_container_element_type()) { + // typed array assignee and untyped array result + mark_node_unsafe(p_assignment); } } } @@ -2484,6 +2487,62 @@ 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) { + for (int index = 0; map[index][0]; index++) { + if (map[index][0] == key) { + return map[index][1]; + } + } + return nullptr; +} + +// 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) { + switch (type) { + case GDScriptParser::Node::IDENTIFIER: { + // Check properties + const char *result = get_rename_from_map(ProjectConverter3To4::gdscript_properties_renames, identifier); + if (result) { + return result; + } + // Check enum values + result = get_rename_from_map(ProjectConverter3To4::enum_renames, identifier); + if (result) { + return result; + } + // Check color constants + result = get_rename_from_map(ProjectConverter3To4::color_renames, identifier); + if (result) { + return result; + } + // Check type names + result = get_rename_from_map(ProjectConverter3To4::class_renames, identifier); + if (result) { + return result; + } + return get_rename_from_map(ProjectConverter3To4::builtin_types_renames, identifier); + } + case GDScriptParser::Node::CALL: { + const char *result = get_rename_from_map(ProjectConverter3To4::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); + } + // Signal references don't get parsed through the GDScriptAnalyzer. No support for signal rename hints. + default: + // No rename found, return null + return nullptr; + } +} +#endif // DISABLE_DEPRECATED +#endif // TOOLS_ENABLED + void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_await, bool p_is_root) { bool all_is_constant = true; HashMap<int, GDScriptParser::ArrayNode *> arrays; // For array literal to potentially type when passing. @@ -2822,7 +2881,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a for (const KeyValue<int, GDScriptParser::ArrayNode *> &E : arrays) { int index = E.key; if (index < par_types.size() && par_types[index].has_container_element_type()) { - update_array_literal_element_type(par_types[index], E.value); + update_array_literal_element_type(E.value, par_types[index].get_container_element_type()); } } validate_call_arg(par_types, default_arg_count, is_vararg, p_call); @@ -2901,7 +2960,22 @@ 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 + 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); + if (renamed_function_name) { + rename_hint = " " + vformat(R"(Did you mean to use "%s"?)", String(renamed_function_name) + "()"); + } + } + 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 } 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); } @@ -2933,6 +3007,10 @@ void GDScriptAnalyzer::reduce_cast(GDScriptParser::CastNode *p_cast) { } } + if (p_cast->operand->type == GDScriptParser::Node::ARRAY && cast_type.has_container_element_type()) { + update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_cast->operand), cast_type.get_container_element_type()); + } + if (!cast_type.is_variant()) { GDScriptParser::DataType op_type = p_cast->operand->get_datatype(); if (op_type.is_variant() || !op_type.is_hard_type()) { @@ -3038,10 +3116,12 @@ void GDScriptAnalyzer::reduce_identifier_from_base_set_class(GDScriptParser::Ide p_identifier->set_datatype(p_identifier_datatype); Error err = OK; - GDScript *scr = GDScriptCache::get_shallow_script(p_identifier_datatype.script_path, err).ptr(); - ERR_FAIL_COND_MSG(err != OK, vformat(R"(Error while getting cache for script "%s".)", p_identifier_datatype.script_path)); - scr = scr->find_class(p_identifier_datatype.class_type->fqcn); - p_identifier->reduced_value = scr; + Ref<GDScript> scr = GDScriptCache::get_shallow_script(p_identifier_datatype.script_path, err); + if (err) { + push_error(vformat(R"(Error while getting cache for script "%s".)", p_identifier_datatype.script_path), p_identifier); + return; + } + p_identifier->reduced_value = scr->find_class(p_identifier_datatype.class_type->fqcn); p_identifier->is_constant = true; } @@ -3085,7 +3165,22 @@ 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()) { - push_error(vformat(R"(Cannot find constant "%s" on type "%s".)", name, base.to_string()), p_identifier); +#ifdef TOOLS_ENABLED +#ifndef DISABLE_DEPRECATED + 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); + if (renamed_identifier_name) { + rename_hint = " " + vformat(R"(Did you mean to use "%s"?)", renamed_identifier_name); + } + } + 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 } } else { switch (base.builtin_type) { @@ -3114,7 +3209,22 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod } } if (base.is_hard_type()) { +#ifdef TOOLS_ENABLED +#ifndef DISABLE_DEPRECATED + 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); + if (renamed_identifier_name) { + rename_hint = " " + vformat(R"(Did you mean to use "%s"?)", renamed_identifier_name); + } + } + 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 } } } @@ -3453,7 +3563,22 @@ 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 + 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); + if (renamed_identifier_name) { + rename_hint = " " + vformat(R"(Did you mean to use "%s"?)", renamed_identifier_name); + } + } + 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 } GDScriptParser::DataType dummy; dummy.kind = GDScriptParser::DataType::VARIANT; @@ -3585,12 +3710,6 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri reduce_identifier(static_cast<GDScriptParser::IdentifierNode *>(p_subscript->base), true); } else { reduce_expression(p_subscript->base); - - if (p_subscript->base->type == GDScriptParser::Node::ARRAY) { - const_fold_array(static_cast<GDScriptParser::ArrayNode *>(p_subscript->base), false); - } else if (p_subscript->base->type == GDScriptParser::Node::DICTIONARY) { - const_fold_dictionary(static_cast<GDScriptParser::DictionaryNode *>(p_subscript->base), false); - } } GDScriptParser::DataType result_type; @@ -3915,58 +4034,146 @@ void GDScriptAnalyzer::reduce_unary_op(GDScriptParser::UnaryOpNode *p_unary_op) p_unary_op->set_datatype(result); } -void GDScriptAnalyzer::const_fold_array(GDScriptParser::ArrayNode *p_array, bool p_is_const) { +Variant GDScriptAnalyzer::make_expression_reduced_value(GDScriptParser::ExpressionNode *p_expression, bool &is_reduced) { + Variant value; + + if (p_expression->is_constant) { + is_reduced = true; + value = p_expression->reduced_value; + } else if (p_expression->type == GDScriptParser::Node::ARRAY) { + value = make_array_reduced_value(static_cast<GDScriptParser::ArrayNode *>(p_expression), is_reduced); + } else if (p_expression->type == GDScriptParser::Node::DICTIONARY) { + value = make_dictionary_reduced_value(static_cast<GDScriptParser::DictionaryNode *>(p_expression), is_reduced); + } else if (p_expression->type == GDScriptParser::Node::SUBSCRIPT) { + value = make_subscript_reduced_value(static_cast<GDScriptParser::SubscriptNode *>(p_expression), is_reduced); + } + + return value; +} + +Variant GDScriptAnalyzer::make_array_reduced_value(GDScriptParser::ArrayNode *p_array, bool &is_reduced) { + Array array = p_array->get_datatype().has_container_element_type() ? make_array_from_element_datatype(p_array->get_datatype().get_container_element_type()) : Array(); + + array.resize(p_array->elements.size()); for (int i = 0; i < p_array->elements.size(); i++) { GDScriptParser::ExpressionNode *element = p_array->elements[i]; - if (element->type == GDScriptParser::Node::ARRAY) { - const_fold_array(static_cast<GDScriptParser::ArrayNode *>(element), p_is_const); - } else if (element->type == GDScriptParser::Node::DICTIONARY) { - const_fold_dictionary(static_cast<GDScriptParser::DictionaryNode *>(element), p_is_const); + bool is_element_value_reduced = false; + Variant element_value = make_expression_reduced_value(element, is_element_value_reduced); + if (!is_element_value_reduced) { + return Variant(); } - if (!element->is_constant) { - return; - } + array[i] = element_value; } - Array array; - array.resize(p_array->elements.size()); - for (int i = 0; i < p_array->elements.size(); i++) { - array[i] = p_array->elements[i]->reduced_value; - } - if (p_is_const) { - array.make_read_only(); - } - p_array->is_constant = true; - p_array->reduced_value = array; + array.make_read_only(); + + is_reduced = true; + return array; } -void GDScriptAnalyzer::const_fold_dictionary(GDScriptParser::DictionaryNode *p_dictionary, bool p_is_const) { +Variant GDScriptAnalyzer::make_dictionary_reduced_value(GDScriptParser::DictionaryNode *p_dictionary, bool &is_reduced) { + Dictionary dictionary; + for (int i = 0; i < p_dictionary->elements.size(); i++) { const GDScriptParser::DictionaryNode::Pair &element = p_dictionary->elements[i]; - if (element.value->type == GDScriptParser::Node::ARRAY) { - const_fold_array(static_cast<GDScriptParser::ArrayNode *>(element.value), p_is_const); - } else if (element.value->type == GDScriptParser::Node::DICTIONARY) { - const_fold_dictionary(static_cast<GDScriptParser::DictionaryNode *>(element.value), p_is_const); + bool is_element_key_reduced = false; + Variant element_key = make_expression_reduced_value(element.key, is_element_key_reduced); + if (!is_element_key_reduced) { + return Variant(); } - if (!element.key->is_constant || !element.value->is_constant) { - return; + bool is_element_value_reduced = false; + Variant element_value = make_expression_reduced_value(element.value, is_element_value_reduced); + if (!is_element_value_reduced) { + return Variant(); } + + dictionary[element_key] = element_value; } - Dictionary dict; - for (int i = 0; i < p_dictionary->elements.size(); i++) { - const GDScriptParser::DictionaryNode::Pair &element = p_dictionary->elements[i]; - dict[element.key->reduced_value] = element.value->reduced_value; + dictionary.make_read_only(); + + is_reduced = true; + return dictionary; +} + +Variant GDScriptAnalyzer::make_subscript_reduced_value(GDScriptParser::SubscriptNode *p_subscript, bool &is_reduced) { + bool is_base_value_reduced = false; + Variant base_value = make_expression_reduced_value(p_subscript->base, is_base_value_reduced); + if (!is_base_value_reduced) { + return Variant(); + } + + if (p_subscript->is_attribute) { + bool is_valid = false; + Variant value = base_value.get_named(p_subscript->attribute->name, is_valid); + if (is_valid) { + is_reduced = true; + return value; + } else { + return Variant(); + } + } else { + bool is_index_value_reduced = false; + Variant index_value = make_expression_reduced_value(p_subscript->index, is_index_value_reduced); + if (!is_index_value_reduced) { + return Variant(); + } + + bool is_valid = false; + Variant value = base_value.get(index_value, &is_valid); + if (is_valid) { + is_reduced = true; + return value; + } else { + return Variant(); + } } - if (p_is_const) { - dict.make_read_only(); +} + +Array GDScriptAnalyzer::make_array_from_element_datatype(const GDScriptParser::DataType &p_element_datatype, const GDScriptParser::Node *p_source_node) { + Array array; + + Ref<Script> script_type = p_element_datatype.script_type; + if (p_element_datatype.kind == GDScriptParser::DataType::CLASS && script_type.is_null()) { + Error err = OK; + Ref<GDScript> scr = GDScriptCache::get_shallow_script(p_element_datatype.script_path, err); + if (err) { + push_error(vformat(R"(Error while getting cache for script "%s".)", p_element_datatype.script_path), p_source_node); + return array; + } + script_type.reference_ptr(scr->find_class(p_element_datatype.class_type->fqcn)); } - p_dictionary->is_constant = true; - p_dictionary->reduced_value = dict; + + array.set_typed(p_element_datatype.builtin_type, p_element_datatype.native_type, script_type); + + return array; +} + +Variant GDScriptAnalyzer::make_variable_default_value(GDScriptParser::VariableNode *p_variable) { + Variant result = Variant(); + + if (p_variable->initializer) { + bool is_initializer_value_reduced = false; + Variant initializer_value = make_expression_reduced_value(p_variable->initializer, is_initializer_value_reduced); + if (is_initializer_value_reduced) { + result = initializer_value; + } + } else { + GDScriptParser::DataType datatype = p_variable->get_datatype(); + if (datatype.is_hard_type() && datatype.kind == GDScriptParser::DataType::BUILTIN && datatype.builtin_type != Variant::OBJECT) { + if (datatype.builtin_type == Variant::ARRAY && datatype.has_container_element_type()) { + result = make_array_from_element_datatype(datatype.get_container_element_type()); + } else { + VariantInternal::initialize(&result, datatype.builtin_type); + } + } + } + + return result; } GDScriptParser::DataType GDScriptAnalyzer::type_from_variant(const Variant &p_value, const GDScriptParser::Node *p_source) { @@ -4437,14 +4644,8 @@ bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_targ } if (valid && p_target.builtin_type == Variant::ARRAY && p_source.builtin_type == Variant::ARRAY) { // Check the element type. - if (p_target.has_container_element_type()) { - if (!p_source.has_container_element_type()) { - // TODO: Maybe this is valid but unsafe? - // Variant array can't be appended to typed array. - valid = false; - } else { - valid = is_type_compatible(p_target.get_container_element_type(), p_source.get_container_element_type(), p_allow_implicit_conversion); - } + if (p_target.has_container_element_type() && p_source.has_container_element_type()) { + valid = p_target.get_container_element_type() == p_source.get_container_element_type(); } } return valid; @@ -4546,7 +4747,7 @@ bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_targ return ClassDB::is_parent_class(src_native, GDScript::get_class_static()); } while (src_class != nullptr) { - if (src_class->fqcn == p_target.class_type->fqcn) { + if (src_class == p_target.class_type || src_class->fqcn == p_target.class_type->fqcn) { return true; } src_class = src_class->base_type.class_type; @@ -4646,11 +4847,6 @@ Ref<GDScriptParserRef> GDScriptAnalyzer::get_parser_for(const String &p_path) { } Error GDScriptAnalyzer::resolve_inheritance() { - // Apply annotations. - for (GDScriptParser::AnnotationNode *&E : parser->head->annotations) { - resolve_annotation(E); - E->apply(parser, parser->head); - } return resolve_class_inheritance(parser->head, true); } @@ -4684,6 +4880,12 @@ Error GDScriptAnalyzer::analyze() { return err; } + // Apply annotations. + for (GDScriptParser::AnnotationNode *&E : parser->head->annotations) { + resolve_annotation(E); + E->apply(parser, parser->head); + } + resolve_interface(); resolve_body(); if (!parser->errors.is_empty()) { diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h index cd2c4c6569..b51564fb0a 100644 --- a/modules/gdscript/gdscript_analyzer.h +++ b/modules/gdscript/gdscript_analyzer.h @@ -37,6 +37,10 @@ #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; @@ -102,10 +106,13 @@ class GDScriptAnalyzer { void reduce_ternary_op(GDScriptParser::TernaryOpNode *p_ternary_op, bool p_is_root = false); void reduce_unary_op(GDScriptParser::UnaryOpNode *p_unary_op); - void const_fold_array(GDScriptParser::ArrayNode *p_array, bool p_is_const); - void const_fold_dictionary(GDScriptParser::DictionaryNode *p_dictionary, bool p_is_const); + Variant make_expression_reduced_value(GDScriptParser::ExpressionNode *p_expression, bool &is_reduced); + Variant make_array_reduced_value(GDScriptParser::ArrayNode *p_array, bool &is_reduced); + Variant make_dictionary_reduced_value(GDScriptParser::DictionaryNode *p_dictionary, bool &is_reduced); + Variant make_subscript_reduced_value(GDScriptParser::SubscriptNode *p_subscript, bool &is_reduced); // Helpers. + Array make_array_from_element_datatype(const GDScriptParser::DataType &p_element_datatype, const GDScriptParser::Node *p_source_node = nullptr); GDScriptParser::DataType type_from_variant(const Variant &p_value, const GDScriptParser::Node *p_source); static GDScriptParser::DataType type_from_metatype(const GDScriptParser::DataType &p_meta_type); GDScriptParser::DataType type_from_property(const PropertyInfo &p_property, bool p_is_arg = false) const; @@ -117,7 +124,7 @@ class GDScriptAnalyzer { GDScriptParser::DataType get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, const GDScriptParser::DataType &p_b, bool &r_valid, const GDScriptParser::Node *p_source); GDScriptParser::DataType get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, bool &r_valid, const GDScriptParser::Node *p_source); void update_const_expression_builtin_type(GDScriptParser::ExpressionNode *p_expression, const GDScriptParser::DataType &p_type, const char *p_usage, bool p_is_cast = false); - void update_array_literal_element_type(const GDScriptParser::DataType &p_base_type, GDScriptParser::ArrayNode *p_array_literal); + void update_array_literal_element_type(GDScriptParser::ArrayNode *p_array, const GDScriptParser::DataType &p_element_type); bool is_type_compatible(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion = false, const GDScriptParser::Node *p_source_node = nullptr); void push_error(const String &p_message, const GDScriptParser::Node *p_origin = nullptr); void mark_node_unsafe(const GDScriptParser::Node *p_node); @@ -125,11 +132,18 @@ class GDScriptAnalyzer { void mark_lambda_use_self(); bool class_exists(const StringName &p_class) const; Ref<GDScriptParserRef> get_parser_for(const String &p_path); - static void reduce_identifier_from_base_set_class(GDScriptParser::IdentifierNode *p_identifier, GDScriptParser::DataType p_identifier_datatype); + void reduce_identifier_from_base_set_class(GDScriptParser::IdentifierNode *p_identifier, GDScriptParser::DataType p_identifier_datatype); #ifdef DEBUG_ENABLED 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(); @@ -137,6 +151,8 @@ public: Error resolve_dependencies(); Error analyze(); + Variant make_variable_default_value(GDScriptParser::VariableNode *p_variable); + GDScriptAnalyzer(GDScriptParser *p_parser); }; diff --git a/modules/gdscript/gdscript_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp index e19dda090e..ec7a2b0f1c 100644 --- a/modules/gdscript/gdscript_byte_codegen.cpp +++ b/modules/gdscript/gdscript_byte_codegen.cpp @@ -826,9 +826,13 @@ void GDScriptByteCodeGenerator::write_assign_with_conversion(const Address &p_ta switch (p_target.type.kind) { case GDScriptDataType::BUILTIN: { if (p_target.type.builtin_type == Variant::ARRAY && p_target.type.has_container_element_type()) { + const GDScriptDataType &element_type = p_target.type.get_container_element_type(); append_opcode(GDScriptFunction::OPCODE_ASSIGN_TYPED_ARRAY); append(p_target); append(p_source); + append(get_constant_pos(element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS)); + append(element_type.builtin_type); + append(element_type.native_type); } else { append_opcode(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN); append(p_target); @@ -868,9 +872,13 @@ void GDScriptByteCodeGenerator::write_assign_with_conversion(const Address &p_ta void GDScriptByteCodeGenerator::write_assign(const Address &p_target, const Address &p_source) { if (p_target.type.kind == GDScriptDataType::BUILTIN && p_target.type.builtin_type == Variant::ARRAY && p_target.type.has_container_element_type()) { + const GDScriptDataType &element_type = p_target.type.get_container_element_type(); append_opcode(GDScriptFunction::OPCODE_ASSIGN_TYPED_ARRAY); append(p_target); append(p_source); + append(get_constant_pos(element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS)); + append(element_type.builtin_type); + append(element_type.native_type); } else if (p_target.type.kind == GDScriptDataType::BUILTIN && p_source.type.kind == GDScriptDataType::BUILTIN && p_target.type.builtin_type != p_source.type.builtin_type) { // Need conversion. append_opcode(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN); @@ -1326,14 +1334,7 @@ void GDScriptByteCodeGenerator::write_construct_typed_array(const Address &p_tar append(p_arguments[i]); } append(get_call_target(p_target)); - if (p_element_type.script_type) { - Variant script_type = Ref<Script>(p_element_type.script_type); - int addr = get_constant_pos(script_type); - addr |= GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS; - append(addr); - } else { - append(Address()); // null. - } + append(get_constant_pos(p_element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS)); append(p_arguments.size()); append(p_element_type.builtin_type); append(p_element_type.native_type); @@ -1608,14 +1609,10 @@ void GDScriptByteCodeGenerator::write_return(const Address &p_return_value) { if (function->return_type.kind == GDScriptDataType::BUILTIN && function->return_type.builtin_type == Variant::ARRAY && function->return_type.has_container_element_type()) { // Typed array. const GDScriptDataType &element_type = function->return_type.get_container_element_type(); - - Variant script = element_type.script_type; - int script_idx = get_constant_pos(script) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS); - append_opcode(GDScriptFunction::OPCODE_RETURN_TYPED_ARRAY); append(p_return_value); - append(script_idx); - append(element_type.kind == GDScriptDataType::BUILTIN ? element_type.builtin_type : Variant::OBJECT); + append(get_constant_pos(element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS)); + append(element_type.builtin_type); append(element_type.native_type); } else if (function->return_type.kind == GDScriptDataType::BUILTIN && p_return_value.type.kind == GDScriptDataType::BUILTIN && function->return_type.builtin_type != p_return_value.type.builtin_type) { // Add conversion. @@ -1636,15 +1633,10 @@ void GDScriptByteCodeGenerator::write_return(const Address &p_return_value) { case GDScriptDataType::BUILTIN: { if (function->return_type.builtin_type == Variant::ARRAY && function->return_type.has_container_element_type()) { const GDScriptDataType &element_type = function->return_type.get_container_element_type(); - - Variant script = function->return_type.script_type; - int script_idx = get_constant_pos(script); - script_idx |= (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS); - append_opcode(GDScriptFunction::OPCODE_RETURN_TYPED_ARRAY); append(p_return_value); - append(script_idx); - append(element_type.kind == GDScriptDataType::BUILTIN ? element_type.builtin_type : Variant::OBJECT); + append(get_constant_pos(element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS)); + append(element_type.builtin_type); append(element_type.native_type); } else { append_opcode(GDScriptFunction::OPCODE_RETURN_TYPED_BUILTIN); diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index 6acdc9f212..46cd4b0d55 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -1859,7 +1859,12 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui } } - gen->write_return(return_value); + if (return_n->void_return) { + // Always return "null", even if the expression is a call to a void function. + gen->write_return(codegen.add_constant(Variant())); + } else { + gen->write_return(return_value); + } if (return_value.mode == GDScriptCodeGenerator::Address::TEMPORARY) { codegen.generator->pop_temporary(); } @@ -1904,14 +1909,6 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui bool initialized = false; if (lv->initializer != nullptr) { - // For typed arrays we need to make sure this is already initialized correctly so typed assignment work. - if (local_type.has_type && local_type.builtin_type == Variant::ARRAY) { - if (local_type.has_container_element_type()) { - codegen.generator->write_construct_typed_array(local, local_type.get_container_element_type(), Vector<GDScriptCodeGenerator::Address>()); - } else { - codegen.generator->write_construct_array(local, Vector<GDScriptCodeGenerator::Address>()); - } - } GDScriptCodeGenerator::Address src_address = _parse_expression(codegen, err, lv->initializer); if (err) { return err; @@ -2052,14 +2049,6 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_ // Emit proper line change. codegen.generator->write_newline(field->initializer->start_line); - // For typed arrays we need to make sure this is already initialized correctly so typed assignment work. - if (field_type.has_type && field_type.builtin_type == Variant::ARRAY) { - if (field_type.has_container_element_type()) { - codegen.generator->write_construct_typed_array(dst_address, field_type.get_container_element_type(), Vector<GDScriptCodeGenerator::Address>()); - } else { - codegen.generator->write_construct_array(dst_address, Vector<GDScriptCodeGenerator::Address>()); - } - } GDScriptCodeGenerator::Address src_address = _parse_expression(codegen, r_error, field->initializer, false, true); if (r_error) { memdelete(codegen.generator); @@ -2100,17 +2089,6 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_ return nullptr; } GDScriptCodeGenerator::Address dst_addr = codegen.parameters[parameter->identifier->name]; - - // For typed arrays we need to make sure this is already initialized correctly so typed assignment work. - GDScriptDataType par_type = dst_addr.type; - if (par_type.has_type && par_type.builtin_type == Variant::ARRAY) { - if (par_type.has_container_element_type()) { - codegen.generator->write_construct_typed_array(dst_addr, par_type.get_container_element_type(), Vector<GDScriptCodeGenerator::Address>()); - } else { - codegen.generator->write_construct_array(dst_addr, Vector<GDScriptCodeGenerator::Address>()); - } - } - codegen.generator->write_assign_default_parameter(dst_addr, src_addr, parameter->use_conversion_assign); if (src_addr.mode == GDScriptCodeGenerator::Address::TEMPORARY) { codegen.generator->pop_temporary(); diff --git a/modules/gdscript/gdscript_disassembler.cpp b/modules/gdscript/gdscript_disassembler.cpp index b5c8a6f478..d4f4358ac1 100644 --- a/modules/gdscript/gdscript_disassembler.cpp +++ b/modules/gdscript/gdscript_disassembler.cpp @@ -317,7 +317,7 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { text += " = "; text += DADDR(2); - incr += 3; + incr += 6; } break; case OPCODE_ASSIGN_TYPED_NATIVE: { text += "assign typed native ("; @@ -434,7 +434,7 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { int instr_var_args = _code_ptr[++ip]; int argc = _code_ptr[ip + 1 + instr_var_args]; - Ref<Script> script_type = get_constant(_code_ptr[ip + argc + 2]); + Ref<Script> script_type = get_constant(_code_ptr[ip + argc + 2] & GDScriptFunction::ADDR_MASK); Variant::Type builtin_type = (Variant::Type)_code_ptr[ip + argc + 4]; StringName native_type = get_global_name(_code_ptr[ip + argc + 5]); @@ -463,7 +463,7 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { text += "]"; - incr += 3 + argc; + incr += 6 + argc; } break; case OPCODE_CONSTRUCT_DICTIONARY: { int instr_var_args = _code_ptr[++ip]; diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 66374d0a6d..713ad3ed17 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -51,46 +51,8 @@ static HashMap<StringName, Variant::Type> builtin_types; Variant::Type GDScriptParser::get_builtin_type(const StringName &p_type) { if (builtin_types.is_empty()) { - builtin_types["bool"] = Variant::BOOL; - builtin_types["int"] = Variant::INT; - builtin_types["float"] = Variant::FLOAT; - builtin_types["String"] = Variant::STRING; - builtin_types["Vector2"] = Variant::VECTOR2; - builtin_types["Vector2i"] = Variant::VECTOR2I; - builtin_types["Rect2"] = Variant::RECT2; - builtin_types["Rect2i"] = Variant::RECT2I; - builtin_types["Transform2D"] = Variant::TRANSFORM2D; - builtin_types["Vector3"] = Variant::VECTOR3; - builtin_types["Vector3i"] = Variant::VECTOR3I; - builtin_types["Vector4"] = Variant::VECTOR4; - builtin_types["Vector4i"] = Variant::VECTOR4I; - builtin_types["AABB"] = Variant::AABB; - builtin_types["Plane"] = Variant::PLANE; - builtin_types["Quaternion"] = Variant::QUATERNION; - builtin_types["Basis"] = Variant::BASIS; - builtin_types["Transform3D"] = Variant::TRANSFORM3D; - builtin_types["Projection"] = Variant::PROJECTION; - builtin_types["Color"] = Variant::COLOR; - builtin_types["RID"] = Variant::RID; - builtin_types["Object"] = Variant::OBJECT; - builtin_types["StringName"] = Variant::STRING_NAME; - builtin_types["NodePath"] = Variant::NODE_PATH; - builtin_types["Dictionary"] = Variant::DICTIONARY; - builtin_types["Callable"] = Variant::CALLABLE; - builtin_types["Signal"] = Variant::SIGNAL; - builtin_types["Array"] = Variant::ARRAY; - builtin_types["PackedByteArray"] = Variant::PACKED_BYTE_ARRAY; - builtin_types["PackedInt32Array"] = Variant::PACKED_INT32_ARRAY; - builtin_types["PackedInt64Array"] = Variant::PACKED_INT64_ARRAY; - builtin_types["PackedFloat32Array"] = Variant::PACKED_FLOAT32_ARRAY; - builtin_types["PackedFloat64Array"] = Variant::PACKED_FLOAT64_ARRAY; - builtin_types["PackedStringArray"] = Variant::PACKED_STRING_ARRAY; - builtin_types["PackedVector2Array"] = Variant::PACKED_VECTOR2_ARRAY; - builtin_types["PackedVector3Array"] = Variant::PACKED_VECTOR3_ARRAY; - builtin_types["PackedColorArray"] = Variant::PACKED_COLOR_ARRAY; - // NIL is not here, hence the -1. - if (builtin_types.size() != Variant::VARIANT_MAX - 1) { - ERR_PRINT("Outdated parser: amount of built-in types don't match the amount of types in Variant."); + for (int i = 1; i < Variant::VARIANT_MAX; i++) { + builtin_types[Variant::get_type_name((Variant::Type)i)] = (Variant::Type)i; } } @@ -529,7 +491,12 @@ void GDScriptParser::parse_program() { AnnotationNode *annotation = parse_annotation(AnnotationInfo::SCRIPT | AnnotationInfo::STANDALONE | AnnotationInfo::CLASS_LEVEL); if (annotation != nullptr) { if (annotation->applies_to(AnnotationInfo::SCRIPT)) { - head->annotations.push_back(annotation); + // `@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 { annotation_stack.push_back(annotation); // This annotation must appear after script-level annotations @@ -847,7 +814,7 @@ void GDScriptParser::parse_class_body(bool p_is_multiline) { if (previous.type != GDScriptTokenizer::Token::NEWLINE) { push_error(R"(Expected newline after a standalone annotation.)"); } - if (annotation->name == "@export_category" || annotation->name == "@export_group" || annotation->name == "@export_subgroup") { + if (annotation->name == SNAME("@export_category") || annotation->name == SNAME("@export_group") || annotation->name == SNAME("@export_subgroup")) { current_class->add_member_group(annotation); } else { // For potential non-group standalone annotations. @@ -982,14 +949,14 @@ GDScriptParser::VariableNode *GDScriptParser::parse_property(VariableNode *p_var // Run with a loop because order doesn't matter. for (int i = 0; i < 2; i++) { - if (function->name == "set") { + if (function->name == SNAME("set")) { if (setter_used) { push_error(R"(Properties can only have one setter.)"); } else { parse_property_setter(property); setter_used = true; } - } else if (function->name == "get") { + } else if (function->name == SNAME("get")) { if (getter_used) { push_error(R"(Properties can only have one getter.)"); } else { @@ -1474,7 +1441,7 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali match(GDScriptTokenizer::Token::NEWLINE); // Newline after annotation is optional. if (valid) { - valid = validate_annotation_argument_count(annotation); + valid = validate_annotation_arguments(annotation); } return valid ? annotation : nullptr; @@ -2921,7 +2888,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre // Arguments. CompletionType ct = COMPLETION_CALL_ARGUMENTS; - if (call->function_name == "load") { + if (call->function_name == SNAME("load")) { ct = COMPLETION_RESOURCE_PATH; } push_completion_call(call); @@ -3589,7 +3556,7 @@ bool GDScriptParser::AnnotationNode::applies_to(uint32_t p_target_kinds) const { return (info->target_kind & p_target_kinds) > 0; } -bool GDScriptParser::validate_annotation_argument_count(AnnotationNode *p_annotation) { +bool GDScriptParser::validate_annotation_arguments(AnnotationNode *p_annotation) { ERR_FAIL_COND_V_MSG(!valid_annotations.has(p_annotation->name), false, vformat(R"(Annotation "%s" not found to validate.)", p_annotation->name)); const MethodInfo &info = valid_annotations[p_annotation->name].info; @@ -3604,6 +3571,27 @@ bool GDScriptParser::validate_annotation_argument_count(AnnotationNode *p_annota return false; } + // `@icon`'s argument needs to be resolved in the parser. See GH-72444. + if (p_annotation->name == SNAME("@icon")) { + ExpressionNode *argument = p_annotation->arguments[0]; + + if (argument->type != Node::LITERAL) { + push_error(R"(Argument 1 of annotation "@icon" must be a string literal.)", argument); + return false; + } + + Variant value = static_cast<LiteralNode *>(argument)->value; + + if (value.get_type() != Variant::STRING) { + push_error(R"(Argument 1 of annotation "@icon" must be a string literal.)", argument); + return false; + } + + p_annotation->resolved_arguments.push_back(value); + } + + // For other annotations, see `GDScriptAnalyzer::resolve_annotation()`. + return true; } diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index 74e12d0b5e..07dac25ec5 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -190,7 +190,7 @@ public: case SCRIPT: return script_type == p_other.script_type; case CLASS: - return class_type == p_other.class_type; + return class_type == p_other.class_type || class_type->fqcn == p_other.class_type->fqcn; case RESOLVING: case UNRESOLVED: break; @@ -970,6 +970,7 @@ public: struct ReturnNode : public Node { ExpressionNode *return_value = nullptr; + bool void_return = false; ReturnNode() { type = RETURN; @@ -1401,7 +1402,7 @@ private: // Annotations AnnotationNode *parse_annotation(uint32_t p_valid_targets); bool register_annotation(const MethodInfo &p_info, uint32_t p_target_kinds, AnnotationAction p_apply, const Vector<Variant> &p_default_arguments = Vector<Variant>(), bool p_is_vararg = false); - bool validate_annotation_argument_count(AnnotationNode *p_annotation); + bool validate_annotation_arguments(AnnotationNode *p_annotation); void clear_unused_annotations(); bool tool_annotation(const AnnotationNode *p_annotation, Node *p_target); bool icon_annotation(const AnnotationNode *p_annotation, Node *p_target); diff --git a/modules/gdscript/gdscript_utility_functions.cpp b/modules/gdscript/gdscript_utility_functions.cpp index 10d83dcfe5..758b61bb31 100644 --- a/modules/gdscript/gdscript_utility_functions.cpp +++ b/modules/gdscript/gdscript_utility_functions.cpp @@ -112,28 +112,6 @@ struct GDScriptUtilityFunctionsDefinitions { *r_ret = String(result); } - static inline void str(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) { - if (p_arg_count < 1) { - r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; - r_error.argument = 1; - r_error.expected = 1; - *r_ret = Variant(); - return; - } - - String str; - for (int i = 0; i < p_arg_count; i++) { - String os = p_args[i]->operator String(); - - if (i == 0) { - str = os; - } else { - str += os; - } - } - *r_ret = str; - } - static inline void range(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) { switch (p_arg_count) { case 0: { @@ -651,7 +629,6 @@ void GDScriptUtilityFunctions::register_functions() { REGISTER_VARIANT_FUNC(convert, true, VARARG("what"), ARG("type", Variant::INT)); REGISTER_FUNC(type_exists, true, Variant::BOOL, ARG("type", Variant::STRING_NAME)); REGISTER_FUNC(_char, true, Variant::STRING, ARG("char", Variant::INT)); - REGISTER_VARARG_FUNC(str, true, Variant::STRING); REGISTER_VARARG_FUNC(range, false, Variant::ARRAY); REGISTER_CLASS_FUNC(load, false, "Resource", ARG("path", Variant::STRING)); REGISTER_FUNC(inst_to_dict, false, Variant::DICTIONARY, ARG("instance", Variant::OBJECT)); diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp index 4ea4438b5e..e18a4a6190 100644 --- a/modules/gdscript/gdscript_vm.cpp +++ b/modules/gdscript/gdscript_vm.cpp @@ -47,6 +47,16 @@ static String _get_script_name(const Ref<Script> p_script) { } } +static String _get_element_type(Variant::Type builtin_type, const StringName &native_type, const Ref<Script> &script_type) { + if (script_type.is_valid() && script_type->is_valid()) { + return _get_script_name(script_type); + } else if (native_type != StringName()) { + return native_type.operator String(); + } else { + return Variant::get_type_name(builtin_type); + } +} + static String _get_var_type(const Variant *p_var) { String basestr; @@ -75,15 +85,8 @@ static String _get_var_type(const Variant *p_var) { basestr = "Array"; const Array *p_array = VariantInternal::get_array(p_var); Variant::Type builtin_type = (Variant::Type)p_array->get_typed_builtin(); - StringName native_type = p_array->get_typed_class_name(); - Ref<Script> script_type = p_array->get_typed_script(); - - if (script_type.is_valid() && script_type->is_valid()) { - basestr += "[" + _get_script_name(script_type) + "]"; - } else if (native_type != StringName()) { - basestr += "[" + native_type.operator String() + "]"; - } else if (builtin_type != Variant::NIL) { - basestr += "[" + Variant::get_type_name(builtin_type) + "]"; + if (builtin_type != Variant::NIL) { + basestr += "[" + _get_element_type(builtin_type, p_array->get_typed_class_name(), p_array->get_typed_script()) + "]"; } } else { basestr = Variant::get_type_name(p_var->get_type()); @@ -101,10 +104,7 @@ Variant GDScriptFunction::_get_default_variant_for_data_type(const GDScriptDataT // Typed array. if (p_data_type.has_container_element_type()) { const GDScriptDataType &element_type = p_data_type.get_container_element_type(); - array.set_typed( - element_type.kind == GDScriptDataType::BUILTIN ? element_type.builtin_type : Variant::OBJECT, - element_type.native_type, - element_type.script_type); + array.set_typed(element_type.builtin_type, element_type.native_type, element_type.script_type); } return array; @@ -131,6 +131,8 @@ String GDScriptFunction::_get_call_error(const Callable::CallError &p_err, const #ifdef DEBUG_ENABLED if (p_err.expected == Variant::OBJECT && argptrs[errorarg]->get_type() == p_err.expected) { err_text = "Invalid type in " + p_where + ". The Object-derived class of argument " + itos(errorarg + 1) + " (" + _get_var_type(argptrs[errorarg]) + ") is not a subclass of the expected argument class."; + } else if (p_err.expected == Variant::ARRAY && argptrs[errorarg]->get_type() == p_err.expected) { + err_text = "Invalid type in " + p_where + ". The array of argument " + itos(errorarg + 1) + " (" + _get_var_type(argptrs[errorarg]) + ") does not have the same element type as the expected typed array argument."; } else #endif // DEBUG_ENABLED { @@ -518,7 +520,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a if (!argument_types[i].is_type(*p_args[i], true)) { r_err.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_err.argument = i; - r_err.expected = argument_types[i].kind == GDScriptDataType::BUILTIN ? argument_types[i].builtin_type : Variant::OBJECT; + r_err.expected = argument_types[i].builtin_type; return _get_default_variant_for_data_type(return_type); } if (argument_types[i].kind == GDScriptDataType::BUILTIN) { @@ -1174,27 +1176,37 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a DISPATCH_OPCODE; OPCODE(OPCODE_ASSIGN_TYPED_ARRAY) { - CHECK_SPACE(3); + CHECK_SPACE(6); GET_VARIANT_PTR(dst, 0); GET_VARIANT_PTR(src, 1); - Array *dst_arr = VariantInternal::get_array(dst); + GET_VARIANT_PTR(script_type, 2); + Variant::Type builtin_type = (Variant::Type)_code_ptr[ip + 4]; + int native_type_idx = _code_ptr[ip + 5]; + GD_ERR_BREAK(native_type_idx < 0 || native_type_idx >= _global_names_count); + const StringName native_type = _global_names_ptr[native_type_idx]; if (src->get_type() != Variant::ARRAY) { #ifdef DEBUG_ENABLED - err_text = "Trying to assign value of type '" + Variant::get_type_name(src->get_type()) + - "' to a variable of type '" + +"'."; -#endif + err_text = vformat(R"(Trying to assign a value of type "%s" to a variable of type "Array[%s]".)", + _get_var_type(src), _get_element_type(builtin_type, native_type, *script_type)); +#endif // DEBUG_ENABLED OPCODE_BREAK; } - if (!dst_arr->typed_assign(*src)) { + + Array *array = VariantInternal::get_array(src); + + if (array->get_typed_builtin() != ((uint32_t)builtin_type) || array->get_typed_class_name() != native_type || array->get_typed_script() != *script_type || array->get_typed_class_name() != native_type) { #ifdef DEBUG_ENABLED - err_text = "Trying to assign a typed array with an array of different type.'"; -#endif + err_text = vformat(R"(Trying to assign an array of type "%s" to a variable of type "Array[%s]".)", + _get_var_type(src), _get_element_type(builtin_type, native_type, *script_type)); +#endif // DEBUG_ENABLED OPCODE_BREAK; } - ip += 3; + *dst = *src; + + ip += 6; } DISPATCH_OPCODE; @@ -1469,9 +1481,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a const StringName native_type = _global_names_ptr[native_type_idx]; Array array; - array.set_typed(builtin_type, native_type, *script_type); array.resize(argc); - for (int i = 0; i < argc; i++) { array[i] = *(instruction_args[i]); } @@ -1479,7 +1489,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a GET_INSTRUCTION_ARG(dst, argc); *dst = Variant(); // Clear potential previous typed array. - *dst = array; + *dst = Array(array, builtin_type, native_type, *script_type); ip += 4; } @@ -2486,30 +2496,25 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a if (r->get_type() != Variant::ARRAY) { #ifdef DEBUG_ENABLED - err_text = vformat(R"(Trying to return value of type "%s" from a function which the return type is "Array[%s]".)", - Variant::get_type_name(r->get_type()), Variant::get_type_name(builtin_type)); -#endif + err_text = vformat(R"(Trying to return a value of type "%s" where expected return type is "Array[%s]".)", + _get_var_type(r), _get_element_type(builtin_type, native_type, *script_type)); +#endif // DEBUG_ENABLED OPCODE_BREAK; } - Array array; - array.set_typed(builtin_type, native_type, *script_type); + Array *array = VariantInternal::get_array(r); + if (array->get_typed_builtin() != ((uint32_t)builtin_type) || array->get_typed_class_name() != native_type || array->get_typed_script() != *script_type || array->get_typed_class_name() != native_type) { #ifdef DEBUG_ENABLED - bool valid = array.typed_assign(*VariantInternal::get_array(r)); -#else - array.typed_assign(*VariantInternal::get_array(r)); + err_text = vformat(R"(Trying to return an array of type "%s" where expected return type is "Array[%s]".)", + _get_var_type(r), _get_element_type(builtin_type, native_type, *script_type)); #endif // DEBUG_ENABLED - - // Assign the return value anyway since we want it to be the valid type. - retvalue = array; - -#ifdef DEBUG_ENABLED - if (!valid) { - err_text = "Trying to return a typed array with an array of different type.'"; OPCODE_BREAK; } + retvalue = *array; + +#ifdef DEBUG_ENABLED exit_ok = true; #endif // DEBUG_ENABLED OPCODE_BREAK; diff --git a/modules/gdscript/gdscript_warning.cpp b/modules/gdscript/gdscript_warning.cpp index 024fed8517..9436146bed 100644 --- a/modules/gdscript/gdscript_warning.cpp +++ b/modules/gdscript/gdscript_warning.cpp @@ -125,6 +125,10 @@ String GDScriptWarning::get_message() const { CHECK_SYMBOLS(4); return "The argument '" + symbols[0] + "' of the function '" + symbols[1] + "' requires a the subtype '" + symbols[2] + "' but the supertype '" + symbols[3] + "' was provided"; } break; + case UNSAFE_VOID_RETURN: { + CHECK_SYMBOLS(2); + return "The method '" + symbols[0] + "()' returns 'void' but it's trying to return a call to '" + symbols[1] + "()' that can't be ensured to also be 'void'."; + } break; case DEPRECATED_KEYWORD: { CHECK_SYMBOLS(2); return "The '" + symbols[0] + "' keyword is deprecated and will be removed in a future release, please replace its uses by '" + symbols[1] + "'."; @@ -163,6 +167,9 @@ String GDScriptWarning::get_message() const { CHECK_SYMBOLS(1); return vformat(R"(The identifier "%s" has misleading characters and might be confused with something else.)", symbols[0]); } + case RENAMED_IN_GD4_HINT: { + break; // Renamed identifier hint is taken care of by the GDScriptAnalyzer. No message needed here. + } case WARNING_MAX: break; // Can't happen, but silences warning } @@ -184,6 +191,9 @@ int GDScriptWarning::get_default_value(Code p_code) { PropertyInfo GDScriptWarning::get_property_info(Code p_code) { // Making this a separate function in case a warning needs different PropertyInfo in the future. + if (p_code == Code::RENAMED_IN_GD4_HINT) { + return PropertyInfo(Variant::BOOL, get_settings_path_from_code(p_code)); + } return PropertyInfo(Variant::INT, get_settings_path_from_code(p_code), PROPERTY_HINT_ENUM, "Ignore,Warn,Error"); } @@ -218,6 +228,7 @@ String GDScriptWarning::get_name_from_code(Code p_code) { "UNSAFE_METHOD_ACCESS", "UNSAFE_CAST", "UNSAFE_CALL_ARGUMENT", + "UNSAFE_VOID_RETURN", "DEPRECATED_KEYWORD", "STANDALONE_TERNARY", "ASSERT_ALWAYS_TRUE", @@ -229,6 +240,7 @@ String GDScriptWarning::get_name_from_code(Code p_code) { "INT_AS_ENUM_WITHOUT_MATCH", "STATIC_CALLED_ON_INSTANCE", "CONFUSABLE_IDENTIFIER", + "RENAMED_IN_GODOT_4_HINT" }; static_assert((sizeof(names) / sizeof(*names)) == WARNING_MAX, "Amount of warning types don't match the amount of warning names."); diff --git a/modules/gdscript/gdscript_warning.h b/modules/gdscript/gdscript_warning.h index 7492972c1a..fa2907cdae 100644 --- a/modules/gdscript/gdscript_warning.h +++ b/modules/gdscript/gdscript_warning.h @@ -69,6 +69,7 @@ public: UNSAFE_METHOD_ACCESS, // Function not found in the detected type (but can be in subtypes). UNSAFE_CAST, // Cast used in an unknown type. UNSAFE_CALL_ARGUMENT, // Function call argument is of a supertype of the require argument. + UNSAFE_VOID_RETURN, // Function returns void but returned a call to a function that can't be type checked. DEPRECATED_KEYWORD, // The keyword is deprecated and should be replaced. STANDALONE_TERNARY, // Return value of ternary expression is discarded. ASSERT_ALWAYS_TRUE, // Expression for assert argument is always true. @@ -80,6 +81,7 @@ public: INT_AS_ENUM_WITHOUT_MATCH, // An integer value was used as an enum value without matching enum member. STATIC_CALLED_ON_INSTANCE, // A static method was called on an instance of a class instead of on the class itself. CONFUSABLE_IDENTIFIER, // The identifier contains misleading characters that can be confused. E.g. "usеr" (has Cyrillic "е" instead of Latin "e"). + RENAMED_IN_GD4_HINT, // A variable or function that could not be found has been renamed in Godot 4 WARNING_MAX, }; diff --git a/modules/gdscript/tests/gdscript_test_runner.cpp b/modules/gdscript/tests/gdscript_test_runner.cpp index d2c8b5c317..5b8af0ff34 100644 --- a/modules/gdscript/tests/gdscript_test_runner.cpp +++ b/modules/gdscript/tests/gdscript_test_runner.cpp @@ -132,9 +132,10 @@ void finish_language() { StringName GDScriptTestRunner::test_function_name; -GDScriptTestRunner::GDScriptTestRunner(const String &p_source_dir, bool p_init_language) { +GDScriptTestRunner::GDScriptTestRunner(const String &p_source_dir, bool p_init_language, bool p_print_filenames) { test_function_name = StaticCString::create("test"); do_init_languages = p_init_language; + print_filenames = p_print_filenames; source_dir = p_source_dir; if (!source_dir.ends_with("/")) { @@ -194,6 +195,9 @@ int GDScriptTestRunner::run_tests() { int failed = 0; for (int i = 0; i < tests.size(); i++) { GDScriptTest test = tests[i]; + if (print_filenames) { + print_line(test.get_source_relative_filepath()); + } GDScriptTest::TestResult result = test.run_test(); String expected = FileAccess::get_file_as_string(test.get_output_file()); @@ -225,8 +229,13 @@ bool GDScriptTestRunner::generate_outputs() { } for (int i = 0; i < tests.size(); i++) { - OS::get_singleton()->print("."); GDScriptTest test = tests[i]; + if (print_filenames) { + print_line(test.get_source_relative_filepath()); + } else { + OS::get_singleton()->print("."); + } + bool result = test.generate_output(); if (!result) { @@ -337,15 +346,10 @@ GDScriptTest::GDScriptTest(const String &p_source_path, const String &p_output_p void GDScriptTestRunner::handle_cmdline() { List<String> cmdline_args = OS::get_singleton()->get_cmdline_args(); - // TODO: this could likely be ported to use test commands: - // https://github.com/godotengine/godot/pull/41355 - // Currently requires to startup the whole engine, which is slow. - String test_cmd = "--gdscript-test"; - String gen_cmd = "--gdscript-generate-tests"; for (List<String>::Element *E = cmdline_args.front(); E; E = E->next()) { String &cmd = E->get(); - if (cmd == test_cmd || cmd == gen_cmd) { + if (cmd == "--gdscript-generate-tests") { if (E->next() == nullptr) { ERR_PRINT("Needed a path for the test files."); exit(-1); @@ -353,14 +357,10 @@ void GDScriptTestRunner::handle_cmdline() { const String &path = E->next()->get(); - GDScriptTestRunner runner(path, false); - int failed = 0; - if (cmd == test_cmd) { - failed = runner.run_tests(); - } else { - bool completed = runner.generate_outputs(); - failed = completed ? 0 : -1; - } + GDScriptTestRunner runner(path, false, cmdline_args.find("--print-filenames") != nullptr); + + bool completed = runner.generate_outputs(); + int failed = completed ? 0 : -1; exit(failed); } } diff --git a/modules/gdscript/tests/gdscript_test_runner.h b/modules/gdscript/tests/gdscript_test_runner.h index b097f1b485..60b48c6a57 100644 --- a/modules/gdscript/tests/gdscript_test_runner.h +++ b/modules/gdscript/tests/gdscript_test_runner.h @@ -92,6 +92,7 @@ public: bool generate_output(); const String &get_source_file() const { return source_file; } + const String get_source_relative_filepath() const { return source_file.trim_prefix(base_dir); } const String &get_output_file() const { return output_file; } GDScriptTest(const String &p_source_path, const String &p_output_path, const String &p_base_dir); @@ -105,6 +106,7 @@ class GDScriptTestRunner { bool is_generating = false; bool do_init_languages = false; + bool print_filenames; // Whether filenames should be printed when generated/running tests bool make_tests(); bool make_tests_for_dir(const String &p_dir); @@ -117,7 +119,7 @@ public: int run_tests(); bool generate_outputs(); - GDScriptTestRunner(const String &p_source_dir, bool p_init_language); + GDScriptTestRunner(const String &p_source_dir, bool p_init_language, bool p_print_filenames = false); ~GDScriptTestRunner(); }; diff --git a/modules/gdscript/tests/gdscript_test_runner_suite.h b/modules/gdscript/tests/gdscript_test_runner_suite.h index aed0ac2baf..e27b6218f1 100644 --- a/modules/gdscript/tests/gdscript_test_runner_suite.h +++ b/modules/gdscript/tests/gdscript_test_runner_suite.h @@ -41,7 +41,8 @@ TEST_SUITE("[Modules][GDScript]") { // Allow the tests to fail, but do not ignore errors during development. // Update the scripts and expected output as needed. TEST_CASE("Script compilation and runtime") { - GDScriptTestRunner runner("modules/gdscript/tests/scripts", true); + bool print_filenames = OS::get_singleton()->get_cmdline_args().find("--print-filenames") != nullptr; + GDScriptTestRunner runner("modules/gdscript/tests/scripts", true, print_filenames); int fail_count = runner.run_tests(); INFO("Make sure `*.out` files have expected results."); REQUIRE_MESSAGE(fail_count == 0, "All GDScript tests should pass."); diff --git a/modules/gdscript/tests/scripts/analyzer/errors/invalid_array_index.out b/modules/gdscript/tests/scripts/analyzer/errors/invalid_array_index.out index 6f7f0783f0..015ad756f8 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/invalid_array_index.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/invalid_array_index.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -Cannot get index "true" from "[0, 1]". +Invalid index type "bool" for a base of type "Array". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/typed_array_assign_differently_typed.gd b/modules/gdscript/tests/scripts/analyzer/errors/typed_array_assign_differently_typed.gd new file mode 100644 index 0000000000..ce50cccb3c --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/typed_array_assign_differently_typed.gd @@ -0,0 +1,4 @@ +func test(): + var differently: Array[float] = [1.0] + var typed: Array[int] = differently + print('not ok') diff --git a/modules/gdscript/tests/scripts/analyzer/errors/typed_array_assign_differently_typed.out b/modules/gdscript/tests/scripts/analyzer/errors/typed_array_assign_differently_typed.out new file mode 100644 index 0000000000..c6d39781ee --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/typed_array_assign_differently_typed.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot assign a value of type Array[float] to variable "typed" with specified type Array[int]. diff --git a/modules/gdscript/tests/scripts/analyzer/typed_array_assignment.gd b/modules/gdscript/tests/scripts/analyzer/errors/typed_array_assignment.gd index 9f86d0531c..9f86d0531c 100644 --- a/modules/gdscript/tests/scripts/analyzer/typed_array_assignment.gd +++ b/modules/gdscript/tests/scripts/analyzer/errors/typed_array_assignment.gd diff --git a/modules/gdscript/tests/scripts/analyzer/errors/typed_array_assignment.out b/modules/gdscript/tests/scripts/analyzer/errors/typed_array_assignment.out new file mode 100644 index 0000000000..8530783673 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/typed_array_assignment.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot include a value of type "String" as "int". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/typed_array_init_with_unconvertable_in_literal.gd b/modules/gdscript/tests/scripts/analyzer/errors/typed_array_init_with_unconvertable_in_literal.gd new file mode 100644 index 0000000000..25cde1d40b --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/typed_array_init_with_unconvertable_in_literal.gd @@ -0,0 +1,4 @@ +func test(): + var unconvertable := 1 + var typed: Array[Object] = [unconvertable] + print('not ok') diff --git a/modules/gdscript/tests/scripts/analyzer/errors/typed_array_init_with_unconvertable_in_literal.out b/modules/gdscript/tests/scripts/analyzer/errors/typed_array_init_with_unconvertable_in_literal.out new file mode 100644 index 0000000000..dfe3443761 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/typed_array_init_with_unconvertable_in_literal.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot have an element of type "int" in an array of type "Array[Object]". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/typed_array_pass_differently_to_typed.gd b/modules/gdscript/tests/scripts/analyzer/errors/typed_array_pass_differently_to_typed.gd new file mode 100644 index 0000000000..1a90bd121e --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/typed_array_pass_differently_to_typed.gd @@ -0,0 +1,7 @@ +func expect_typed(typed: Array[int]): + print(typed.size()) + +func test(): + var differently: Array[float] = [1.0] + expect_typed(differently) + print('not ok') diff --git a/modules/gdscript/tests/scripts/analyzer/errors/typed_array_pass_differently_to_typed.out b/modules/gdscript/tests/scripts/analyzer/errors/typed_array_pass_differently_to_typed.out new file mode 100644 index 0000000000..297e1283e8 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/typed_array_pass_differently_to_typed.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Invalid argument for "expect_typed()" function: argument 1 should be "Array[int]" but is "Array[float]". diff --git a/modules/gdscript/tests/scripts/analyzer/features/allow_void_function_to_return_void.gd b/modules/gdscript/tests/scripts/analyzer/features/allow_void_function_to_return_void.gd new file mode 100644 index 0000000000..df89137f40 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/allow_void_function_to_return_void.gd @@ -0,0 +1,20 @@ +func test(): + return_call() + return_nothing() + return_side_effect() + var r = return_side_effect.call() # Untyped call to check return value. + prints(r, typeof(r) == TYPE_NIL) + print("end") + +func side_effect(v): + print("effect") + return v + +func return_call() -> void: + return print("hello") + +func return_nothing() -> void: + return + +func return_side_effect() -> void: + return side_effect("x") diff --git a/modules/gdscript/tests/scripts/analyzer/features/allow_void_function_to_return_void.out b/modules/gdscript/tests/scripts/analyzer/features/allow_void_function_to_return_void.out new file mode 100644 index 0000000000..7c0416371f --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/allow_void_function_to_return_void.out @@ -0,0 +1,10 @@ +GDTEST_OK +>> WARNING +>> Line: 20 +>> UNSAFE_VOID_RETURN +>> The method 'return_side_effect()' returns 'void' but it's trying to return a call to 'side_effect()' that can't be ensured to also be 'void'. +hello +effect +effect +<null> true +end diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_array_as_default_parameter.out b/modules/gdscript/tests/scripts/analyzer/features/typed_array_as_default_parameter.out index 082e3ade19..2729c5b6c7 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/typed_array_as_default_parameter.out +++ b/modules/gdscript/tests/scripts/analyzer/features/typed_array_as_default_parameter.out @@ -2,7 +2,7 @@ GDTEST_OK [0] 0 [1] -2 +0 [2] 2 ok diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd b/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd new file mode 100644 index 0000000000..e1e6134fd4 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd @@ -0,0 +1,204 @@ +class A: pass +class B extends A: pass + +enum E { E0 = 391 } + +func floats_identity(floats: Array[float]): return floats + +class Members: + var one: Array[int] = [104] + var two: Array[int] = one + + func check_passing() -> bool: + assert(str(one) == '[104]') + assert(str(two) == '[104]') + two.push_back(582) + assert(str(one) == '[104, 582]') + assert(str(two) == '[104, 582]') + two = [486] + assert(str(one) == '[104, 582]') + assert(str(two) == '[486]') + return true + + +@warning_ignore("unsafe_method_access") +@warning_ignore("assert_always_true") +@warning_ignore("return_value_discarded") +func test(): + var untyped_basic = [459] + assert(str(untyped_basic) == '[459]') + assert(untyped_basic.get_typed_builtin() == TYPE_NIL) + + var inferred_basic := [366] + assert(str(inferred_basic) == '[366]') + assert(inferred_basic.get_typed_builtin() == TYPE_NIL) + + var typed_basic: Array = [521] + assert(str(typed_basic) == '[521]') + assert(typed_basic.get_typed_builtin() == TYPE_NIL) + + + var empty_floats: Array[float] = [] + assert(str(empty_floats) == '[]') + assert(empty_floats.get_typed_builtin() == TYPE_FLOAT) + + untyped_basic = empty_floats + assert(untyped_basic.get_typed_builtin() == TYPE_FLOAT) + + inferred_basic = empty_floats + assert(inferred_basic.get_typed_builtin() == TYPE_FLOAT) + + typed_basic = empty_floats + assert(typed_basic.get_typed_builtin() == TYPE_FLOAT) + + empty_floats.push_back(705.0) + untyped_basic.push_back(430.0) + inferred_basic.push_back(263.0) + typed_basic.push_back(518.0) + assert(str(empty_floats) == '[705, 430, 263, 518]') + assert(str(untyped_basic) == '[705, 430, 263, 518]') + assert(str(inferred_basic) == '[705, 430, 263, 518]') + assert(str(typed_basic) == '[705, 430, 263, 518]') + + + const constant_float := 950.0 + const constant_int := 170 + var typed_float := 954.0 + var filled_floats: Array[float] = [constant_float, constant_int, typed_float, empty_floats[1] + empty_floats[2]] + assert(str(filled_floats) == '[950, 170, 954, 693]') + assert(filled_floats.get_typed_builtin() == TYPE_FLOAT) + + var casted_floats := [empty_floats[2] * 2] as Array[float] + assert(str(casted_floats) == '[526]') + assert(casted_floats.get_typed_builtin() == TYPE_FLOAT) + + var returned_floats = (func () -> Array[float]: return [554]).call() + assert(str(returned_floats) == '[554]') + assert(returned_floats.get_typed_builtin() == TYPE_FLOAT) + + var passed_floats = floats_identity([663.0 if randf() > 0.5 else 663.0]) + assert(str(passed_floats) == '[663]') + assert(passed_floats.get_typed_builtin() == TYPE_FLOAT) + + var default_floats = (func (floats: Array[float] = [364.0]): return floats).call() + assert(str(default_floats) == '[364]') + assert(default_floats.get_typed_builtin() == TYPE_FLOAT) + + var typed_int := 556 + var converted_floats: Array[float] = [typed_int] + assert(str(converted_floats) == '[556]') + assert(converted_floats.get_typed_builtin() == TYPE_FLOAT) + + + const constant_basic = [228] + assert(str(constant_basic) == '[228]') + assert(constant_basic.get_typed_builtin() == TYPE_NIL) + + const constant_floats: Array[float] = [constant_float - constant_basic[0] - constant_int] + assert(str(constant_floats) == '[552]') + assert(constant_floats.get_typed_builtin() == TYPE_FLOAT) + + + var source_floats: Array[float] = [999.74] + untyped_basic = source_floats + var destination_floats: Array[float] = untyped_basic + destination_floats[0] -= 0.74 + assert(str(source_floats) == '[999]') + assert(str(untyped_basic) == '[999]') + assert(str(destination_floats) == '[999]') + assert(destination_floats.get_typed_builtin() == TYPE_FLOAT) + + + var duplicated_floats := empty_floats.duplicate().slice(2, 3) + duplicated_floats[0] *= 3 + assert(str(duplicated_floats) == '[789]') + assert(duplicated_floats.get_typed_builtin() == TYPE_FLOAT) + + + var b_objects: Array[B] = [B.new(), null] + assert(b_objects.size() == 2) + assert(b_objects.get_typed_builtin() == TYPE_OBJECT) + assert(b_objects.get_typed_script() == B) + + var a_objects: Array[A] = [A.new(), B.new(), null, b_objects[0]] + assert(a_objects.size() == 4) + assert(a_objects.get_typed_builtin() == TYPE_OBJECT) + assert(a_objects.get_typed_script() == A) + + var a_passed = (func check_a_passing(a_objects: Array[A]): return a_objects.size()).call(a_objects) + assert(a_passed == 4) + + var b_passed = (func check_b_passing(basic: Array): return basic[0] != null).call(b_objects) + assert(b_passed == true) + + + var empty_strings: Array[String] = [] + var empty_bools: Array[bool] = [] + var empty_basic_one := [] + var empty_basic_two := [] + assert(empty_strings == empty_bools) + assert(empty_basic_one == empty_basic_two) + assert(empty_strings.hash() == empty_bools.hash()) + assert(empty_basic_one.hash() == empty_basic_two.hash()) + + + var assign_source: Array[int] = [527] + var assign_target: Array[int] = [] + assign_target.assign(assign_source) + assert(str(assign_source) == '[527]') + assert(str(assign_target) == '[527]') + assign_source.push_back(657) + assert(str(assign_source) == '[527, 657]') + assert(str(assign_target) == '[527]') + + + var defaults_passed = (func check_defaults_passing(one: Array[int] = [], two := one): + one.push_back(887) + two.push_back(198) + assert(str(one) == '[887, 198]') + assert(str(two) == '[887, 198]') + two = [130] + assert(str(one) == '[887, 198]') + assert(str(two) == '[130]') + return true + ).call() + assert(defaults_passed == true) + + + var members := Members.new() + var members_passed := members.check_passing() + assert(members_passed == true) + + + var resized_basic: Array = [] + resized_basic.resize(1) + assert(typeof(resized_basic[0]) == TYPE_NIL) + assert(resized_basic[0] == null) + + var resized_ints: Array[int] = [] + resized_ints.resize(1) + assert(typeof(resized_ints[0]) == TYPE_INT) + assert(resized_ints[0] == 0) + + var resized_arrays: Array[Array] = [] + resized_arrays.resize(1) + assert(typeof(resized_arrays[0]) == TYPE_ARRAY) + resized_arrays[0].resize(1) + resized_arrays[0][0] = 523 + assert(str(resized_arrays) == '[[523]]') + + var resized_objects: Array[Object] = [] + resized_objects.resize(1) + assert(typeof(resized_objects[0]) == TYPE_NIL) + assert(resized_objects[0] == null) + + + var typed_enums: Array[E] = [] + typed_enums.resize(1) + assert(str(typed_enums) == '[0]') + typed_enums[0] = E.E0 + assert(str(typed_enums) == '[391]') + assert(typed_enums.get_typed_builtin() == TYPE_INT) + + + print('ok') diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.out b/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.out new file mode 100644 index 0000000000..1b47ed10dc --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.out @@ -0,0 +1,2 @@ +GDTEST_OK +ok diff --git a/modules/gdscript/tests/scripts/analyzer/typed_array_assignment.out b/modules/gdscript/tests/scripts/analyzer/typed_array_assignment.out deleted file mode 100644 index ad2e6558d7..0000000000 --- a/modules/gdscript/tests/scripts/analyzer/typed_array_assignment.out +++ /dev/null @@ -1,2 +0,0 @@ -GDTEST_ANALYZER_ERROR -Cannot assign a value of type Array[String] to constant "arr" with specified type Array[int]. diff --git a/modules/gdscript/tests/scripts/parser/errors/class_name_after_annotation.gd b/modules/gdscript/tests/scripts/parser/errors/class_name_after_annotation.gd index 0085b3f367..2470fe978e 100644 --- a/modules/gdscript/tests/scripts/parser/errors/class_name_after_annotation.gd +++ b/modules/gdscript/tests/scripts/parser/errors/class_name_after_annotation.gd @@ -1,5 +1,5 @@ # Error here. Annotations should be used before `class_name`, not after. -class_name HelloWorld +class_name WrongAnnotationPlace @icon("res://path/to/optional/icon.svg") func test(): diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_array_assign_basic_to_typed.gd b/modules/gdscript/tests/scripts/runtime/errors/typed_array_assign_basic_to_typed.gd new file mode 100644 index 0000000000..e9dbc1b640 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/typed_array_assign_basic_to_typed.gd @@ -0,0 +1,4 @@ +func test(): + var basic := [1] + var typed: Array[int] = basic + print('not ok') diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_array_assign_basic_to_typed.out b/modules/gdscript/tests/scripts/runtime/errors/typed_array_assign_basic_to_typed.out new file mode 100644 index 0000000000..bca700b4ec --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/typed_array_assign_basic_to_typed.out @@ -0,0 +1,6 @@ +GDTEST_RUNTIME_ERROR +>> SCRIPT ERROR +>> on function: test() +>> runtime/errors/typed_array_assign_basic_to_typed.gd +>> 3 +>> Trying to assign an array of type "Array" to a variable of type "Array[int]". diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_array_assign_differently_typed.gd b/modules/gdscript/tests/scripts/runtime/errors/typed_array_assign_differently_typed.gd new file mode 100644 index 0000000000..920352a6ea --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/typed_array_assign_differently_typed.gd @@ -0,0 +1,4 @@ +func test(): + var differently: Variant = [1.0] as Array[float] + var typed: Array[int] = differently + print('not ok') diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_array_assign_differently_typed.out b/modules/gdscript/tests/scripts/runtime/errors/typed_array_assign_differently_typed.out new file mode 100644 index 0000000000..402ab38fb3 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/typed_array_assign_differently_typed.out @@ -0,0 +1,6 @@ +GDTEST_RUNTIME_ERROR +>> SCRIPT ERROR +>> on function: test() +>> runtime/errors/typed_array_assign_differently_typed.gd +>> 3 +>> Trying to assign an array of type "Array[float]" to a variable of type "Array[int]". diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_array_pass_basic_to_typed.gd b/modules/gdscript/tests/scripts/runtime/errors/typed_array_pass_basic_to_typed.gd new file mode 100644 index 0000000000..e1fd0f7168 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/typed_array_pass_basic_to_typed.gd @@ -0,0 +1,7 @@ +func expect_typed(typed: Array[int]): + print(typed.size()) + +func test(): + var basic := [1] + expect_typed(basic) + print('not ok') diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_array_pass_basic_to_typed.out b/modules/gdscript/tests/scripts/runtime/errors/typed_array_pass_basic_to_typed.out new file mode 100644 index 0000000000..6f210e944e --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/typed_array_pass_basic_to_typed.out @@ -0,0 +1,6 @@ +GDTEST_RUNTIME_ERROR +>> SCRIPT ERROR +>> on function: test() +>> runtime/errors/typed_array_pass_basic_to_typed.gd +>> 6 +>> Invalid type in function 'expect_typed' in base 'RefCounted ()'. The array of argument 1 (Array) does not have the same element type as the expected typed array argument. diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_array_pass_differently_to_typed.gd b/modules/gdscript/tests/scripts/runtime/errors/typed_array_pass_differently_to_typed.gd new file mode 100644 index 0000000000..e2d2721e8c --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/typed_array_pass_differently_to_typed.gd @@ -0,0 +1,7 @@ +func expect_typed(typed: Array[int]): + print(typed.size()) + +func test(): + var differently: Variant = [1.0] as Array[float] + expect_typed(differently) + print('not ok') diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_array_pass_differently_to_typed.out b/modules/gdscript/tests/scripts/runtime/errors/typed_array_pass_differently_to_typed.out new file mode 100644 index 0000000000..3cd4e25bd8 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/typed_array_pass_differently_to_typed.out @@ -0,0 +1,6 @@ +GDTEST_RUNTIME_ERROR +>> SCRIPT ERROR +>> on function: test() +>> runtime/errors/typed_array_pass_differently_to_typed.gd +>> 6 +>> Invalid type in function 'expect_typed' in base 'RefCounted ()'. The array of argument 1 (Array[float]) does not have the same element type as the expected typed array argument. diff --git a/modules/gdscript/tests/scripts/runtime/features/typed_array_init_with_untyped_in_literal.gd b/modules/gdscript/tests/scripts/runtime/features/typed_array_init_with_untyped_in_literal.gd new file mode 100644 index 0000000000..ec444b4ffa --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/typed_array_init_with_untyped_in_literal.gd @@ -0,0 +1,6 @@ +func test(): + var untyped: Variant = 32 + var typed: Array[int] = [untyped] + assert(typed.get_typed_builtin() == TYPE_INT) + assert(str(typed) == '[32]') + print('ok') diff --git a/modules/gdscript/tests/scripts/runtime/features/typed_array_init_with_untyped_in_literal.out b/modules/gdscript/tests/scripts/runtime/features/typed_array_init_with_untyped_in_literal.out new file mode 100644 index 0000000000..1b47ed10dc --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/typed_array_init_with_untyped_in_literal.out @@ -0,0 +1,2 @@ +GDTEST_OK +ok diff --git a/modules/gltf/doc_classes/GLTFDocumentExtension.xml b/modules/gltf/doc_classes/GLTFDocumentExtension.xml index 6004de32f1..6e8340f618 100644 --- a/modules/gltf/doc_classes/GLTFDocumentExtension.xml +++ b/modules/gltf/doc_classes/GLTFDocumentExtension.xml @@ -22,7 +22,7 @@ </description> </method> <method name="_export_node" qualifiers="virtual"> - <return type="int" /> + <return type="int" enum="Error" /> <param index="0" name="state" type="GLTFState" /> <param index="1" name="gltf_node" type="GLTFNode" /> <param index="2" name="json" type="Dictionary" /> @@ -33,7 +33,7 @@ </description> </method> <method name="_export_post" qualifiers="virtual"> - <return type="int" /> + <return type="int" enum="Error" /> <param index="0" name="state" type="GLTFState" /> <description> Part of the export process. This method is run last, after all other parts of the export process. @@ -41,7 +41,7 @@ </description> </method> <method name="_export_preflight" qualifiers="virtual"> - <return type="int" /> + <return type="int" enum="Error" /> <param index="0" name="state" type="GLTFState" /> <param index="1" name="root" type="Node" /> <description> @@ -67,7 +67,7 @@ </description> </method> <method name="_import_node" qualifiers="virtual"> - <return type="int" /> + <return type="int" enum="Error" /> <param index="0" name="state" type="GLTFState" /> <param index="1" name="gltf_node" type="GLTFNode" /> <param index="2" name="json" type="Dictionary" /> @@ -78,7 +78,7 @@ </description> </method> <method name="_import_post" qualifiers="virtual"> - <return type="int" /> + <return type="int" enum="Error" /> <param index="0" name="state" type="GLTFState" /> <param index="1" name="root" type="Node" /> <description> @@ -87,7 +87,7 @@ </description> </method> <method name="_import_post_parse" qualifiers="virtual"> - <return type="int" /> + <return type="int" enum="Error" /> <param index="0" name="state" type="GLTFState" /> <description> Part of the import process. This method is run after [method _generate_scene_node] and before [method _import_node]. @@ -95,7 +95,7 @@ </description> </method> <method name="_import_preflight" qualifiers="virtual"> - <return type="int" /> + <return type="int" enum="Error" /> <param index="0" name="state" type="GLTFState" /> <param index="1" name="extensions" type="PackedStringArray" /> <description> @@ -104,7 +104,7 @@ </description> </method> <method name="_parse_node_extensions" qualifiers="virtual"> - <return type="int" /> + <return type="int" enum="Error" /> <param index="0" name="state" type="GLTFState" /> <param index="1" name="gltf_node" type="GLTFNode" /> <param index="2" name="extensions" type="Dictionary" /> diff --git a/modules/gltf/doc_classes/GLTFState.xml b/modules/gltf/doc_classes/GLTFState.xml index b8943795a0..b322c07cec 100644 --- a/modules/gltf/doc_classes/GLTFState.xml +++ b/modules/gltf/doc_classes/GLTFState.xml @@ -264,10 +264,16 @@ </members> <constants> <constant name="HANDLE_BINARY_DISCARD_TEXTURES" value="0"> + Discards all embedded textures and uses untextured materials. </constant> <constant name="HANDLE_BINARY_EXTRACT_TEXTURES" value="1"> + Extracts embedded textures to be reimported and compressed. Editor only. Acts as uncompressed at runtime. </constant> <constant name="HANDLE_BINARY_EMBED_AS_BASISU" value="2"> + Embeds textures VRAM compressed with Basis Universal into the generated scene. + </constant> + <constant name="HANDLE_BINARY_EMBED_AS_UNCOMPRESSED" value="3"> + Embeds textures compressed losslessly into the generated scene, matching old behavior. </constant> </constants> </class> diff --git a/modules/gltf/editor/editor_import_blend_runner.cpp b/modules/gltf/editor/editor_import_blend_runner.cpp new file mode 100644 index 0000000000..c203a91834 --- /dev/null +++ b/modules/gltf/editor/editor_import_blend_runner.cpp @@ -0,0 +1,314 @@ +/**************************************************************************/ +/* editor_import_blend_runner.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "editor_import_blend_runner.h" + +#ifdef TOOLS_ENABLED + +#include "core/io/http_client.h" +#include "editor/editor_file_system.h" +#include "editor/editor_node.h" +#include "editor/editor_settings.h" + +static constexpr char PYTHON_SCRIPT_RPC[] = R"( +import bpy, sys, threading +from xmlrpc.server import SimpleXMLRPCServer +req = threading.Condition() +res = threading.Condition() +info = None +def xmlrpc_server(): + server = SimpleXMLRPCServer(('127.0.0.1', %d)) + server.register_function(export_gltf) + server.serve_forever() +def export_gltf(opts): + with req: + global info + info = ('export_gltf', opts) + req.notify() + with res: + res.wait() +if bpy.app.version < (3, 0, 0): + print('Blender 3.0 or higher is required.', file=sys.stderr) +threading.Thread(target=xmlrpc_server).start() +while True: + with req: + while info is None: + req.wait() + method, opts = info + if method == 'export_gltf': + try: + bpy.ops.wm.open_mainfile(filepath=opts['path']) + if opts['unpack_all']: + bpy.ops.file.unpack_all(method='USE_LOCAL') + bpy.ops.export_scene.gltf(**opts['gltf_options']) + except: + pass + info = None + with res: + res.notify() +)"; + +static constexpr char PYTHON_SCRIPT_DIRECT[] = R"( +import bpy, sys +opts = %s +if bpy.app.version < (3, 0, 0): + print('Blender 3.0 or higher is required.', file=sys.stderr) +bpy.ops.wm.open_mainfile(filepath=opts['path']) +if opts['unpack_all']: + bpy.ops.file.unpack_all(method='USE_LOCAL') +bpy.ops.export_scene.gltf(**opts['gltf_options']) +)"; + +String dict_to_python(const Dictionary &p_dict) { + String entries; + Array dict_keys = p_dict.keys(); + for (int i = 0; i < dict_keys.size(); i++) { + const String key = dict_keys[i]; + String value; + Variant raw_value = p_dict[key]; + + switch (raw_value.get_type()) { + case Variant::Type::BOOL: { + value = raw_value ? "True" : "False"; + break; + } + case Variant::Type::STRING: + case Variant::Type::STRING_NAME: { + value = raw_value; + value = vformat("'%s'", value.c_escape()); + break; + } + case Variant::Type::DICTIONARY: { + value = dict_to_python(raw_value); + break; + } + default: { + ERR_FAIL_V_MSG("", vformat("Unhandled Variant type %s for python dictionary", Variant::get_type_name(raw_value.get_type()))); + } + } + + entries += vformat("'%s': %s,", key, value); + } + return vformat("{%s}", entries); +} + +String dict_to_xmlrpc(const Dictionary &p_dict) { + String members; + Array dict_keys = p_dict.keys(); + for (int i = 0; i < dict_keys.size(); i++) { + const String key = dict_keys[i]; + String value; + Variant raw_value = p_dict[key]; + + switch (raw_value.get_type()) { + case Variant::Type::BOOL: { + value = vformat("<boolean>%d</boolean>", raw_value ? 1 : 0); + break; + } + case Variant::Type::STRING: + case Variant::Type::STRING_NAME: { + value = raw_value; + value = vformat("<string>%s</string>", value.xml_escape()); + break; + } + case Variant::Type::DICTIONARY: { + value = dict_to_xmlrpc(raw_value); + break; + } + default: { + ERR_FAIL_V_MSG("", vformat("Unhandled Variant type %s for XMLRPC", Variant::get_type_name(raw_value.get_type()))); + } + } + + members += vformat("<member><name>%s</name><value>%s</value></member>", key, value); + } + return vformat("<struct>%s</struct>", members); +} + +Error EditorImportBlendRunner::start_blender(const String &p_python_script, bool p_blocking) { + String blender_path = EDITOR_GET("filesystem/import/blender/blender3_path"); + +#ifdef WINDOWS_ENABLED + blender_path = blender_path.path_join("blender.exe"); +#else + blender_path = blender_path.path_join("blender"); +#endif + + List<String> args; + args.push_back("--background"); + args.push_back("--python-expr"); + args.push_back(p_python_script); + + Error err; + if (p_blocking) { + int exitcode = 0; + err = OS::get_singleton()->execute(blender_path, args, nullptr, &exitcode); + if (exitcode != 0) { + return FAILED; + } + } else { + err = OS::get_singleton()->create_process(blender_path, args, &blender_pid); + } + return err; +} + +Error EditorImportBlendRunner::do_import(const Dictionary &p_options) { + if (is_using_rpc()) { + return do_import_rpc(p_options); + } else { + return do_import_direct(p_options); + } +} + +Error EditorImportBlendRunner::do_import_rpc(const Dictionary &p_options) { + kill_timer->stop(); + + // Start Blender if not already running. + if (!is_running()) { + // Start an XML RPC server on the given port. + String python = vformat(PYTHON_SCRIPT_RPC, rpc_port); + Error err = start_blender(python, false); + if (err != OK || blender_pid == 0) { + return FAILED; + } + } + + // Convert options to XML body. + String xml_options = dict_to_xmlrpc(p_options); + String xml_body = vformat("<?xml version=\"1.0\"?><methodCall><methodName>export_gltf</methodName><params><param><value>%s</value></param></params></methodCall>", xml_options); + + // Connect to RPC server. + Ref<HTTPClient> client = HTTPClient::create(); + client->connect_to_host("127.0.0.1", rpc_port); + + bool done = false; + while (!done) { + HTTPClient::Status status = client->get_status(); + switch (status) { + case HTTPClient::STATUS_RESOLVING: + case HTTPClient::STATUS_CONNECTING: { + client->poll(); + break; + } + case HTTPClient::STATUS_CONNECTED: { + done = true; + break; + } + default: { + ERR_FAIL_V_MSG(ERR_CONNECTION_ERROR, vformat("Unexpected status during RPC connection: %d", status)); + } + } + } + + // Send XML request. + PackedByteArray xml_buffer = xml_body.to_utf8_buffer(); + Error err = client->request(HTTPClient::METHOD_POST, "/", Vector<String>(), xml_buffer.ptr(), xml_buffer.size()); + if (err != OK) { + ERR_FAIL_V_MSG(err, vformat("Unable to send RPC request: %d", err)); + } + + // Wait for response. + done = false; + while (!done) { + HTTPClient::Status status = client->get_status(); + switch (status) { + case HTTPClient::STATUS_REQUESTING: { + client->poll(); + break; + } + case HTTPClient::STATUS_BODY: { + client->poll(); + // Parse response here if needed. For now we can just ignore it. + done = true; + break; + } + default: { + ERR_FAIL_V_MSG(ERR_CONNECTION_ERROR, vformat("Unexpected status during RPC response: %d", status)); + } + } + } + + return OK; +} + +Error EditorImportBlendRunner::do_import_direct(const Dictionary &p_options) { + // Export glTF directly. + String python = vformat(PYTHON_SCRIPT_DIRECT, dict_to_python(p_options)); + Error err = start_blender(python, true); + if (err != OK) { + return err; + } + + return OK; +} + +void EditorImportBlendRunner::_resources_reimported(const PackedStringArray &p_files) { + if (is_running()) { + // After a batch of imports is done, wait a few seconds before trying to kill blender, + // in case of having multiple imports trigger in quick succession. + kill_timer->start(); + } +} + +void EditorImportBlendRunner::_kill_blender() { + kill_timer->stop(); + if (is_running()) { + OS::get_singleton()->kill(blender_pid); + } + blender_pid = 0; +} + +void EditorImportBlendRunner::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_PREDELETE: { + _kill_blender(); + break; + } + } +} + +EditorImportBlendRunner *EditorImportBlendRunner::singleton = nullptr; + +EditorImportBlendRunner::EditorImportBlendRunner() { + ERR_FAIL_COND_MSG(singleton != nullptr, "EditorImportBlendRunner already created."); + singleton = this; + + rpc_port = EDITOR_GET("filesystem/import/blender/rpc_port"); + + kill_timer = memnew(Timer); + add_child(kill_timer); + kill_timer->set_one_shot(true); + kill_timer->set_wait_time(EDITOR_GET("filesystem/import/blender/rpc_server_uptime")); + kill_timer->connect("timeout", callable_mp(this, &EditorImportBlendRunner::_kill_blender)); + + EditorFileSystem::get_singleton()->connect("resources_reimported", callable_mp(this, &EditorImportBlendRunner::_resources_reimported)); +} + +#endif // TOOLS_ENABLED diff --git a/modules/gltf/editor/editor_import_blend_runner.h b/modules/gltf/editor/editor_import_blend_runner.h new file mode 100644 index 0000000000..b2b82394e1 --- /dev/null +++ b/modules/gltf/editor/editor_import_blend_runner.h @@ -0,0 +1,69 @@ +/**************************************************************************/ +/* editor_import_blend_runner.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef EDITOR_IMPORT_BLEND_RUNNER_H +#define EDITOR_IMPORT_BLEND_RUNNER_H + +#ifdef TOOLS_ENABLED + +#include "core/os/os.h" +#include "scene/main/node.h" +#include "scene/main/timer.h" + +class EditorImportBlendRunner : public Node { + GDCLASS(EditorImportBlendRunner, Node); + + static EditorImportBlendRunner *singleton; + + Timer *kill_timer; + void _resources_reimported(const PackedStringArray &p_files); + void _kill_blender(); + void _notification(int p_what); + +protected: + int rpc_port = 0; + OS::ProcessID blender_pid = 0; + Error start_blender(const String &p_python_script, bool p_blocking); + Error do_import_direct(const Dictionary &p_options); + Error do_import_rpc(const Dictionary &p_options); + +public: + static EditorImportBlendRunner *get_singleton() { return singleton; } + + bool is_running() { return blender_pid != 0 && OS::get_singleton()->is_process_running(blender_pid); } + bool is_using_rpc() { return rpc_port != 0; } + Error do_import(const Dictionary &p_options); + + EditorImportBlendRunner(); +}; + +#endif // TOOLS_ENABLED + +#endif // EDITOR_IMPORT_BLEND_RUNNER_H diff --git a/modules/gltf/editor/editor_scene_importer_blend.cpp b/modules/gltf/editor/editor_scene_importer_blend.cpp index 5415c5818f..520f33261a 100644 --- a/modules/gltf/editor/editor_scene_importer_blend.cpp +++ b/modules/gltf/editor/editor_scene_importer_blend.cpp @@ -34,6 +34,7 @@ #include "../gltf_defines.h" #include "../gltf_document.h" +#include "editor_import_blend_runner.h" #include "core/config/project_settings.h" #include "editor/editor_file_dialog.h" @@ -68,149 +69,129 @@ Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_ // Handle configuration options. - String parameters_arg; + Dictionary request_options; + Dictionary parameters_map; + + parameters_map["filepath"] = sink_global; + parameters_map["export_keep_originals"] = true; + parameters_map["export_format"] = "GLTF_SEPARATE"; + parameters_map["export_yup"] = true; if (p_options.has(SNAME("blender/nodes/custom_properties")) && p_options[SNAME("blender/nodes/custom_properties")]) { - parameters_arg += "export_extras=True,"; + parameters_map["export_extras"] = true; } else { - parameters_arg += "export_extras=False,"; + parameters_map["export_extras"] = false; } if (p_options.has(SNAME("blender/meshes/skins"))) { int32_t skins = p_options["blender/meshes/skins"]; if (skins == BLEND_BONE_INFLUENCES_NONE) { - parameters_arg += "export_skins=False,"; + parameters_map["export_skins"] = false; } else if (skins == BLEND_BONE_INFLUENCES_COMPATIBLE) { - parameters_arg += "export_all_influences=False,export_skins=True,"; + parameters_map["export_skins"] = true; + parameters_map["export_all_influences"] = false; } else if (skins == BLEND_BONE_INFLUENCES_ALL) { - parameters_arg += "export_all_influences=True,export_skins=True,"; + parameters_map["export_skins"] = true; + parameters_map["export_all_influences"] = true; } } else { - parameters_arg += "export_skins=False,"; + parameters_map["export_skins"] = false; } if (p_options.has(SNAME("blender/materials/export_materials"))) { int32_t exports = p_options["blender/materials/export_materials"]; if (exports == BLEND_MATERIAL_EXPORT_PLACEHOLDER) { - parameters_arg += "export_materials='PLACEHOLDER',"; + parameters_map["export_materials"] = "PLACEHOLDER"; } else if (exports == BLEND_MATERIAL_EXPORT_EXPORT) { - parameters_arg += "export_materials='EXPORT',"; + parameters_map["export_materials"] = "EXPORT"; } } else { - parameters_arg += "export_materials='PLACEHOLDER',"; + parameters_map["export_materials"] = "PLACEHOLDER"; } if (p_options.has(SNAME("blender/nodes/cameras")) && p_options[SNAME("blender/nodes/cameras")]) { - parameters_arg += "export_cameras=True,"; + parameters_map["export_cameras"] = true; } else { - parameters_arg += "export_cameras=False,"; + parameters_map["export_cameras"] = false; } if (p_options.has(SNAME("blender/nodes/punctual_lights")) && p_options[SNAME("blender/nodes/punctual_lights")]) { - parameters_arg += "export_lights=True,"; + parameters_map["export_lights"] = true; } else { - parameters_arg += "export_lights=False,"; + parameters_map["export_lights"] = false; } if (p_options.has(SNAME("blender/meshes/colors")) && p_options[SNAME("blender/meshes/colors")]) { - parameters_arg += "export_colors=True,"; + parameters_map["export_colors"] = true; } else { - parameters_arg += "export_colors=False,"; + parameters_map["export_colors"] = false; } if (p_options.has(SNAME("blender/nodes/visible"))) { int32_t visible = p_options["blender/nodes/visible"]; if (visible == BLEND_VISIBLE_VISIBLE_ONLY) { - parameters_arg += "use_visible=True,"; + parameters_map["use_visible"] = true; } else if (visible == BLEND_VISIBLE_RENDERABLE) { - parameters_arg += "use_renderable=True,"; + parameters_map["use_renderable"] = true; } else if (visible == BLEND_VISIBLE_ALL) { - parameters_arg += "use_visible=False,use_renderable=False,"; + parameters_map["use_renderable"] = false; + parameters_map["use_visible"] = false; } } else { - parameters_arg += "use_visible=False,use_renderable=False,"; + parameters_map["use_renderable"] = false; + parameters_map["use_visible"] = false; } if (p_options.has(SNAME("blender/meshes/uvs")) && p_options[SNAME("blender/meshes/uvs")]) { - parameters_arg += "export_texcoords=True,"; + parameters_map["export_texcoords"] = true; } else { - parameters_arg += "export_texcoords=False,"; + parameters_map["export_texcoords"] = false; } if (p_options.has(SNAME("blender/meshes/normals")) && p_options[SNAME("blender/meshes/normals")]) { - parameters_arg += "export_normals=True,"; + parameters_map["export_normals"] = true; } else { - parameters_arg += "export_normals=False,"; + parameters_map["export_normals"] = false; } if (p_options.has(SNAME("blender/meshes/tangents")) && p_options[SNAME("blender/meshes/tangents")]) { - parameters_arg += "export_tangents=True,"; + parameters_map["export_tangents"] = true; } else { - parameters_arg += "export_tangents=False,"; + parameters_map["export_tangents"] = false; } if (p_options.has(SNAME("blender/animation/group_tracks")) && p_options[SNAME("blender/animation/group_tracks")]) { - parameters_arg += "export_nla_strips=True,"; + parameters_map["export_nla_strips"] = true; } else { - parameters_arg += "export_nla_strips=False,"; + parameters_map["export_nla_strips"] = false; } if (p_options.has(SNAME("blender/animation/limit_playback")) && p_options[SNAME("blender/animation/limit_playback")]) { - parameters_arg += "export_frame_range=True,"; + parameters_map["export_frame_range"] = true; } else { - parameters_arg += "export_frame_range=False,"; + parameters_map["export_frame_range"] = false; } if (p_options.has(SNAME("blender/animation/always_sample")) && p_options[SNAME("blender/animation/always_sample")]) { - parameters_arg += "export_force_sampling=True,"; + parameters_map["export_force_sampling"] = true; } else { - parameters_arg += "export_force_sampling=False,"; + parameters_map["export_force_sampling"] = false; } if (p_options.has(SNAME("blender/meshes/export_bones_deforming_mesh_only")) && p_options[SNAME("blender/meshes/export_bones_deforming_mesh_only")]) { - parameters_arg += "export_def_bones=True,"; + parameters_map["export_def_bones"] = true; } else { - parameters_arg += "export_def_bones=False,"; + parameters_map["export_def_bones"] = false; } if (p_options.has(SNAME("blender/nodes/modifiers")) && p_options[SNAME("blender/nodes/modifiers")]) { - parameters_arg += "export_apply=True"; + parameters_map["export_apply"] = true; } else { - parameters_arg += "export_apply=False"; + parameters_map["export_apply"] = false; } - String unpack_all; if (p_options.has(SNAME("blender/materials/unpack_enabled")) && p_options[SNAME("blender/materials/unpack_enabled")]) { - unpack_all = "bpy.ops.file.unpack_all(method='USE_LOCAL');"; + request_options["unpack_all"] = true; + } else { + request_options["unpack_all"] = false; } - // Prepare Blender export script. - - String common_args = vformat("filepath='%s',", sink_global) + - "export_format='GLTF_SEPARATE'," - "export_yup=True," + - parameters_arg; - String export_script = - String("import bpy, sys;") + - "print('Blender 3.0 or higher is required.', file=sys.stderr) if bpy.app.version < (3, 0, 0) else None;" + - vformat("bpy.ops.wm.open_mainfile(filepath='%s');", source_global) + - unpack_all + - vformat("bpy.ops.export_scene.gltf(export_keep_originals=True,%s);", common_args); - print_verbose(export_script); - - // Run script with configured Blender binary. + request_options["path"] = source_global; + request_options["gltf_options"] = parameters_map; - String blender_path = EDITOR_GET("filesystem/import/blender/blender3_path"); - -#ifdef WINDOWS_ENABLED - blender_path = blender_path.path_join("blender.exe"); -#else - blender_path = blender_path.path_join("blender"); -#endif - - List<String> args; - args.push_back("--background"); - args.push_back("--python-expr"); - args.push_back(export_script); - - String standard_out; - int ret; - OS::get_singleton()->execute(blender_path, args, &standard_out, &ret, true); - print_verbose(blender_path); - print_verbose(standard_out); - - if (ret != 0) { + // Run Blender and export glTF. + Error err = EditorImportBlendRunner::get_singleton()->do_import(request_options); + if (err != OK) { if (r_err) { *r_err = ERR_SCRIPT_FAILED; } - ERR_PRINT(vformat("Blend export to glTF failed with error: %d.", ret)); return nullptr; } @@ -226,7 +207,7 @@ Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_ if (p_options.has(SNAME("blender/materials/unpack_enabled")) && p_options[SNAME("blender/materials/unpack_enabled")]) { base_dir = sink.get_base_dir(); } - Error err = gltf->append_from_file(sink.get_basename() + ".gltf", state, p_flags, base_dir); + err = gltf->append_from_file(sink.get_basename() + ".gltf", state, p_flags, base_dir); if (err != OK) { if (r_err) { *r_err = FAILED; diff --git a/modules/gltf/editor/editor_scene_importer_gltf.cpp b/modules/gltf/editor/editor_scene_importer_gltf.cpp index 67bbf8dd15..012a144d52 100644 --- a/modules/gltf/editor/editor_scene_importer_gltf.cpp +++ b/modules/gltf/editor/editor_scene_importer_gltf.cpp @@ -51,8 +51,8 @@ Node *EditorSceneFormatImporterGLTF::import_scene(const String &p_path, uint32_t doc.instantiate(); Ref<GLTFState> state; state.instantiate(); - if (p_options.has("meshes/handle_gltf_embedded_images")) { - int32_t enum_option = p_options["meshes/handle_gltf_embedded_images"]; + if (p_options.has("gltf/embedded_image_handling")) { + int32_t enum_option = p_options["gltf/embedded_image_handling"]; state->set_handle_binary_image(enum_option); } Error err = doc->append_from_file(p_path, state, p_flags); @@ -87,7 +87,7 @@ Node *EditorSceneFormatImporterGLTF::import_scene(const String &p_path, uint32_t void EditorSceneFormatImporterGLTF::get_import_options(const String &p_path, List<ResourceImporter::ImportOption> *r_options) { - r_options->push_back(ResourceImporterScene::ImportOption(PropertyInfo(Variant::INT, "meshes/handle_gltf_embedded_images", PROPERTY_HINT_ENUM, "Discard All Textures,Extract Textures,Embed As Basis Universal", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), GLTFState::HANDLE_BINARY_EXTRACT_TEXTURES)); + r_options->push_back(ResourceImporterScene::ImportOption(PropertyInfo(Variant::INT, "gltf/embedded_image_handling", PROPERTY_HINT_ENUM, "Discard All Textures,Extract Textures,Embed As Basis Universal,Embed as Uncompressed", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), GLTFState::HANDLE_BINARY_EXTRACT_TEXTURES)); } #endif // TOOLS_ENABLED diff --git a/modules/gltf/extensions/gltf_document_extension.cpp b/modules/gltf/extensions/gltf_document_extension.cpp index 496a8f6cb8..bedb42eb32 100644 --- a/modules/gltf/extensions/gltf_document_extension.cpp +++ b/modules/gltf/extensions/gltf_document_extension.cpp @@ -49,9 +49,9 @@ void GLTFDocumentExtension::_bind_methods() { // Import process. Error GLTFDocumentExtension::import_preflight(Ref<GLTFState> p_state, Vector<String> p_extensions) { ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER); - int err = OK; + Error err = OK; GDVIRTUAL_CALL(_import_preflight, p_state, p_extensions, err); - return Error(err); + return err; } Vector<String> GLTFDocumentExtension::get_supported_extensions() { @@ -63,9 +63,9 @@ Vector<String> GLTFDocumentExtension::get_supported_extensions() { Error GLTFDocumentExtension::parse_node_extensions(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &p_extensions) { ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER); ERR_FAIL_NULL_V(p_gltf_node, ERR_INVALID_PARAMETER); - int err = OK; + Error err = OK; GDVIRTUAL_CALL(_parse_node_extensions, p_state, p_gltf_node, p_extensions, err); - return Error(err); + return err; } Node3D *GLTFDocumentExtension::generate_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_parent) { @@ -79,34 +79,34 @@ Node3D *GLTFDocumentExtension::generate_scene_node(Ref<GLTFState> p_state, Ref<G Error GLTFDocumentExtension::import_post_parse(Ref<GLTFState> p_state) { ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER); - int err = OK; + Error err = OK; GDVIRTUAL_CALL(_import_post_parse, p_state, err); - return Error(err); + return err; } Error GLTFDocumentExtension::import_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &r_dict, Node *p_node) { ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER); ERR_FAIL_NULL_V(p_gltf_node, ERR_INVALID_PARAMETER); ERR_FAIL_NULL_V(p_node, ERR_INVALID_PARAMETER); - int err = OK; + Error err = OK; GDVIRTUAL_CALL(_import_node, p_state, p_gltf_node, r_dict, p_node, err); - return Error(err); + return err; } Error GLTFDocumentExtension::import_post(Ref<GLTFState> p_state, Node *p_root) { ERR_FAIL_NULL_V(p_root, ERR_INVALID_PARAMETER); ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER); - int err = OK; + Error err = OK; GDVIRTUAL_CALL(_import_post, p_state, p_root, err); - return Error(err); + return err; } // Export process. Error GLTFDocumentExtension::export_preflight(Ref<GLTFState> p_state, Node *p_root) { ERR_FAIL_NULL_V(p_root, ERR_INVALID_PARAMETER); - int err = OK; + Error err = OK; GDVIRTUAL_CALL(_export_preflight, p_state, p_root, err); - return Error(err); + return err; } void GLTFDocumentExtension::convert_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_node) { @@ -120,14 +120,14 @@ Error GLTFDocumentExtension::export_node(Ref<GLTFState> p_state, Ref<GLTFNode> p ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER); ERR_FAIL_NULL_V(p_gltf_node, ERR_INVALID_PARAMETER); ERR_FAIL_NULL_V(p_node, ERR_INVALID_PARAMETER); - int err = OK; + Error err = OK; GDVIRTUAL_CALL(_export_node, p_state, p_gltf_node, r_dict, p_node, err); - return Error(err); + return err; } Error GLTFDocumentExtension::export_post(Ref<GLTFState> p_state) { ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER); - int err = OK; + Error err = OK; GDVIRTUAL_CALL(_export_post, p_state, err); - return Error(err); + return err; } diff --git a/modules/gltf/extensions/gltf_document_extension.h b/modules/gltf/extensions/gltf_document_extension.h index 97f5045a1c..3531f81f6f 100644 --- a/modules/gltf/extensions/gltf_document_extension.h +++ b/modules/gltf/extensions/gltf_document_extension.h @@ -55,18 +55,18 @@ public: virtual Error export_post(Ref<GLTFState> p_state); // Import process. - GDVIRTUAL2R(int, _import_preflight, Ref<GLTFState>, Vector<String>); + GDVIRTUAL2R(Error, _import_preflight, Ref<GLTFState>, Vector<String>); GDVIRTUAL0R(Vector<String>, _get_supported_extensions); - GDVIRTUAL3R(int, _parse_node_extensions, Ref<GLTFState>, Ref<GLTFNode>, Dictionary); + GDVIRTUAL3R(Error, _parse_node_extensions, Ref<GLTFState>, Ref<GLTFNode>, Dictionary); GDVIRTUAL3R(Node3D *, _generate_scene_node, Ref<GLTFState>, Ref<GLTFNode>, Node *); - GDVIRTUAL1R(int, _import_post_parse, Ref<GLTFState>); - GDVIRTUAL4R(int, _import_node, Ref<GLTFState>, Ref<GLTFNode>, Dictionary, Node *); - GDVIRTUAL2R(int, _import_post, Ref<GLTFState>, Node *); + GDVIRTUAL1R(Error, _import_post_parse, Ref<GLTFState>); + GDVIRTUAL4R(Error, _import_node, Ref<GLTFState>, Ref<GLTFNode>, Dictionary, Node *); + GDVIRTUAL2R(Error, _import_post, Ref<GLTFState>, Node *); // Export process. - GDVIRTUAL2R(int, _export_preflight, Ref<GLTFState>, Node *); + GDVIRTUAL2R(Error, _export_preflight, Ref<GLTFState>, Node *); GDVIRTUAL3(_convert_scene_node, Ref<GLTFState>, Ref<GLTFNode>, Node *); - GDVIRTUAL4R(int, _export_node, Ref<GLTFState>, Ref<GLTFNode>, Dictionary, Node *); - GDVIRTUAL1R(int, _export_post, Ref<GLTFState>); + GDVIRTUAL4R(Error, _export_node, Ref<GLTFState>, Ref<GLTFNode>, Dictionary, Node *); + GDVIRTUAL1R(Error, _export_post, Ref<GLTFState>); }; #endif // GLTF_DOCUMENT_EXTENSION_H diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp index 0b519bd6a3..1a09b5bdcc 100644 --- a/modules/gltf/gltf_document.cpp +++ b/modules/gltf/gltf_document.cpp @@ -32,6 +32,7 @@ #include "extensions/gltf_spec_gloss.h" +#include "core/config/project_settings.h" #include "core/crypto/crypto_core.h" #include "core/io/config_file.h" #include "core/io/dir_access.h" @@ -3065,6 +3066,7 @@ Error GLTFDocument::_parse_images(Ref<GLTFState> p_state, const String &p_base_p // Ref: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#images const Array &images = p_state->json["images"]; + HashSet<String> used_names; for (int i = 0; i < images.size(); i++) { const Dictionary &d = images[i]; @@ -3092,11 +3094,21 @@ Error GLTFDocument::_parse_images(Ref<GLTFState> p_state, const String &p_base_p int data_size = 0; String image_name; + if (d.has("name")) { + image_name = d["name"]; + image_name = image_name.get_file().get_basename().validate_filename(); + } + if (image_name.is_empty()) { + image_name = itos(i); + } + while (used_names.has(image_name)) { + image_name += "_" + itos(i); + } + used_names.insert(image_name); if (d.has("uri")) { // Handles the first two bullet points from the spec (embedded data, or external file). String uri = d["uri"]; - image_name = uri; if (uri.begins_with("data:")) { // Embedded data using base64. // Validate data MIME types and throw a warning if it's one we don't know/support. @@ -3158,7 +3170,6 @@ Error GLTFDocument::_parse_images(Ref<GLTFState> p_state, const String &p_base_p vformat("glTF: Image index '%d' specifies 'bufferView' but no 'mimeType', which is invalid.", i)); const GLTFBufferViewIndex bvi = d["bufferView"]; - image_name = itos(bvi); ERR_FAIL_INDEX_V(bvi, p_state->buffer_views.size(), ERR_PARAMETER_RANGE_ERROR); @@ -3206,13 +3217,12 @@ Error GLTFDocument::_parse_images(Ref<GLTFState> p_state, const String &p_base_p p_state->source_images.push_back(Ref<Image>()); continue; } + img->set_name(image_name); if (GLTFState::GLTFHandleBinary(p_state->handle_binary_image) == GLTFState::GLTFHandleBinary::HANDLE_BINARY_DISCARD_TEXTURES) { p_state->images.push_back(Ref<Texture2D>()); p_state->source_images.push_back(Ref<Image>()); - continue; - } else if (GLTFState::GLTFHandleBinary(p_state->handle_binary_image) == GLTFState::GLTFHandleBinary::HANDLE_BINARY_EXTRACT_TEXTURES) { - String extracted_image_name = image_name.get_file().get_basename().validate_filename(); - img->set_name(extracted_image_name); +#ifdef TOOLS_ENABLED + } else if (Engine::get_singleton()->is_editor_hint() && GLTFState::GLTFHandleBinary(p_state->handle_binary_image) == GLTFState::GLTFHandleBinary::HANDLE_BINARY_EXTRACT_TEXTURES) { if (p_state->base_path.is_empty()) { p_state->images.push_back(Ref<Texture2D>()); p_state->source_images.push_back(Ref<Image>()); @@ -3221,26 +3231,56 @@ Error GLTFDocument::_parse_images(Ref<GLTFState> p_state, const String &p_base_p p_state->images.push_back(Ref<Texture2D>()); p_state->source_images.push_back(Ref<Image>()); } else { + Error err = OK; + bool must_import = false; String file_path = p_state->get_base_path() + "/" + p_state->filename.get_basename() + "_" + img->get_name() + ".png"; - Ref<ConfigFile> config; - config.instantiate(); - if (FileAccess::exists(file_path + ".import")) { - config->load(file_path + ".import"); + if (!FileAccess::exists(file_path + ".import")) { + Ref<ConfigFile> config; + config.instantiate(); + config->set_value("remap", "importer", "texture"); + config->set_value("remap", "type", "Texture2D"); + // Currently, it will likely use project defaults of Detect 3D, so textures will be reimported again. + if (!config->has_section_key("params", "mipmaps/generate")) { + config->set_value("params", "mipmaps/generate", true); + } + + if (ProjectSettings::get_singleton()->has_setting("importer_defaults/texture")) { + //use defaults if exist + Dictionary importer_defaults = GLOBAL_GET("importer_defaults/texture"); + List<Variant> importer_def_keys; + importer_defaults.get_key_list(&importer_def_keys); + for (const Variant &key : importer_def_keys) { + if (!config->has_section_key("params", (String)key)) { + config->set_value("params", (String)key, importer_defaults[key]); + } + } + } + err = config->save(file_path + ".import"); + ERR_FAIL_COND_V(err != OK, err); + must_import = true; } - config->set_value("remap", "importer", "texture"); - config->set_value("remap", "type", "Texture2D"); - if (!config->has_section_key("params", "compress/mode")) { - config->set_value("remap", "compress/mode", 2); //user may want another compression, so leave it bes + Vector<uint8_t> png_buffer = img->save_png_to_buffer(); + if (ResourceLoader::exists(file_path)) { + Ref<FileAccess> file = FileAccess::open(file_path, FileAccess::READ, &err); + if (err == OK && file.is_valid()) { + Vector<uint8_t> orig_png_buffer = file->get_buffer(file->get_length()); + if (png_buffer != orig_png_buffer) { + must_import = true; + } + } + } else { + must_import = true; } - if (!config->has_section_key("params", "mipmaps/generate")) { - config->set_value("params", "mipmaps/generate", true); + if (must_import) { + Ref<FileAccess> file = FileAccess::open(file_path, FileAccess::WRITE, &err); + ERR_FAIL_COND_V(err != OK, err); + ERR_FAIL_COND_V(file.is_null(), FAILED); + file->store_buffer(png_buffer); + file->flush(); + file.unref(); + // ResourceLoader::import will crash if not is_editor_hint(), so this case is protected above and will fall through to uncompressed. + ResourceLoader::import(file_path); } - Error err = OK; - err = config->save(file_path + ".import"); - ERR_FAIL_COND_V(err != OK, err); - img->save_png(file_path); - ERR_FAIL_COND_V(err != OK, err); - ResourceLoader::import(file_path); Ref<Texture2D> saved_image = ResourceLoader::load(file_path, "Texture2D"); if (saved_image.is_valid()) { p_state->images.push_back(saved_image); @@ -3252,7 +3292,7 @@ Error GLTFDocument::_parse_images(Ref<GLTFState> p_state, const String &p_base_p p_state->source_images.push_back(Ref<Image>()); } } - continue; +#endif } else if (GLTFState::GLTFHandleBinary(p_state->handle_binary_image) == GLTFState::GLTFHandleBinary::HANDLE_BINARY_EMBED_AS_BASISU) { Ref<PortableCompressedTexture2D> tex; tex.instantiate(); @@ -3262,11 +3302,15 @@ Error GLTFDocument::_parse_images(Ref<GLTFState> p_state, const String &p_base_p tex->create_from_image(img, PortableCompressedTexture2D::COMPRESSION_MODE_BASIS_UNIVERSAL); p_state->images.push_back(tex); p_state->source_images.push_back(img); - continue; + } else { + // This handles two cases: if editor hint and HANDLE_BINARY_EXTRACT_TEXTURES; or if HANDLE_BINARY_EMBED_AS_UNCOMPRESSED + Ref<ImageTexture> tex; + tex.instantiate(); + tex->set_name(img->get_name()); + tex->set_image(img); + p_state->images.push_back(tex); + p_state->source_images.push_back(img); } - - p_state->images.push_back(Ref<Texture2D>()); - p_state->source_images.push_back(Ref<Image>()); } print_verbose("glTF: Total images: " + itos(p_state->images.size())); diff --git a/modules/gltf/gltf_state.cpp b/modules/gltf/gltf_state.cpp index b67484fc8e..b7b7113a97 100644 --- a/modules/gltf/gltf_state.cpp +++ b/modules/gltf/gltf_state.cpp @@ -120,11 +120,12 @@ void GLTFState::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "skeleton_to_node", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_skeleton_to_node", "get_skeleton_to_node"); // RBMap<GLTFSkeletonIndex, ADD_PROPERTY(PropertyInfo(Variant::BOOL, "create_animations"), "set_create_animations", "get_create_animations"); // bool ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "animations", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_animations", "get_animations"); // Vector<Ref<GLTFAnimation>> - ADD_PROPERTY(PropertyInfo(Variant::INT, "handle_binary_image", PROPERTY_HINT_ENUM, "Discard All Textures,Extract Textures,Embed As Basis Universal", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_handle_binary_image", "get_handle_binary_image"); // enum + ADD_PROPERTY(PropertyInfo(Variant::INT, "handle_binary_image", PROPERTY_HINT_ENUM, "Discard All Textures,Extract Textures,Embed As Basis Universal,Embed as Uncompressed", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_handle_binary_image", "get_handle_binary_image"); // enum BIND_CONSTANT(HANDLE_BINARY_DISCARD_TEXTURES); BIND_CONSTANT(HANDLE_BINARY_EXTRACT_TEXTURES); BIND_CONSTANT(HANDLE_BINARY_EMBED_AS_BASISU); + BIND_CONSTANT(HANDLE_BINARY_EMBED_AS_UNCOMPRESSED); } void GLTFState::add_used_extension(const String &p_extension_name, bool p_required) { diff --git a/modules/gltf/gltf_state.h b/modules/gltf/gltf_state.h index 52d7949d03..b6979ca48e 100644 --- a/modules/gltf/gltf_state.h +++ b/modules/gltf/gltf_state.h @@ -108,6 +108,7 @@ public: HANDLE_BINARY_DISCARD_TEXTURES = 0, HANDLE_BINARY_EXTRACT_TEXTURES, HANDLE_BINARY_EMBED_AS_BASISU, + HANDLE_BINARY_EMBED_AS_UNCOMPRESSED, // if this value changes from 3, ResourceImporterScene::pre_import must be changed as well. }; int32_t get_handle_binary_image() { return handle_binary_image; diff --git a/modules/gltf/register_types.cpp b/modules/gltf/register_types.cpp index f80e12bbae..78589090db 100644 --- a/modules/gltf/register_types.cpp +++ b/modules/gltf/register_types.cpp @@ -36,6 +36,7 @@ #ifdef TOOLS_ENABLED #include "core/config/project_settings.h" +#include "editor/editor_import_blend_runner.h" #include "editor/editor_node.h" #include "editor/editor_scene_exporter_gltf_plugin.h" #include "editor/editor_scene_importer_blend.h" @@ -52,6 +53,14 @@ static void _editor_init() { bool blend_enabled = GLOBAL_GET("filesystem/import/blender/enabled"); // Defined here because EditorSettings doesn't exist in `register_gltf_types` yet. + EDITOR_DEF_RST("filesystem/import/blender/rpc_port", 6011); + EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::INT, + "filesystem/import/blender/rpc_port", PROPERTY_HINT_RANGE, "0,65535,1")); + + EDITOR_DEF_RST("filesystem/import/blender/rpc_server_uptime", 5); + EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::FLOAT, + "filesystem/import/blender/rpc_server_uptime", PROPERTY_HINT_RANGE, "0,300,1,or_greater,suffix:s")); + String blender3_path = EDITOR_DEF_RST("filesystem/import/blender/blender3_path", ""); EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "filesystem/import/blender/blender3_path", PROPERTY_HINT_GLOBAL_DIR)); @@ -71,6 +80,8 @@ static void _editor_init() { EditorFileSystem::get_singleton()->add_import_format_support_query(blend_import_query); } } + memnew(EditorImportBlendRunner); + EditorNode::get_singleton()->add_child(EditorImportBlendRunner::get_singleton()); // FBX to glTF importer. diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/PackedSceneExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/PackedSceneExtensions.cs index 8463403096..4610761bdb 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/PackedSceneExtensions.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/PackedSceneExtensions.cs @@ -7,7 +7,7 @@ namespace Godot /// <summary> /// Instantiates the scene's node hierarchy, erroring on failure. /// Triggers child scene instantiation(s). Triggers a - /// <see cref="Node.NotificationInstanced"/> notification on the root node. + /// <see cref="Node.NotificationSceneInstantiated"/> notification on the root node. /// </summary> /// <seealso cref="InstantiateOrNull{T}(GenEditState)"/> /// <exception cref="InvalidCastException"> @@ -23,7 +23,7 @@ namespace Godot /// <summary> /// Instantiates the scene's node hierarchy, returning <see langword="null"/> on failure. /// Triggers child scene instantiation(s). Triggers a - /// <see cref="Node.NotificationInstanced"/> notification on the root node. + /// <see cref="Node.NotificationSceneInstantiated"/> notification on the root node. /// </summary> /// <seealso cref="Instantiate{T}(GenEditState)"/> /// <typeparam name="T">The type to cast to. Should be a descendant of <see cref="Node"/>.</typeparam> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rid.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rid.cs index 150eb98fc7..350626389b 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rid.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rid.cs @@ -6,13 +6,17 @@ using Godot.NativeInterop; namespace Godot { /// <summary> - /// The Rid type is used to access the unique integer ID of a resource. - /// They are opaque, which means they do not grant access to the associated - /// resource by themselves. They are used by and with the low-level Server - /// classes such as <see cref="RenderingServer"/>. + /// The RID type is used to access a low-level resource by its unique ID. + /// RIDs are opaque, which means they do not grant access to the resource + /// by themselves. They are used by the low-level server classes, such as + /// <see cref="DisplayServer"/>, <see cref="RenderingServer"/>, + /// <see cref="TextServer"/>, etc. + /// + /// A low-level resource may correspond to a high-level <see cref="Resource"/>, + /// such as <see cref="Texture"/> or <see cref="Mesh"/> /// </summary> [StructLayout(LayoutKind.Sequential)] - public readonly struct Rid + public readonly struct Rid : IEquatable<Rid> { private readonly ulong _id; // Default is 0 @@ -28,15 +32,73 @@ namespace Godot => _id = from is Resource res ? res.GetRid()._id : default; /// <summary> - /// Returns the ID of the referenced resource. + /// Returns the ID of the referenced low-level resource. /// </summary> /// <returns>The ID of the referenced resource.</returns> public ulong Id => _id; /// <summary> + /// Returns <see langword="true"/> if the <see cref="Rid"/> is not <c>0</c>. + /// </summary> + /// <returns>Whether or not the ID is valid.</returns> + public bool IsValid => _id != 0; + + /// <summary> + /// Returns <see langword="true"/> if both <see cref="Rid"/>s are equal, + /// which means they both refer to the same low-level resource. + /// </summary> + /// <param name="left">The left RID.</param> + /// <param name="right">The right RID.</param> + /// <returns>Whether or not the RIDs are equal.</returns> + public static bool operator ==(Rid left, Rid right) + { + return left.Equals(right); + } + + /// <summary> + /// Returns <see langword="true"/> if the <see cref="Rid"/>s are not equal. + /// </summary> + /// <param name="left">The left RID.</param> + /// <param name="right">The right RID.</param> + /// <returns>Whether or not the RIDs are equal.</returns> + public static bool operator !=(Rid left, Rid right) + { + return !left.Equals(right); + } + + /// <summary> + /// Returns <see langword="true"/> if this RID and <paramref name="obj"/> are equal. + /// </summary> + /// <param name="obj">The other object to compare.</param> + /// <returns>Whether or not the color and the other object are equal.</returns> + public override readonly bool Equals(object obj) + { + return obj is Rid other && Equals(other); + } + + /// <summary> + /// Returns <see langword="true"/> if the RIDs are equal. + /// </summary> + /// <param name="other">The other RID.</param> + /// <returns>Whether or not the RIDs are equal.</returns> + public readonly bool Equals(Rid other) + { + return _id == other.Id; + } + + /// <summary> + /// Serves as the hash function for <see cref="Rid"/>. + /// </summary> + /// <returns>A hash code for this RID.</returns> + public override readonly int GetHashCode() + { + return HashCode.Combine(_id); + } + + /// <summary> /// Converts this <see cref="Rid"/> to a string. /// </summary> /// <returns>A string representation of this Rid.</returns> - public override string ToString() => $"Rid({Id})"; + public override string ToString() => $"RID({Id})"; } } diff --git a/modules/openxr/doc_classes/OpenXRInterface.xml b/modules/openxr/doc_classes/OpenXRInterface.xml index 7251a4a9bd..f3cc469c9d 100644 --- a/modules/openxr/doc_classes/OpenXRInterface.xml +++ b/modules/openxr/doc_classes/OpenXRInterface.xml @@ -11,12 +11,33 @@ <link title="Setting up XR">$DOCS_URL/tutorials/xr/setting_up_xr.html</link> </tutorials> <methods> + <method name="get_action_sets" qualifiers="const"> + <return type="Array" /> + <description> + Returns a list of action sets registered with Godot (loaded from the action map at runtime). + </description> + </method> <method name="get_available_display_refresh_rates" qualifiers="const"> <return type="Array" /> <description> Returns display refresh rates supported by the current HMD. Only returned if this feature is supported by the OpenXR runtime and after the interface has been initialized. </description> </method> + <method name="is_action_set_active" qualifiers="const"> + <return type="bool" /> + <param index="0" name="name" type="String" /> + <description> + Returns [code]true[/code] if the given action set is active. + </description> + </method> + <method name="set_action_set_active"> + <return type="void" /> + <param index="0" name="name" type="String" /> + <param index="1" name="active" type="bool" /> + <description> + Sets the given action set as active or inactive. + </description> + </method> </methods> <members> <member name="display_refresh_rate" type="float" setter="set_display_refresh_rate" getter="get_display_refresh_rate" default="0.0"> diff --git a/modules/openxr/openxr_interface.cpp b/modules/openxr/openxr_interface.cpp index f9afdf2d4a..702e56b410 100644 --- a/modules/openxr/openxr_interface.cpp +++ b/modules/openxr/openxr_interface.cpp @@ -47,6 +47,10 @@ void OpenXRInterface::_bind_methods() { ClassDB::bind_method(D_METHOD("set_display_refresh_rate", "refresh_rate"), &OpenXRInterface::set_display_refresh_rate); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "display_refresh_rate"), "set_display_refresh_rate", "get_display_refresh_rate"); + ClassDB::bind_method(D_METHOD("is_action_set_active", "name"), &OpenXRInterface::is_action_set_active); + ClassDB::bind_method(D_METHOD("set_action_set_active", "name", "active"), &OpenXRInterface::set_action_set_active); + ClassDB::bind_method(D_METHOD("get_action_sets"), &OpenXRInterface::get_action_sets); + ClassDB::bind_method(D_METHOD("get_available_display_refresh_rates"), &OpenXRInterface::get_available_display_refresh_rates); } @@ -621,6 +625,38 @@ Array OpenXRInterface::get_available_display_refresh_rates() const { } } +bool OpenXRInterface::is_action_set_active(const String &p_action_set) const { + for (ActionSet *action_set : action_sets) { + if (action_set->action_set_name == p_action_set) { + return action_set->is_active; + } + } + + WARN_PRINT("OpenXR: Unknown action set " + p_action_set); + return false; +} + +void OpenXRInterface::set_action_set_active(const String &p_action_set, bool p_active) { + for (ActionSet *action_set : action_sets) { + if (action_set->action_set_name == p_action_set) { + action_set->is_active = p_active; + return; + } + } + + WARN_PRINT("OpenXR: Unknown action set " + p_action_set); +} + +Array OpenXRInterface::get_action_sets() const { + Array arr; + + for (ActionSet *action_set : action_sets) { + arr.push_back(action_set->action_set_name); + } + + return arr; +} + Size2 OpenXRInterface::get_render_target_size() { if (openxr_api == nullptr) { return Size2(); diff --git a/modules/openxr/openxr_interface.h b/modules/openxr/openxr_interface.h index 2b43369523..cce329d8e6 100644 --- a/modules/openxr/openxr_interface.h +++ b/modules/openxr/openxr_interface.h @@ -123,6 +123,10 @@ public: void set_display_refresh_rate(float p_refresh_rate); Array get_available_display_refresh_rates() const; + bool is_action_set_active(const String &p_action_set) const; + void set_action_set_active(const String &p_action_set, bool p_active); + Array get_action_sets() const; + virtual Size2 get_render_target_size() override; virtual uint32_t get_view_count() override; virtual Transform3D get_camera_transform() override; diff --git a/modules/theora/doc_classes/VideoStreamTheora.xml b/modules/theora/doc_classes/VideoStreamTheora.xml index e07af8f169..3ec762a880 100644 --- a/modules/theora/doc_classes/VideoStreamTheora.xml +++ b/modules/theora/doc_classes/VideoStreamTheora.xml @@ -9,19 +9,4 @@ </description> <tutorials> </tutorials> - <methods> - <method name="get_file"> - <return type="String" /> - <description> - Returns the Ogg Theora video file handled by this [VideoStreamTheora]. - </description> - </method> - <method name="set_file"> - <return type="void" /> - <param index="0" name="file" type="String" /> - <description> - Sets the Ogg Theora video file that this [VideoStreamTheora] resource handles. The [code]file[/code] name should have the [code].ogv[/code] extension. - </description> - </method> - </methods> </class> diff --git a/modules/theora/video_stream_theora.cpp b/modules/theora/video_stream_theora.cpp index c600924a3e..b38f7225a2 100644 --- a/modules/theora/video_stream_theora.cpp +++ b/modules/theora/video_stream_theora.cpp @@ -574,25 +574,10 @@ bool VideoStreamPlaybackTheora::is_paused() const { return paused; } -void VideoStreamPlaybackTheora::set_loop(bool p_enable) { -} - -bool VideoStreamPlaybackTheora::has_loop() const { - return false; -} - double VideoStreamPlaybackTheora::get_length() const { return 0; } -String VideoStreamPlaybackTheora::get_stream_name() const { - return ""; -} - -int VideoStreamPlaybackTheora::get_loop_count() const { - return 0; -} - double VideoStreamPlaybackTheora::get_playback_position() const { return get_time(); } @@ -601,11 +586,6 @@ void VideoStreamPlaybackTheora::seek(double p_time) { WARN_PRINT_ONCE("Seeking in Theora videos is not implemented yet (it's only supported for GDExtension-provided video streams)."); } -void VideoStreamPlaybackTheora::set_mix_callback(AudioMixCallback p_callback, void *p_userdata) { - mix_callback = p_callback; - mix_udata = p_userdata; -} - int VideoStreamPlaybackTheora::get_channels() const { return vi.channels; } @@ -657,16 +637,9 @@ VideoStreamPlaybackTheora::~VideoStreamPlaybackTheora() { memdelete(thread_sem); #endif clear(); -} - -void VideoStreamTheora::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_file", "file"), &VideoStreamTheora::set_file); - ClassDB::bind_method(D_METHOD("get_file"), &VideoStreamTheora::get_file); - - ADD_PROPERTY(PropertyInfo(Variant::STRING, "file", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_file", "get_file"); -} +}; -//////////// +void VideoStreamTheora::_bind_methods() {} Ref<Resource> ResourceFormatLoaderTheora::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) { Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ); diff --git a/modules/theora/video_stream_theora.h b/modules/theora/video_stream_theora.h index f047440df7..32adc28a88 100644 --- a/modules/theora/video_stream_theora.h +++ b/modules/theora/video_stream_theora.h @@ -99,8 +99,6 @@ class VideoStreamPlaybackTheora : public VideoStreamPlayback { Ref<ImageTexture> texture; - AudioMixCallback mix_callback = nullptr; - void *mix_udata = nullptr; bool paused = false; #ifdef THEORA_USE_THREAD_STREAMING @@ -133,15 +131,8 @@ public: virtual void set_paused(bool p_paused) override; virtual bool is_paused() const override; - virtual void set_loop(bool p_enable) override; - virtual bool has_loop() const override; - virtual double get_length() const override; - virtual String get_stream_name() const; - - virtual int get_loop_count() const; - virtual double get_playback_position() const override; virtual void seek(double p_time) override; @@ -150,7 +141,6 @@ public: virtual Ref<Texture2D> get_texture() const override; virtual void update(double p_delta) override; - virtual void set_mix_callback(AudioMixCallback p_callback, void *p_userdata) override; virtual int get_channels() const override; virtual int get_mix_rate() const override; @@ -163,9 +153,6 @@ public: class VideoStreamTheora : public VideoStream { GDCLASS(VideoStreamTheora, VideoStream); - String file; - int audio_track; - protected: static void _bind_methods(); @@ -177,8 +164,6 @@ public: return pb; } - void set_file(const String &p_file) { file = p_file; } - String get_file() { return file; } void set_audio_track(int p_track) override { audio_track = p_track; } VideoStreamTheora() { audio_track = 0; } diff --git a/modules/webrtc/webrtc_multiplayer_peer.cpp b/modules/webrtc/webrtc_multiplayer_peer.cpp index 36d0b41889..9224760c5b 100644 --- a/modules/webrtc/webrtc_multiplayer_peer.cpp +++ b/modules/webrtc/webrtc_multiplayer_peer.cpp @@ -128,7 +128,6 @@ void WebRTCMultiplayerPeer::poll() { // Server connected. connection_status = CONNECTION_CONNECTED; emit_signal(SNAME("peer_connected"), TARGET_PEER_SERVER); - emit_signal(SNAME("connection_succeeded")); } else { emit_signal(SNAME("peer_connected"), E); } diff --git a/modules/websocket/websocket_multiplayer_peer.cpp b/modules/websocket/websocket_multiplayer_peer.cpp index 389d8c56ad..c12fc5e834 100644 --- a/modules/websocket/websocket_multiplayer_peer.cpp +++ b/modules/websocket/websocket_multiplayer_peer.cpp @@ -237,7 +237,6 @@ void WebSocketMultiplayerPeer::_poll_client() { } connection_status = CONNECTION_CONNECTED; emit_signal("peer_connected", 1); - emit_signal("connection_succeeded"); } else { return; // Still waiting for an ID. } |