diff options
Diffstat (limited to 'modules')
53 files changed, 734 insertions, 249 deletions
diff --git a/modules/etcpak/image_compress_etcpak.cpp b/modules/etcpak/image_compress_etcpak.cpp index 1fd98a43d4..b5192bd664 100644 --- a/modules/etcpak/image_compress_etcpak.cpp +++ b/modules/etcpak/image_compress_etcpak.cpp @@ -118,6 +118,7 @@ void _compress_etcpak(EtcpakType p_compresstype, Image *r_img, float p_lossy_qua } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2_RA_AS_RG) { target_format = Image::FORMAT_ETC2_RA_AS_RG; r_img->convert_rg_to_ra_rgba8(); + r_img->convert_rgba8_to_bgra8(); // It's badly documented but ETCPAK seems to be expected BGRA8 for ETC. } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2_ALPHA) { target_format = Image::FORMAT_ETC2_RGBA8; r_img->convert_rgba8_to_bgra8(); // It's badly documented but ETCPAK seems to be expected BGRA8 for ETC. @@ -222,9 +223,9 @@ void _compress_etcpak(EtcpakType p_compresstype, Image *r_img, float p_lossy_qua } if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC1) { CompressEtc1RgbDither(src_mip_read, dest_mip_write, blocks, mip_w); - } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2 || p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2_RA_AS_RG) { + } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2) { CompressEtc2Rgb(src_mip_read, dest_mip_write, blocks, mip_w, true); - } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2_ALPHA) { + } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2_ALPHA || p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2_RA_AS_RG) { CompressEtc2Rgba(src_mip_read, dest_mip_write, blocks, mip_w, true); } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_DXT1) { CompressDxt1Dither(src_mip_read, dest_mip_write, blocks, mip_w); diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index 7b79c9cf7e..28f478e9cd 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -1037,7 +1037,7 @@ String GDScript::get_script_path() const { } Error GDScript::load_source_code(const String &p_path) { - if (p_path.is_empty() || ResourceLoader::get_resource_type(p_path.get_slice("::", 0)) == "PackedScene") { + if (p_path.is_empty() || p_path.begins_with("gdscript://") || ResourceLoader::get_resource_type(p_path.get_slice("::", 0)) == "PackedScene") { return OK; } @@ -1363,6 +1363,8 @@ GDScript::GDScript() : GDScriptLanguage::get_singleton()->script_list.add(&script_list); } + + path = vformat("gdscript://%d.gd", get_instance_id()); } void GDScript::_save_orphaned_subclasses(GDScript::ClearData *p_clear_data) { diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 7bde4e7c4b..0c858a36e7 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -68,9 +68,6 @@ static MethodInfo info_from_utility_func(const StringName &p_function) { pi.name = "arg" + itos(i + 1); #endif pi.type = Variant::get_utility_function_argument_type(p_function, i); - if (pi.type == Variant::NIL) { - pi.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; - } info.arguments.push_back(pi); } } @@ -103,8 +100,21 @@ static GDScriptParser::DataType make_native_meta_type(const StringName &p_class_ type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; type.kind = GDScriptParser::DataType::NATIVE; type.builtin_type = Variant::OBJECT; - type.is_constant = true; type.native_type = p_class_name; + type.is_constant = true; + type.is_meta_type = true; + return type; +} + +static GDScriptParser::DataType make_script_meta_type(const Ref<Script> &p_script) { + GDScriptParser::DataType type; + type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + type.kind = GDScriptParser::DataType::SCRIPT; + type.builtin_type = Variant::OBJECT; + type.native_type = p_script->get_instance_base_type(); + type.script_type = p_script; + type.script_path = p_script->get_path(); + type.is_constant = true; type.is_meta_type = true; return type; } @@ -421,7 +431,7 @@ Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_c return err; } base = info_parser->get_parser()->head->get_datatype(); - } else if (class_exists(name) && ClassDB::can_instantiate(name)) { + } else if (class_exists(name)) { base.kind = GDScriptParser::DataType::NATIVE; base.native_type = name; } else { @@ -581,11 +591,8 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type result.builtin_type = GDScriptParser::get_builtin_type(first); if (result.builtin_type == Variant::ARRAY) { - GDScriptParser::DataType container_type = resolve_datatype(p_type->container_type); - + GDScriptParser::DataType container_type = type_from_metatype(resolve_datatype(p_type->container_type)); if (container_type.kind != GDScriptParser::DataType::VARIANT) { - container_type.is_meta_type = false; - container_type.is_constant = false; result.set_container_element_type(container_type); } } @@ -607,12 +614,7 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type } result = ref->get_parser()->head->get_datatype(); } else { - result.kind = GDScriptParser::DataType::SCRIPT; - result.script_type = ResourceLoader::load(path, "Script"); - result.native_type = result.script_type->get_instance_base_type(); - result.script_path = path; - result.is_constant = true; - result.is_meta_type = false; + result = make_script_meta_type(ResourceLoader::load(path, "Script")); } } } else if (ProjectSettings::get_singleton()->has_autoload(first) && ProjectSettings::get_singleton()->get_autoload(first).is_singleton) { @@ -656,7 +658,6 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type case GDScriptParser::ClassNode::Member::CONSTANT: if (member.get_datatype().is_meta_type) { result = member.get_datatype(); - result.is_meta_type = false; found = true; break; } else if (Ref<Script>(member.constant->initializer->reduced_value).is_valid()) { @@ -668,15 +669,8 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type return bad_type; } result = ref->get_parser()->head->get_datatype(); - result.is_meta_type = false; } else { - Ref<Script> script = member.constant->initializer->reduced_value; - result.kind = GDScriptParser::DataType::SCRIPT; - result.builtin_type = Variant::OBJECT; - result.script_type = script; - result.script_path = script->get_path(); - result.native_type = script->get_instance_base_type(); - result.is_meta_type = false; + result = make_script_meta_type(member.constant->initializer->reduced_value); } found = true; break; @@ -832,8 +826,7 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class, for (int j = 0; j < member.signal->parameters.size(); j++) { GDScriptParser::ParameterNode *param = member.signal->parameters[j]; - GDScriptParser::DataType param_type = resolve_datatype(param->datatype_specifier); - param_type.is_meta_type = false; + GDScriptParser::DataType param_type = type_from_metatype(resolve_datatype(param->datatype_specifier)); param->set_datatype(param_type); mi.arguments.push_back(PropertyInfo(param_type.builtin_type, param->identifier->name)); // TODO: add signal parameter default values @@ -1518,14 +1511,12 @@ void GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode *p_assi GDScriptParser::DataType type; type.kind = GDScriptParser::DataType::VARIANT; - bool is_variable = p_assignable->type == GDScriptParser::Node::VARIABLE; bool is_constant = p_assignable->type == GDScriptParser::Node::CONSTANT; GDScriptParser::DataType specified_type; bool has_specified_type = p_assignable->datatype_specifier != nullptr; if (has_specified_type) { - specified_type = resolve_datatype(p_assignable->datatype_specifier); - specified_type.is_meta_type = false; + specified_type = type_from_metatype(resolve_datatype(p_assignable->datatype_specifier)); type = specified_type; } @@ -1581,13 +1572,14 @@ void GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode *p_assi } else if (!specified_type.is_variant()) { if (initializer_type.is_variant() || !initializer_type.is_hard_type()) { mark_node_unsafe(p_assignable->initializer); - if (is_variable) { - static_cast<GDScriptParser::VariableNode *>(p_assignable)->use_conversion_assign = true; + p_assignable->use_conversion_assign = true; + if (!initializer_type.is_variant() && !is_type_compatible(specified_type, initializer_type, true, p_assignable->initializer)) { + downgrade_node_type_source(p_assignable->initializer); } } else if (!is_type_compatible(specified_type, initializer_type, true, p_assignable->initializer)) { - if (is_variable && is_type_compatible(initializer_type, specified_type, true, p_assignable->initializer)) { + if (!is_constant && is_type_compatible(initializer_type, specified_type, true, p_assignable->initializer)) { mark_node_unsafe(p_assignable->initializer); - static_cast<GDScriptParser::VariableNode *>(p_assignable)->use_conversion_assign = true; + p_assignable->use_conversion_assign = true; } 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); } @@ -2108,84 +2100,86 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig GDScriptParser::DataType assigned_value_type = p_assignment->assigned_value->get_datatype(); + bool assignee_is_variant = assignee_type.is_variant(); + bool assignee_is_hard = assignee_type.is_hard_type(); + bool assigned_is_variant = assigned_value_type.is_variant(); + bool assigned_is_hard = assigned_value_type.is_hard_type(); bool compatible = true; + bool downgrades_assignee = false; + bool downgrades_assigned = false; GDScriptParser::DataType op_type = assigned_value_type; if (p_assignment->operation != GDScriptParser::AssignmentNode::OP_NONE && !op_type.is_variant()) { op_type = get_operation_type(p_assignment->variant_op, assignee_type, assigned_value_type, compatible, p_assignment->assigned_value); + + if (assignee_is_variant) { + // variant assignee + mark_node_unsafe(p_assignment); + } else if (!compatible) { + // incompatible hard types and non-variant assignee + mark_node_unsafe(p_assignment); + if (assigned_is_variant) { + // incompatible hard non-variant assignee and hard variant assigned + p_assignment->use_conversion_assign = true; + } else { + // incompatible hard non-variant types + push_error(vformat(R"(Invalid operands "%s" and "%s" for assignment operator.)", assignee_type.to_string(), assigned_value_type.to_string()), p_assignment); + } + } else if (op_type.type_source == GDScriptParser::DataType::UNDETECTED && !assigned_is_variant) { + // incompatible non-variant types (at least one weak) + downgrades_assignee = !assignee_is_hard; + downgrades_assigned = !assigned_is_hard; + } } p_assignment->set_datatype(op_type); - // If Assignee is a variant, then you can assign anything - // When the assigned value has a known type, further checks are possible. - if (assignee_type.is_hard_type() && !assignee_type.is_variant() && op_type.is_hard_type() && !op_type.is_variant()) { - if (compatible) { - compatible = is_type_compatible(assignee_type, op_type, true, p_assignment->assigned_value); - if (!compatible) { - // Try reverse test since it can be a masked subtype. - if (!is_type_compatible(op_type, assignee_type, true)) { - push_error(vformat(R"(Value of type "%s" cannot be assigned to a variable of type "%s".)", assigned_value_type.to_string(), assignee_type.to_string()), p_assignment->assigned_value); - } else { - // TODO: Add warning. - mark_node_unsafe(p_assignment); - p_assignment->use_conversion_assign = true; - } - } - } else { - push_error(vformat(R"(Invalid operands "%s" and "%s" for assignment operator.)", assignee_type.to_string(), assigned_value_type.to_string()), p_assignment); + if (assignee_is_variant) { + if (!assignee_is_hard) { + // weak variant assignee + mark_node_unsafe(p_assignment); } - } else if (assignee_type.is_hard_type() && !assignee_type.is_variant()) { - mark_node_unsafe(p_assignment); - p_assignment->use_conversion_assign = true; } else { - mark_node_unsafe(p_assignment); - if (assignee_type.is_hard_type() && !assignee_type.is_variant()) { + if (assignee_is_hard && !assigned_is_hard) { + // hard non-variant assignee and weak assigned + mark_node_unsafe(p_assignment); p_assignment->use_conversion_assign = true; - } - } - - if (p_assignment->assignee->type == GDScriptParser::Node::IDENTIFIER) { - // Change source type so it's not wrongly detected later. - GDScriptParser::IdentifierNode *identifier = static_cast<GDScriptParser::IdentifierNode *>(p_assignment->assignee); - - switch (identifier->source) { - case GDScriptParser::IdentifierNode::MEMBER_VARIABLE: { - GDScriptParser::DataType id_type = identifier->variable_source->get_datatype(); - if (!id_type.is_hard_type()) { - id_type.kind = GDScriptParser::DataType::VARIANT; - id_type.type_source = GDScriptParser::DataType::UNDETECTED; - identifier->variable_source->set_datatype(id_type); - } - } break; - case GDScriptParser::IdentifierNode::FUNCTION_PARAMETER: { - GDScriptParser::DataType id_type = identifier->parameter_source->get_datatype(); - if (!id_type.is_hard_type()) { - id_type.kind = GDScriptParser::DataType::VARIANT; - id_type.type_source = GDScriptParser::DataType::UNDETECTED; - identifier->parameter_source->set_datatype(id_type); - } - } break; - case GDScriptParser::IdentifierNode::LOCAL_VARIABLE: { - GDScriptParser::DataType id_type = identifier->variable_source->get_datatype(); - if (!id_type.is_hard_type()) { - id_type.kind = GDScriptParser::DataType::VARIANT; - id_type.type_source = GDScriptParser::DataType::UNDETECTED; - identifier->variable_source->set_datatype(id_type); + downgrades_assigned = downgrades_assigned || (!assigned_is_variant && !is_type_compatible(assignee_type, op_type, true, p_assignment->assigned_value)); + } else if (compatible) { + if (op_type.is_variant()) { + // non-variant assignee and variant result + mark_node_unsafe(p_assignment); + if (assignee_is_hard) { + // hard non-variant assignee and variant result + p_assignment->use_conversion_assign = true; + } else { + // weak non-variant assignee and variant result + downgrades_assignee = true; } - } break; - case GDScriptParser::IdentifierNode::LOCAL_ITERATOR: { - GDScriptParser::DataType id_type = identifier->bind_source->get_datatype(); - if (!id_type.is_hard_type()) { - id_type.kind = GDScriptParser::DataType::VARIANT; - id_type.type_source = GDScriptParser::DataType::UNDETECTED; - identifier->bind_source->set_datatype(id_type); + } else if (!is_type_compatible(assignee_type, op_type, assignee_is_hard, p_assignment->assigned_value)) { + // non-variant assignee and incompatible result + mark_node_unsafe(p_assignment); + if (assignee_is_hard) { + if (is_type_compatible(op_type, assignee_type, true, p_assignment->assigned_value)) { + // hard non-variant assignee and maybe compatible result + p_assignment->use_conversion_assign = true; + } else { + // hard non-variant assignee and incompatible result + push_error(vformat(R"(Value of type "%s" cannot be assigned to a variable of type "%s".)", assigned_value_type.to_string(), assignee_type.to_string()), p_assignment->assigned_value); + } + } else { + // weak non-variant assignee and incompatible result + downgrades_assignee = true; } - } break; - default: - // Nothing to do. - break; + } } } + if (downgrades_assignee) { + downgrade_node_type_source(p_assignment->assignee); + } + if (downgrades_assigned) { + downgrade_node_type_source(p_assignment->assigned_value); + } + #ifdef DEBUG_ENABLED if (assignee_type.is_hard_type() && assignee_type.builtin_type == Variant::INT && assigned_value_type.builtin_type == Variant::FLOAT) { parser->push_warning(p_assignment->assigned_value, GDScriptWarning::NARROWING_CONVERSION); @@ -2464,7 +2458,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a bool types_match = true; for (int i = 0; i < p_call->arguments.size(); i++) { - GDScriptParser::DataType par_type = type_from_property(info.arguments[i]); + GDScriptParser::DataType par_type = type_from_property(info.arguments[i], true); if (!is_type_compatible(par_type, p_call->arguments[i]->get_datatype(), true)) { types_match = false; @@ -2521,7 +2515,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: { PropertyInfo wrong_arg = function_info.arguments[err.argument]; push_error(vformat(R"*(Invalid argument for "%s()" function: argument %d should be "%s" but is "%s".)*", function_name, err.argument + 1, - type_from_property(wrong_arg).to_string(), p_call->arguments[err.argument]->get_datatype().to_string()), + type_from_property(wrong_arg, true).to_string(), p_call->arguments[err.argument]->get_datatype().to_string()), p_call->arguments[err.argument]); } break; case Callable::CallError::CALL_ERROR_INVALID_METHOD: @@ -2568,7 +2562,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: { String expected_type_name; if (err.argument < function_info.arguments.size()) { - expected_type_name = type_from_property(function_info.arguments[err.argument]).to_string(); + expected_type_name = type_from_property(function_info.arguments[err.argument], true).to_string(); } else { expected_type_name = Variant::get_type_name((Variant::Type)err.expected); } @@ -2649,6 +2643,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a } else { reduce_expression(subscript->base); base_type = subscript->base->get_datatype(); + is_self = subscript->base->type == GDScriptParser::Node::SELF; } } else { // Invalid call. Error already sent in parser. @@ -2766,14 +2761,13 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a void GDScriptAnalyzer::reduce_cast(GDScriptParser::CastNode *p_cast) { reduce_expression(p_cast->operand); - GDScriptParser::DataType cast_type = resolve_datatype(p_cast->cast_type); + GDScriptParser::DataType cast_type = type_from_metatype(resolve_datatype(p_cast->cast_type)); if (!cast_type.is_set()) { mark_node_unsafe(p_cast); return; } - cast_type = type_from_metatype(cast_type); // The casted value won't be a type name. p_cast->set_datatype(cast_type); if (!cast_type.is_variant()) { @@ -2907,15 +2901,7 @@ GDScriptParser::DataType GDScriptAnalyzer::make_global_class_meta_type(const Str return ref->get_parser()->head->get_datatype(); } else { - type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; - type.kind = GDScriptParser::DataType::SCRIPT; - type.builtin_type = Variant::OBJECT; - type.script_type = ResourceLoader::load(path, "Script"); - type.native_type = type.script_type->get_instance_base_type(); - type.script_path = path; - type.is_constant = true; - type.is_meta_type = true; - return type; + return make_script_meta_type(ResourceLoader::load(path, "Script")); } } @@ -3938,10 +3924,10 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_metatype(const GDScriptPars return result; } -GDScriptParser::DataType GDScriptAnalyzer::type_from_property(const PropertyInfo &p_property) const { +GDScriptParser::DataType GDScriptAnalyzer::type_from_property(const PropertyInfo &p_property, bool p_is_arg) const { GDScriptParser::DataType result; result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; - if (p_property.type == Variant::NIL && (p_property.usage & PROPERTY_USAGE_NIL_IS_VARIANT)) { + if (p_property.type == Variant::NIL && (p_is_arg || (p_property.usage & PROPERTY_USAGE_NIL_IS_VARIANT))) { // Variant result.kind = GDScriptParser::DataType::VARIANT; return result; @@ -4030,6 +4016,25 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, bo return false; } + StringName base_native = p_base_type.native_type; + if (base_native != StringName()) { + // Empty native class might happen in some Script implementations. + // Just ignore it. + if (!class_exists(base_native)) { + push_error(vformat("Native class %s used in script doesn't exist or isn't exposed.", base_native), p_source); + return false; + } else if (p_is_constructor && !ClassDB::can_instantiate(base_native)) { + if (p_base_type.kind == GDScriptParser::DataType::CLASS) { + push_error(vformat(R"(Class "%s" cannot be constructed as it is based on abstract native class "%s".)", p_base_type.class_type->fqcn.get_file(), base_native), p_source); + } else if (p_base_type.kind == GDScriptParser::DataType::SCRIPT) { + push_error(vformat(R"(Script "%s" cannot be constructed as it is based on abstract native class "%s".)", p_base_type.script_path.get_file(), base_native), p_source); + } else { + push_error(vformat(R"(Native class "%s" cannot be constructed as it is abstract.)", base_native), p_source); + } + return false; + } + } + if (p_is_constructor) { function_name = "_init"; r_static = true; @@ -4090,17 +4095,6 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, bo } } - StringName base_native = p_base_type.native_type; -#ifdef DEBUG_ENABLED - if (base_native != StringName()) { - // Empty native class might happen in some Script implementations. - // Just ignore it. - if (!class_exists(base_native)) { - ERR_FAIL_V_MSG(false, vformat("Native class %s used in script doesn't exist or isn't exposed.", base_native)); - } - } -#endif - if (p_is_constructor) { // Native types always have a default constructor. r_return_type = p_base_type; @@ -4128,7 +4122,7 @@ bool GDScriptAnalyzer::function_signature_from_info(const MethodInfo &p_info, GD r_static = (p_info.flags & METHOD_FLAG_STATIC) != 0; for (const PropertyInfo &E : p_info.arguments) { - r_par_types.push_back(type_from_property(E)); + r_par_types.push_back(type_from_property(E, true)); } return true; } @@ -4137,7 +4131,7 @@ bool GDScriptAnalyzer::validate_call_arg(const MethodInfo &p_method, const GDScr List<GDScriptParser::DataType> arg_types; for (const PropertyInfo &E : p_method.arguments) { - arg_types.push_back(type_from_property(E)); + arg_types.push_back(type_from_property(E, true)); } return validate_call_arg(arg_types, p_method.default_arguments.size(), (p_method.flags & METHOD_FLAG_VARARG) != 0, p_call); @@ -4255,9 +4249,6 @@ GDScriptParser::DataType GDScriptAnalyzer::get_operation_type(Variant::Operator } GDScriptParser::DataType GDScriptAnalyzer::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 result; - result.kind = GDScriptParser::DataType::VARIANT; - Variant::Type a_type = p_a.builtin_type; Variant::Type b_type = p_b.builtin_type; @@ -4277,28 +4268,18 @@ GDScriptParser::DataType GDScriptAnalyzer::get_operation_type(Variant::Operator } Variant::ValidatedOperatorEvaluator op_eval = Variant::get_validated_operator_evaluator(p_operation, a_type, b_type); - bool hard_operation = p_a.is_hard_type() && p_b.is_hard_type(); bool validated = op_eval != nullptr; - if (hard_operation && !validated) { - r_valid = false; - return result; - } else if (hard_operation && validated) { + GDScriptParser::DataType result; + if (validated) { r_valid = true; - result.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED; + result.type_source = hard_operation ? GDScriptParser::DataType::ANNOTATED_INFERRED : GDScriptParser::DataType::INFERRED; result.kind = GDScriptParser::DataType::BUILTIN; result.builtin_type = Variant::get_operator_return_type(p_operation, a_type, b_type); - } else if (!hard_operation && !validated) { - r_valid = true; - result.type_source = GDScriptParser::DataType::UNDETECTED; + } else { + r_valid = !hard_operation; result.kind = GDScriptParser::DataType::VARIANT; - result.builtin_type = Variant::NIL; - } else if (!hard_operation && validated) { - r_valid = true; - result.type_source = GDScriptParser::DataType::INFERRED; - result.kind = GDScriptParser::DataType::BUILTIN; - result.builtin_type = Variant::get_operator_return_type(p_operation, a_type, b_type); } return result; @@ -4474,6 +4455,46 @@ void GDScriptAnalyzer::mark_node_unsafe(const GDScriptParser::Node *p_node) { #endif } +void GDScriptAnalyzer::downgrade_node_type_source(GDScriptParser::Node *p_node) { + GDScriptParser::IdentifierNode *identifier = nullptr; + if (p_node->type == GDScriptParser::Node::IDENTIFIER) { + identifier = static_cast<GDScriptParser::IdentifierNode *>(p_node); + } else if (p_node->type == GDScriptParser::Node::SUBSCRIPT) { + GDScriptParser::SubscriptNode *subscript = static_cast<GDScriptParser::SubscriptNode *>(p_node); + if (subscript->is_attribute) { + identifier = subscript->attribute; + } + } + if (identifier == nullptr) { + return; + } + + GDScriptParser::Node *source = nullptr; + switch (identifier->source) { + case GDScriptParser::IdentifierNode::MEMBER_VARIABLE: { + source = identifier->variable_source; + } break; + case GDScriptParser::IdentifierNode::FUNCTION_PARAMETER: { + source = identifier->parameter_source; + } break; + case GDScriptParser::IdentifierNode::LOCAL_VARIABLE: { + source = identifier->variable_source; + } break; + case GDScriptParser::IdentifierNode::LOCAL_ITERATOR: { + source = identifier->bind_source; + } break; + default: + break; + } + if (source == nullptr) { + return; + } + + GDScriptParser::DataType datatype; + datatype.kind = GDScriptParser::DataType::VARIANT; + source->set_datatype(datatype); +} + void GDScriptAnalyzer::mark_lambda_use_self() { for (GDScriptParser::LambdaNode *lambda : lambda_stack) { lambda->use_self = true; diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h index ecae0b4629..b22d47982f 100644 --- a/modules/gdscript/gdscript_analyzer.h +++ b/modules/gdscript/gdscript_analyzer.h @@ -108,7 +108,7 @@ class GDScriptAnalyzer { // Helpers. 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) const; + GDScriptParser::DataType type_from_property(const PropertyInfo &p_property, bool p_is_arg = false) const; GDScriptParser::DataType make_global_class_meta_type(const StringName &p_class_name, const GDScriptParser::Node *p_source); bool get_function_signature(GDScriptParser::Node *p_source, bool p_is_constructor, GDScriptParser::DataType base_type, const StringName &p_function, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg); bool function_signature_from_info(const MethodInfo &p_info, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg); @@ -120,6 +120,7 @@ class GDScriptAnalyzer { 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); + void downgrade_node_type_source(GDScriptParser::Node *p_node); void mark_lambda_use_self(); bool class_exists(const StringName &p_class) const; Ref<GDScriptParserRef> get_parser_for(const String &p_path); diff --git a/modules/gdscript/gdscript_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp index 15a17edb65..6c80fb7665 100644 --- a/modules/gdscript/gdscript_byte_codegen.cpp +++ b/modules/gdscript/gdscript_byte_codegen.cpp @@ -872,8 +872,12 @@ void GDScriptByteCodeGenerator::write_assign_false(const Address &p_target) { append(p_target); } -void GDScriptByteCodeGenerator::write_assign_default_parameter(const Address &p_dst, const Address &p_src) { - write_assign(p_dst, p_src); +void GDScriptByteCodeGenerator::write_assign_default_parameter(const Address &p_dst, const Address &p_src, bool p_use_conversion) { + if (p_use_conversion) { + write_assign_with_conversion(p_dst, p_src); + } else { + write_assign(p_dst, p_src); + } function->default_arguments.push_back(opcodes.size()); } @@ -920,11 +924,17 @@ void GDScriptByteCodeGenerator::write_cast(const Address &p_target, const Addres append(index); } -GDScriptCodeGenerator::Address GDScriptByteCodeGenerator::get_call_target(const GDScriptCodeGenerator::Address &p_target) { +GDScriptCodeGenerator::Address GDScriptByteCodeGenerator::get_call_target(const GDScriptCodeGenerator::Address &p_target, Variant::Type p_type) { if (p_target.mode == Address::NIL) { - uint32_t addr = add_temporary(p_target.type); + GDScriptDataType type; + if (p_type != Variant::NIL) { + type.has_type = true; + type.kind = GDScriptDataType::BUILTIN; + type.builtin_type = p_type; + } + uint32_t addr = add_temporary(type); pop_temporary(); - return Address(Address::TEMPORARY, addr, p_target.type); + return Address(Address::TEMPORARY, addr, type); } else { return p_target; } @@ -989,11 +999,17 @@ void GDScriptByteCodeGenerator::write_call_utility(const Address &p_target, cons } if (is_validated) { + Variant::Type result_type = Variant::has_utility_function_return_value(p_function) ? Variant::get_utility_function_return_type(p_function) : Variant::NIL; + Address target = get_call_target(p_target, result_type); + Variant::Type temp_type = temporaries[target.address].type; + if (result_type != temp_type) { + write_type_adjust(target, result_type); + } append_opcode_and_argcount(GDScriptFunction::OPCODE_CALL_UTILITY_VALIDATED, 1 + p_arguments.size()); for (int i = 0; i < p_arguments.size(); i++) { append(p_arguments[i]); } - append(get_call_target(p_target)); + append(target); append(p_arguments.size()); append(Variant::get_validated_utility_function(p_function)); } else { @@ -1007,7 +1023,7 @@ void GDScriptByteCodeGenerator::write_call_utility(const Address &p_target, cons } } -void GDScriptByteCodeGenerator::write_call_builtin_type(const Address &p_target, const Address &p_base, Variant::Type p_type, const StringName &p_method, const Vector<Address> &p_arguments) { +void GDScriptByteCodeGenerator::write_call_builtin_type(const Address &p_target, const Address &p_base, Variant::Type p_type, const StringName &p_method, bool p_is_static, const Vector<Address> &p_arguments) { bool is_validated = false; // Check if all types are correct. @@ -1027,16 +1043,26 @@ void GDScriptByteCodeGenerator::write_call_builtin_type(const Address &p_target, if (!is_validated) { // Perform regular call. - write_call(p_target, p_base, p_method, p_arguments); + if (p_is_static) { + append_opcode_and_argcount(GDScriptFunction::OPCODE_CALL_BUILTIN_STATIC, p_arguments.size() + 1); + for (int i = 0; i < p_arguments.size(); i++) { + append(p_arguments[i]); + } + append(get_call_target(p_target)); + append(p_type); + append(p_method); + append(p_arguments.size()); + } else { + write_call(p_target, p_base, p_method, p_arguments); + } return; } - if (p_target.mode == Address::TEMPORARY) { - Variant::Type result_type = Variant::get_builtin_method_return_type(p_type, p_method); - Variant::Type temp_type = temporaries[p_target.address].type; - if (result_type != temp_type) { - write_type_adjust(p_target, result_type); - } + Variant::Type result_type = Variant::get_builtin_method_return_type(p_type, p_method); + Address target = get_call_target(p_target, result_type); + Variant::Type temp_type = temporaries[target.address].type; + if (result_type != temp_type) { + write_type_adjust(target, result_type); } append_opcode_and_argcount(GDScriptFunction::OPCODE_CALL_BUILTIN_TYPE_VALIDATED, 2 + p_arguments.size()); @@ -1045,59 +1071,17 @@ void GDScriptByteCodeGenerator::write_call_builtin_type(const Address &p_target, append(p_arguments[i]); } append(p_base); - append(get_call_target(p_target)); + append(target); append(p_arguments.size()); append(Variant::get_validated_builtin_method(p_type, p_method)); } -void GDScriptByteCodeGenerator::write_call_builtin_type_static(const Address &p_target, Variant::Type p_type, const StringName &p_method, const Vector<Address> &p_arguments) { - bool is_validated = false; - - // Check if all types are correct. - if (Variant::is_builtin_method_vararg(p_type, p_method)) { - is_validated = true; // Vararg works fine with any argument, since they can be any type. - } else if (p_arguments.size() == Variant::get_builtin_method_argument_count(p_type, p_method)) { - bool all_types_exact = true; - for (int i = 0; i < p_arguments.size(); i++) { - if (!IS_BUILTIN_TYPE(p_arguments[i], Variant::get_builtin_method_argument_type(p_type, p_method, i))) { - all_types_exact = false; - break; - } - } - - is_validated = all_types_exact; - } - - if (!is_validated) { - // Perform regular call. - append_opcode_and_argcount(GDScriptFunction::OPCODE_CALL_BUILTIN_STATIC, p_arguments.size() + 1); - for (int i = 0; i < p_arguments.size(); i++) { - append(p_arguments[i]); - } - append(get_call_target(p_target)); - append(p_type); - append(p_method); - append(p_arguments.size()); - return; - } - - if (p_target.mode == Address::TEMPORARY) { - Variant::Type result_type = Variant::get_builtin_method_return_type(p_type, p_method); - Variant::Type temp_type = temporaries[p_target.address].type; - if (result_type != temp_type) { - write_type_adjust(p_target, result_type); - } - } - - append_opcode_and_argcount(GDScriptFunction::OPCODE_CALL_BUILTIN_TYPE_VALIDATED, 2 + p_arguments.size()); +void GDScriptByteCodeGenerator::write_call_builtin_type(const Address &p_target, const Address &p_base, Variant::Type p_type, const StringName &p_method, const Vector<Address> &p_arguments) { + write_call_builtin_type(p_target, p_base, p_type, p_method, false, p_arguments); +} - for (int i = 0; i < p_arguments.size(); i++) { - append(p_arguments[i]); - } - append(Address()); // No base since it's static. - append(get_call_target(p_target)); - append(p_arguments.size()); - append(Variant::get_validated_builtin_method(p_type, p_method)); +void GDScriptByteCodeGenerator::write_call_builtin_type_static(const Address &p_target, Variant::Type p_type, const StringName &p_method, const Vector<Address> &p_arguments) { + write_call_builtin_type(p_target, Address(), p_type, p_method, true, p_arguments); } void GDScriptByteCodeGenerator::write_call_native_static(const Address &p_target, const StringName &p_class, const StringName &p_method, const Vector<Address> &p_arguments) { diff --git a/modules/gdscript/gdscript_byte_codegen.h b/modules/gdscript/gdscript_byte_codegen.h index 258b9fb0c2..171c505116 100644 --- a/modules/gdscript/gdscript_byte_codegen.h +++ b/modules/gdscript/gdscript_byte_codegen.h @@ -309,7 +309,7 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator { } } - Address get_call_target(const Address &p_target); + Address get_call_target(const Address &p_target, Variant::Type p_type = Variant::NIL); int address_of(const Address &p_address) { switch (p_address.mode) { @@ -460,7 +460,7 @@ public: virtual void write_assign_with_conversion(const Address &p_target, const Address &p_source) override; virtual void write_assign_true(const Address &p_target) override; virtual void write_assign_false(const Address &p_target) override; - virtual void write_assign_default_parameter(const Address &p_dst, const Address &p_src) override; + virtual void write_assign_default_parameter(const Address &p_dst, const Address &p_src, bool p_use_conversion) override; virtual void write_store_global(const Address &p_dst, int p_global_index) override; virtual void write_store_named_global(const Address &p_dst, const StringName &p_global) override; virtual void write_cast(const Address &p_target, const Address &p_source, const GDScriptDataType &p_type) override; @@ -469,6 +469,7 @@ public: virtual void write_call_async(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) override; virtual void write_call_utility(const Address &p_target, const StringName &p_function, const Vector<Address> &p_arguments) override; virtual void write_call_gdscript_utility(const Address &p_target, GDScriptUtilityFunctions::FunctionPtr p_function, const Vector<Address> &p_arguments) override; + void write_call_builtin_type(const Address &p_target, const Address &p_base, Variant::Type p_type, const StringName &p_method, bool p_is_static, const Vector<Address> &p_arguments); virtual void write_call_builtin_type(const Address &p_target, const Address &p_base, Variant::Type p_type, const StringName &p_method, const Vector<Address> &p_arguments) override; virtual void write_call_builtin_type_static(const Address &p_target, Variant::Type p_type, const StringName &p_method, const Vector<Address> &p_arguments) override; virtual void write_call_native_static(const Address &p_target, const StringName &p_class, const StringName &p_method, const Vector<Address> &p_arguments) override; diff --git a/modules/gdscript/gdscript_codegen.h b/modules/gdscript/gdscript_codegen.h index c7a1bcb9e9..e885938eba 100644 --- a/modules/gdscript/gdscript_codegen.h +++ b/modules/gdscript/gdscript_codegen.h @@ -113,7 +113,7 @@ public: virtual void write_assign_with_conversion(const Address &p_target, const Address &p_source) = 0; virtual void write_assign_true(const Address &p_target) = 0; virtual void write_assign_false(const Address &p_target) = 0; - virtual void write_assign_default_parameter(const Address &dst, const Address &src) = 0; + virtual void write_assign_default_parameter(const Address &dst, const Address &src, bool p_use_conversion) = 0; virtual void write_store_global(const Address &p_dst, int p_global_index) = 0; virtual void write_store_named_global(const Address &p_dst, const StringName &p_global) = 0; virtual void write_cast(const Address &p_target, const Address &p_source, const GDScriptDataType &p_type) = 0; diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index beed6e90d2..77c6690d20 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -2116,7 +2116,7 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_ } } - codegen.generator->write_assign_default_parameter(dst_addr, src_addr); + 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_parser.cpp b/modules/gdscript/gdscript_parser.cpp index a6b8537074..bbea6fe857 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -1321,14 +1321,8 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum() { if (elements.has(item.identifier->name)) { push_error(vformat(R"(Name "%s" was already in this enum (at line %d).)", item.identifier->name, elements[item.identifier->name]), item.identifier); } else if (!named) { - // TODO: Abstract this recursive member check. - ClassNode *parent = current_class; - while (parent != nullptr) { - if (parent->members_indices.has(item.identifier->name)) { - push_error(vformat(R"(Name "%s" is already used as a class %s.)", item.identifier->name, parent->get_member(item.identifier->name).get_type_name())); - break; - } - parent = parent->outer; + if (current_class->members_indices.has(item.identifier->name)) { + push_error(vformat(R"(Name "%s" is already used as a class %s.)", item.identifier->name, current_class->get_member(item.identifier->name).get_type_name())); } } diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index 4bb02c4ea3..0903f62061 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -360,6 +360,7 @@ public: ExpressionNode *initializer = nullptr; TypeNode *datatype_specifier = nullptr; bool infer_datatype = false; + bool use_conversion_assign = false; int usages = 0; virtual ~AssignableNode() {} @@ -1182,7 +1183,6 @@ public: bool onready = false; PropertyInfo export_info; int assignments = 0; - bool use_conversion_assign = false; #ifdef TOOLS_ENABLED String doc_description; #endif // TOOLS_ENABLED diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp index 04612c6793..e17a804003 100644 --- a/modules/gdscript/gdscript_tokenizer.cpp +++ b/modules/gdscript/gdscript_tokenizer.cpp @@ -164,6 +164,7 @@ bool GDScriptTokenizer::Token::is_identifier() const { switch (type) { case IDENTIFIER: case MATCH: // Used in String.match(). + case CONST_INF: // Used in Vector{2,3,4}.INF return true; default: return false; diff --git a/modules/gdscript/tests/scripts/analyzer/errors/abstract_class_instantiate.gd b/modules/gdscript/tests/scripts/analyzer/errors/abstract_class_instantiate.gd new file mode 100644 index 0000000000..38c2faa859 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/abstract_class_instantiate.gd @@ -0,0 +1,2 @@ +func test(): + CanvasItem.new() diff --git a/modules/gdscript/tests/scripts/analyzer/errors/abstract_class_instantiate.out b/modules/gdscript/tests/scripts/analyzer/errors/abstract_class_instantiate.out new file mode 100644 index 0000000000..9eff912b59 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/abstract_class_instantiate.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Native class "CanvasItem" cannot be constructed as it is abstract. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/abstract_script_instantiate.gd b/modules/gdscript/tests/scripts/analyzer/errors/abstract_script_instantiate.gd new file mode 100644 index 0000000000..118e7e8a45 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/abstract_script_instantiate.gd @@ -0,0 +1,9 @@ +class A extends CanvasItem: + func _init(): + print('no') + +class B extends A: + pass + +func test(): + B.new() diff --git a/modules/gdscript/tests/scripts/analyzer/errors/abstract_script_instantiate.out b/modules/gdscript/tests/scripts/analyzer/errors/abstract_script_instantiate.out new file mode 100644 index 0000000000..8b956f5974 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/abstract_script_instantiate.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Class "abstract_script_instantiate.gd::B" cannot be constructed as it is based on abstract native class "CanvasItem". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_shadows_outer_enum.gd b/modules/gdscript/tests/scripts/analyzer/errors/enum_shadows_outer_enum.gd deleted file mode 100644 index f3f3b5ffeb..0000000000 --- a/modules/gdscript/tests/scripts/analyzer/errors/enum_shadows_outer_enum.gd +++ /dev/null @@ -1,7 +0,0 @@ -enum { V } - -class InnerClass: - enum { V } - -func test(): - pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_shadows_outer_enum.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_shadows_outer_enum.out deleted file mode 100644 index c9706003e1..0000000000 --- a/modules/gdscript/tests/scripts/analyzer/errors/enum_shadows_outer_enum.out +++ /dev/null @@ -1,2 +0,0 @@ -GDTEST_PARSER_ERROR -Name "V" is already used as a class enum value. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/incompatible_assignment.gd b/modules/gdscript/tests/scripts/analyzer/errors/incompatible_assignment.gd new file mode 100644 index 0000000000..664e364493 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/incompatible_assignment.gd @@ -0,0 +1,3 @@ +func test(): + var foo: bool = true + foo += 'bar' diff --git a/modules/gdscript/tests/scripts/analyzer/errors/incompatible_assignment.out b/modules/gdscript/tests/scripts/analyzer/errors/incompatible_assignment.out new file mode 100644 index 0000000000..358b096a64 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/incompatible_assignment.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Invalid operands "bool" and "String" for assignment operator. diff --git a/modules/gdscript/tests/scripts/analyzer/features/assignments_with_untyped.gd b/modules/gdscript/tests/scripts/analyzer/features/assignments_with_untyped.gd new file mode 100644 index 0000000000..2d2c2bef19 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/assignments_with_untyped.gd @@ -0,0 +1,19 @@ +func test(): + var one_0 = 0 + one_0 = 1 + var one_1 := one_0 + print(one_1) + + var two: Variant = 0 + two += 2 + print(two) + + var three_0: Variant = 1 + var three_1: int = 2 + three_0 += three_1 + print(three_0) + + var four_0: int = 3 + var four_1: Variant = 1 + four_0 += four_1 + print(four_0) diff --git a/modules/gdscript/tests/scripts/analyzer/features/assignments_with_untyped.out b/modules/gdscript/tests/scripts/analyzer/features/assignments_with_untyped.out new file mode 100644 index 0000000000..7536c38490 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/assignments_with_untyped.out @@ -0,0 +1,5 @@ +GDTEST_OK +1 +2 +3 +4 diff --git a/modules/gdscript/tests/scripts/analyzer/features/extend_abstract_class.gd b/modules/gdscript/tests/scripts/analyzer/features/extend_abstract_class.gd new file mode 100644 index 0000000000..95c3268130 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/extend_abstract_class.gd @@ -0,0 +1,12 @@ +class A extends CanvasItem: + func _init(): + pass + +class B extends A: + pass + +class C extends CanvasItem: + pass + +func test(): + print('ok') diff --git a/modules/gdscript/tests/scripts/analyzer/features/extend_abstract_class.out b/modules/gdscript/tests/scripts/analyzer/features/extend_abstract_class.out new file mode 100644 index 0000000000..1b47ed10dc --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/extend_abstract_class.out @@ -0,0 +1,2 @@ +GDTEST_OK +ok diff --git a/modules/gdscript/tests/scripts/analyzer/features/variant_arg_in_virtual_method.gd b/modules/gdscript/tests/scripts/analyzer/features/variant_arg_in_virtual_method.gd new file mode 100644 index 0000000000..da24c06b2e --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/variant_arg_in_virtual_method.gd @@ -0,0 +1,6 @@ +class Check extends Node: + func _set(_property: StringName, _value: Variant) -> bool: + return true + +func test() -> void: + print('OK') diff --git a/modules/gdscript/tests/scripts/analyzer/features/variant_arg_in_virtual_method.out b/modules/gdscript/tests/scripts/analyzer/features/variant_arg_in_virtual_method.out new file mode 100644 index 0000000000..1ccb591560 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/variant_arg_in_virtual_method.out @@ -0,0 +1,2 @@ +GDTEST_OK +OK diff --git a/modules/gdscript/tests/scripts/parser/features/unnamed_enums_outer_conflicts.gd b/modules/gdscript/tests/scripts/parser/features/unnamed_enums_outer_conflicts.gd new file mode 100644 index 0000000000..4cbb464f59 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/unnamed_enums_outer_conflicts.gd @@ -0,0 +1,17 @@ +class A: + enum { X = 1 } + + class B: + enum { X = 2 } + +class C: + const X = 3 + + class D: + enum { X = 4 } + +func test(): + print(A.X) + print(A.B.X) + print(C.X) + print(C.D.X) diff --git a/modules/gdscript/tests/scripts/parser/features/unnamed_enums_outer_conflicts.out b/modules/gdscript/tests/scripts/parser/features/unnamed_enums_outer_conflicts.out new file mode 100644 index 0000000000..7536c38490 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/unnamed_enums_outer_conflicts.out @@ -0,0 +1,5 @@ +GDTEST_OK +1 +2 +3 +4 diff --git a/modules/gdscript/tests/scripts/parser/features/vector_inf.gd b/modules/gdscript/tests/scripts/parser/features/vector_inf.gd new file mode 100644 index 0000000000..039d51d9ed --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/vector_inf.gd @@ -0,0 +1,6 @@ +func test(): + var vec2: = Vector2.INF + var vec3: = Vector3.INF + + print(vec2.x == INF) + print(vec3.z == INF) diff --git a/modules/gdscript/tests/scripts/parser/features/vector_inf.out b/modules/gdscript/tests/scripts/parser/features/vector_inf.out new file mode 100644 index 0000000000..9d111a8322 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/vector_inf.out @@ -0,0 +1,3 @@ +GDTEST_OK +true +true diff --git a/modules/gdscript/tests/scripts/runtime/errors/bad_conversion_for_default_parameter.gd b/modules/gdscript/tests/scripts/runtime/errors/bad_conversion_for_default_parameter.gd new file mode 100644 index 0000000000..a72ac9b5ee --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/bad_conversion_for_default_parameter.gd @@ -0,0 +1,8 @@ +var weakling = 'not float' +func weak(x: float = weakling): + print(x) + print('typeof x is', typeof(x)) + +func test(): + print(typeof(weak())) + print('not ok') diff --git a/modules/gdscript/tests/scripts/runtime/errors/bad_conversion_for_default_parameter.out b/modules/gdscript/tests/scripts/runtime/errors/bad_conversion_for_default_parameter.out new file mode 100644 index 0000000000..8543cf976e --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/bad_conversion_for_default_parameter.out @@ -0,0 +1,8 @@ +GDTEST_RUNTIME_ERROR +>> SCRIPT ERROR +>> on function: weak() +>> runtime/errors/bad_conversion_for_default_parameter.gd +>> 2 +>> Trying to assign value of type 'String' to a variable of type 'float'. +0 +not ok diff --git a/modules/gdscript/tests/scripts/runtime/features/conversion_for_default_parameter.gd b/modules/gdscript/tests/scripts/runtime/features/conversion_for_default_parameter.gd new file mode 100644 index 0000000000..9d0c6317b3 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/conversion_for_default_parameter.gd @@ -0,0 +1,19 @@ +func literal(x: float = 1): + print('x is ', x) + print('typeof x is ', typeof(x)) + +var inferring := 2 +func inferred(x: float = inferring): + print('x is ', x) + print('typeof x is ', typeof(x)) + +var weakling = 3 +func weak(x: float = weakling): + print('x is ', x) + print('typeof x is ', typeof(x)) + +func test(): + literal() + inferred() + weak() + print('ok') diff --git a/modules/gdscript/tests/scripts/runtime/features/conversion_for_default_parameter.out b/modules/gdscript/tests/scripts/runtime/features/conversion_for_default_parameter.out new file mode 100644 index 0000000000..a9ef4919cf --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/conversion_for_default_parameter.out @@ -0,0 +1,8 @@ +GDTEST_OK +x is 1 +typeof x is 3 +x is 2 +typeof x is 3 +x is 3 +typeof x is 3 +ok diff --git a/modules/gdscript/tests/scripts/runtime/features/does_not_override_temp_values.gd b/modules/gdscript/tests/scripts/runtime/features/does_not_override_temp_values.gd new file mode 100644 index 0000000000..1d4b400d81 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/does_not_override_temp_values.gd @@ -0,0 +1,17 @@ +# https://github.com/godotengine/godot/issues/71177 + +func test(): + builtin_method() + builtin_method_static() + print("done") + +func builtin_method(): + var pba := PackedByteArray() + @warning_ignore(return_value_discarded) + pba.resize(1) # Built-in validated. + + +func builtin_method_static(): + var _pba := PackedByteArray() + @warning_ignore(return_value_discarded) + Vector2.from_angle(PI) # Static built-in validated. diff --git a/modules/gdscript/tests/scripts/runtime/features/does_not_override_temp_values.out b/modules/gdscript/tests/scripts/runtime/features/does_not_override_temp_values.out new file mode 100644 index 0000000000..8e68c97774 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/does_not_override_temp_values.out @@ -0,0 +1,2 @@ +GDTEST_OK +done diff --git a/modules/gdscript/tests/scripts/runtime/features/gdscript.gd b/modules/gdscript/tests/scripts/runtime/features/gdscript.gd new file mode 100644 index 0000000000..f2368643de --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/gdscript.gd @@ -0,0 +1,20 @@ +func test(): + var gdscr: = GDScript.new() + gdscr.source_code = ''' +extends Resource + +func test() -> void: + prints("Outer") + var inner = InnerClass.new() + +class InnerClass: + func _init() -> void: + prints("Inner") +''' + @warning_ignore(return_value_discarded) + gdscr.reload() + + var inst = gdscr.new() + + @warning_ignore(unsafe_method_access) + inst.test() diff --git a/modules/gdscript/tests/scripts/runtime/features/gdscript.out b/modules/gdscript/tests/scripts/runtime/features/gdscript.out new file mode 100644 index 0000000000..16114f57f7 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/gdscript.out @@ -0,0 +1,3 @@ +GDTEST_OK +Outer +Inner diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/NodeExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/NodeExtensions.cs index 03996bafdd..8325af034c 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/NodeExtensions.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/NodeExtensions.cs @@ -48,13 +48,8 @@ namespace Godot } /// <summary> - /// Fetches a node. The <see cref="NodePath"/> can be either a relative path (from - /// the current node) or an absolute path (in the scene tree) to a node. If the path - /// does not exist, a <see langword="null"/> instance is returned and an error - /// is logged. Attempts to access methods on the return value will result in an - /// "Attempt to call <method> on a null instance." error. - /// Note: Fetching absolute paths only works when the node is inside the scene tree - /// (see <see cref="IsInsideTree"/>). + /// Similar to <see cref="GetNode"/>, but does not log an error if <paramref name="path"/> + /// does not point to a valid <see cref="Node"/>. /// </summary> /// <example> /// Example: Assume your current node is Character and the following tree: diff --git a/modules/navigation/godot_navigation_server.cpp b/modules/navigation/godot_navigation_server.cpp index b9b92b77c9..9b5d78d465 100644 --- a/modules/navigation/godot_navigation_server.cpp +++ b/modules/navigation/godot_navigation_server.cpp @@ -829,6 +829,15 @@ void GodotNavigationServer::process(real_t p_delta_time) { return; } + int _new_pm_region_count = 0; + int _new_pm_agent_count = 0; + int _new_pm_link_count = 0; + int _new_pm_polygon_count = 0; + int _new_pm_edge_count = 0; + int _new_pm_edge_merge_count = 0; + int _new_pm_edge_connection_count = 0; + int _new_pm_edge_free_count = 0; + // In c++ we can't be sure that this is performed in the main thread // even with mutable functions. MutexLock lock(operations_mutex); @@ -837,6 +846,15 @@ void GodotNavigationServer::process(real_t p_delta_time) { active_maps[i]->step(p_delta_time); active_maps[i]->dispatch_callbacks(); + _new_pm_region_count += active_maps[i]->get_pm_region_count(); + _new_pm_agent_count += active_maps[i]->get_pm_agent_count(); + _new_pm_link_count += active_maps[i]->get_pm_link_count(); + _new_pm_polygon_count += active_maps[i]->get_pm_polygon_count(); + _new_pm_edge_count += active_maps[i]->get_pm_edge_count(); + _new_pm_edge_merge_count += active_maps[i]->get_pm_edge_merge_count(); + _new_pm_edge_connection_count += active_maps[i]->get_pm_edge_connection_count(); + _new_pm_edge_free_count += active_maps[i]->get_pm_edge_free_count(); + // Emit a signal if a map changed. const uint32_t new_map_update_id = active_maps[i]->get_map_update_id(); if (new_map_update_id != active_maps_update_id[i]) { @@ -844,6 +862,15 @@ void GodotNavigationServer::process(real_t p_delta_time) { active_maps_update_id[i] = new_map_update_id; } } + + pm_region_count = _new_pm_region_count; + pm_agent_count = _new_pm_agent_count; + pm_link_count = _new_pm_link_count; + pm_polygon_count = _new_pm_polygon_count; + pm_edge_count = _new_pm_edge_count; + pm_edge_merge_count = _new_pm_edge_merge_count; + pm_edge_connection_count = _new_pm_edge_connection_count; + pm_edge_free_count = _new_pm_edge_free_count; } PathQueryResult GodotNavigationServer::_query_path(const PathQueryParameters &p_parameters) const { @@ -886,6 +913,40 @@ PathQueryResult GodotNavigationServer::_query_path(const PathQueryParameters &p_ return r_query_result; } +int GodotNavigationServer::get_process_info(ProcessInfo p_info) const { + switch (p_info) { + case INFO_ACTIVE_MAPS: { + return active_maps.size(); + } break; + case INFO_REGION_COUNT: { + return pm_region_count; + } break; + case INFO_AGENT_COUNT: { + return pm_agent_count; + } break; + case INFO_LINK_COUNT: { + return pm_link_count; + } break; + case INFO_POLYGON_COUNT: { + return pm_polygon_count; + } break; + case INFO_EDGE_COUNT: { + return pm_edge_count; + } break; + case INFO_EDGE_MERGE_COUNT: { + return pm_edge_merge_count; + } break; + case INFO_EDGE_CONNECTION_COUNT: { + return pm_edge_connection_count; + } break; + case INFO_EDGE_FREE_COUNT: { + return pm_edge_free_count; + } break; + } + + return 0; +} + #undef COMMAND_1 #undef COMMAND_2 #undef COMMAND_4 diff --git a/modules/navigation/godot_navigation_server.h b/modules/navigation/godot_navigation_server.h index 7a6e5bb208..a87a88d3bc 100644 --- a/modules/navigation/godot_navigation_server.h +++ b/modules/navigation/godot_navigation_server.h @@ -81,6 +81,16 @@ class GodotNavigationServer : public NavigationServer3D { LocalVector<NavMap *> active_maps; LocalVector<uint32_t> active_maps_update_id; + // Performance Monitor + int pm_region_count = 0; + int pm_agent_count = 0; + int pm_link_count = 0; + int pm_polygon_count = 0; + int pm_edge_count = 0; + int pm_edge_merge_count = 0; + int pm_edge_connection_count = 0; + int pm_edge_free_count = 0; + public: GodotNavigationServer(); virtual ~GodotNavigationServer(); @@ -182,6 +192,8 @@ public: virtual void process(real_t p_delta_time) override; virtual NavigationUtilities::PathQueryResult _query_path(const NavigationUtilities::PathQueryParameters &p_parameters) const override; + + int get_process_info(ProcessInfo p_info) const override; }; #undef COMMAND_1 diff --git a/modules/navigation/nav_map.cpp b/modules/navigation/nav_map.cpp index 2a2f8aa1b7..fd735f8793 100644 --- a/modules/navigation/nav_map.cpp +++ b/modules/navigation/nav_map.cpp @@ -611,6 +611,16 @@ void NavMap::remove_agent_as_controlled(RvoAgent *agent) { } void NavMap::sync() { + // Performance Monitor + int _new_pm_region_count = regions.size(); + int _new_pm_agent_count = agents.size(); + int _new_pm_link_count = links.size(); + int _new_pm_polygon_count = pm_polygon_count; + int _new_pm_edge_count = pm_edge_count; + int _new_pm_edge_merge_count = pm_edge_merge_count; + int _new_pm_edge_connection_count = pm_edge_connection_count; + int _new_pm_edge_free_count = pm_edge_free_count; + // Check if we need to update the links. if (regenerate_polygons) { for (uint32_t r = 0; r < regions.size(); r++) { @@ -632,6 +642,12 @@ void NavMap::sync() { } if (regenerate_links) { + _new_pm_polygon_count = 0; + _new_pm_edge_count = 0; + _new_pm_edge_merge_count = 0; + _new_pm_edge_connection_count = 0; + _new_pm_edge_free_count = 0; + // Remove regions connections. for (uint32_t r = 0; r < regions.size(); r++) { regions[r]->get_connections().clear(); @@ -654,6 +670,8 @@ void NavMap::sync() { count += regions[r]->get_polygons().size(); } + _new_pm_polygon_count = polygons.size(); + // Group all edges per key. HashMap<gd::EdgeKey, Vector<gd::Edge::Connection>, gd::EdgeKey> connections; for (uint32_t poly_id = 0; poly_id < polygons.size(); poly_id++) { @@ -666,6 +684,7 @@ void NavMap::sync() { HashMap<gd::EdgeKey, Vector<gd::Edge::Connection>, gd::EdgeKey>::Iterator connection = connections.find(ek); if (!connection) { connections[ek] = Vector<gd::Edge::Connection>(); + _new_pm_edge_count += 1; } if (connections[ek].size() <= 1) { // Add the polygon/edge tuple to this key. @@ -691,6 +710,7 @@ void NavMap::sync() { c1.polygon->edges[c1.edge].connections.push_back(c2); c2.polygon->edges[c2.edge].connections.push_back(c1); // Note: The pathway_start/end are full for those connection and do not need to be modified. + _new_pm_edge_merge_count += 1; } else { CRASH_COND_MSG(E.value.size() != 1, vformat("Number of connection != 1. Found: %d", E.value.size())); free_edges.push_back(E.value[0]); @@ -704,6 +724,8 @@ void NavMap::sync() { // to be connected, create new polygons to remove that small gap is // not really useful and would result in wasteful computation during // connection, integration and path finding. + _new_pm_edge_free_count = free_edges.size(); + for (int i = 0; i < free_edges.size(); i++) { const gd::Edge::Connection &free_edge = free_edges[i]; Vector3 edge_p1 = free_edge.polygon->points[free_edge.edge].pos; @@ -757,6 +779,7 @@ void NavMap::sync() { // Add the connection to the region_connection map. ((NavRegion *)free_edge.polygon->owner)->get_connections().push_back(new_connection); + _new_pm_edge_connection_count += 1; } } @@ -892,6 +915,16 @@ void NavMap::sync() { regenerate_polygons = false; regenerate_links = false; agents_dirty = false; + + // Performance Monitor + pm_region_count = _new_pm_region_count; + pm_agent_count = _new_pm_agent_count; + pm_link_count = _new_pm_link_count; + pm_polygon_count = _new_pm_polygon_count; + pm_edge_count = _new_pm_edge_count; + pm_edge_merge_count = _new_pm_edge_merge_count; + pm_edge_connection_count = _new_pm_edge_connection_count; + pm_edge_free_count = _new_pm_edge_free_count; } void NavMap::compute_single_step(uint32_t index, RvoAgent **agent) { diff --git a/modules/navigation/nav_map.h b/modules/navigation/nav_map.h index 321d5560f0..fce7aff3ba 100644 --- a/modules/navigation/nav_map.h +++ b/modules/navigation/nav_map.h @@ -89,6 +89,16 @@ class NavMap : public NavRid { /// Change the id each time the map is updated. uint32_t map_update_id = 0; + // Performance Monitor + int pm_region_count = 0; + int pm_agent_count = 0; + int pm_link_count = 0; + int pm_polygon_count = 0; + int pm_edge_count = 0; + int pm_edge_merge_count = 0; + int pm_edge_connection_count = 0; + int pm_edge_free_count = 0; + public: NavMap(); ~NavMap(); @@ -152,6 +162,16 @@ public: void step(real_t p_deltatime); void dispatch_callbacks(); + // Performance Monitor + int get_pm_region_count() const { return pm_region_count; } + int get_pm_agent_count() const { return pm_agent_count; } + int get_pm_link_count() const { return pm_link_count; } + int get_pm_polygon_count() const { return pm_polygon_count; } + int get_pm_edge_count() const { return pm_edge_count; } + int get_pm_edge_merge_count() const { return pm_edge_merge_count; } + int get_pm_edge_connection_count() const { return pm_edge_connection_count; } + int get_pm_edge_free_count() const { return pm_edge_free_count; } + private: void compute_single_step(uint32_t index, RvoAgent **agent); void clip_path(const LocalVector<gd::NavigationPoly> &p_navigation_polys, Vector<Vector3> &path, const gd::NavigationPoly *from_poly, const Vector3 &p_to_point, const gd::NavigationPoly *p_to_poly, Vector<int32_t> *r_path_types, TypedArray<RID> *r_path_rids, Vector<int64_t> *r_path_owners) const; diff --git a/modules/navigation/navigation_mesh_generator.cpp b/modules/navigation/navigation_mesh_generator.cpp index 568e8b9b26..fff7a02fc4 100644 --- a/modules/navigation/navigation_mesh_generator.cpp +++ b/modules/navigation/navigation_mesh_generator.cpp @@ -207,11 +207,11 @@ void NavigationMeshGenerator::_parse_geometry(const Transform3D &p_navmesh_trans List<uint32_t> shape_owners; static_body->get_shape_owners(&shape_owners); for (uint32_t shape_owner : shape_owners) { + if (static_body->is_shape_owner_disabled(shape_owner)) { + continue; + } const int shape_count = static_body->shape_owner_get_shape_count(shape_owner); for (int i = 0; i < shape_count; i++) { - if (static_body->is_shape_owner_disabled(i)) { - continue; - } Ref<Shape3D> s = static_body->shape_owner_get_shape(shape_owner, i); if (s.is_null()) { continue; diff --git a/modules/openxr/SCsub b/modules/openxr/SCsub index fefee9bb24..3b39967ba4 100644 --- a/modules/openxr/SCsub +++ b/modules/openxr/SCsub @@ -101,6 +101,7 @@ env_openxr.add_source_files(module_obj, "extensions/openxr_huawei_controller_ext env_openxr.add_source_files(module_obj, "extensions/openxr_hand_tracking_extension.cpp") env_openxr.add_source_files(module_obj, "extensions/openxr_fb_passthrough_extension_wrapper.cpp") env_openxr.add_source_files(module_obj, "extensions/openxr_fb_display_refresh_rate_extension.cpp") +env_openxr.add_source_files(module_obj, "extensions/openxr_pico_controller_extension.cpp") env_openxr.add_source_files(module_obj, "extensions/openxr_wmr_controller_extension.cpp") env.modules_sources += module_obj diff --git a/modules/openxr/action_map/openxr_action_map.cpp b/modules/openxr/action_map/openxr_action_map.cpp index e3ff1b4382..669c395b3e 100644 --- a/modules/openxr/action_map/openxr_action_map.cpp +++ b/modules/openxr/action_map/openxr_action_map.cpp @@ -307,6 +307,31 @@ void OpenXRActionMap::create_default_action_sets() { profile->add_new_binding(haptic, "/user/hand/left/output/haptic,/user/hand/right/output/haptic"); add_interaction_profile(profile); + // Create our Pico 4 / Neo 3 controller profile + profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/pico/neo3_controller"); + profile->add_new_binding(default_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose"); + profile->add_new_binding(aim_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose"); + profile->add_new_binding(grip_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose"); + profile->add_new_binding(palm_pose, "/user/hand/left/input/palm_ext/pose,/user/hand/right/input/palm_ext/pose"); + profile->add_new_binding(select_button, "/user/hand/left/input/system/click,/user/hand/right/input/system/click"); // system click may not be available + profile->add_new_binding(menu_button, "/user/hand/left/input/back/click,/user/hand/right/input/back/click"); // right hand back click may not be available + profile->add_new_binding(ax_button, "/user/hand/left/input/x/click,/user/hand/right/input/a/click"); // x on left hand, a on right hand + profile->add_new_binding(ax_touch, "/user/hand/left/input/x/touch,/user/hand/right/input/a/touch"); + profile->add_new_binding(by_button, "/user/hand/left/input/y/click,/user/hand/right/input/b/click"); // y on left hand, b on right hand + profile->add_new_binding(by_touch, "/user/hand/left/input/y/touch,/user/hand/right/input/b/touch"); + profile->add_new_binding(trigger, "/user/hand/left/input/trigger/value,/user/hand/right/input/trigger/value"); + profile->add_new_binding(trigger_click, "/user/hand/left/input/trigger/value,/user/hand/right/input/trigger/value"); // should be converted to boolean + profile->add_new_binding(trigger_touch, "/user/hand/left/input/trigger/touch,/user/hand/right/input/trigger/touch"); + profile->add_new_binding(grip, "/user/hand/left/input/squeeze/value,/user/hand/right/input/squeeze/value"); // should be converted to boolean + profile->add_new_binding(grip_click, "/user/hand/left/input/squeeze/value,/user/hand/right/input/squeeze/value"); + // primary on our pico controller is our thumbstick + profile->add_new_binding(primary, "/user/hand/left/input/thumbstick,/user/hand/right/input/thumbstick"); + profile->add_new_binding(primary_click, "/user/hand/left/input/thumbstick/click,/user/hand/right/input/thumbstick/click"); + profile->add_new_binding(primary_touch, "/user/hand/left/input/thumbstick/touch,/user/hand/right/input/thumbstick/touch"); + // pico controller has no secondary input + profile->add_new_binding(haptic, "/user/hand/left/output/haptic,/user/hand/right/output/haptic"); + add_interaction_profile(profile); + // Create our Valve index controller profile profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/valve/index_controller"); profile->add_new_binding(default_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose"); diff --git a/modules/openxr/extensions/openxr_android_extension.cpp b/modules/openxr/extensions/openxr_android_extension.cpp index aae284f6bd..4465daf22a 100644 --- a/modules/openxr/extensions/openxr_android_extension.cpp +++ b/modules/openxr/extensions/openxr_android_extension.cpp @@ -51,18 +51,17 @@ OpenXRAndroidExtension::OpenXRAndroidExtension() { HashMap<String, bool *> OpenXRAndroidExtension::get_requested_extensions() { HashMap<String, bool *> request_extensions; - request_extensions[XR_KHR_LOADER_INIT_ANDROID_EXTENSION_NAME] = &loader_init_extension_available; request_extensions[XR_KHR_ANDROID_CREATE_INSTANCE_EXTENSION_NAME] = &create_instance_extension_available; return request_extensions; } void OpenXRAndroidExtension::on_before_instance_created() { - if (!loader_init_extension_available) { - print_line("OpenXR: XR_KHR_loader_init_android is not reported as available - trying to initialize anyway..."); + if (XR_FAILED(EXT_TRY_INIT_XR_FUNC(xrInitializeLoaderKHR))) { + // XR_KHR_loader_init not supported on this platform + return; } - - EXT_INIT_XR_FUNC(xrInitializeLoaderKHR); + loader_init_extension_available = true; JNIEnv *env = get_jni_env(); JavaVM *vm; @@ -85,6 +84,9 @@ static XrInstanceCreateInfoAndroidKHR instance_create_info; void *OpenXRAndroidExtension::set_instance_create_info_and_get_next_pointer(void *p_next_pointer) { if (!create_instance_extension_available) { + if (!loader_init_extension_available) { + WARN_PRINT("No Android extensions available, couldn't pass JVM and Activity to OpenXR"); + } return nullptr; } diff --git a/modules/openxr/extensions/openxr_opengl_extension.cpp b/modules/openxr/extensions/openxr_opengl_extension.cpp index cd371b9ed9..6ce8f0805f 100644 --- a/modules/openxr/extensions/openxr_opengl_extension.cpp +++ b/modules/openxr/extensions/openxr_opengl_extension.cpp @@ -157,7 +157,6 @@ void *OpenXROpenGLExtension::set_session_create_and_get_next_pointer(void *p_nex } void OpenXROpenGLExtension::get_usable_swapchain_formats(Vector<int64_t> &p_usable_swap_chains) { - p_usable_swap_chains.push_back(GL_SRGB8_ALPHA8); p_usable_swap_chains.push_back(GL_RGBA8); } diff --git a/modules/openxr/extensions/openxr_pico_controller_extension.cpp b/modules/openxr/extensions/openxr_pico_controller_extension.cpp new file mode 100644 index 0000000000..f2fcf22ce2 --- /dev/null +++ b/modules/openxr/extensions/openxr_pico_controller_extension.cpp @@ -0,0 +1,98 @@ +/**************************************************************************/ +/* openxr_pico_controller_extension.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 "openxr_pico_controller_extension.h" +#include "../action_map/openxr_interaction_profile_meta_data.h" + +// Pico controllers are not part of the OpenXR spec at the time of writing this +// code. We'll hardcode the extension name that is used internally, verified by +// tests on the Pico 4. Note that later versions of the Pico 4 and OpenXR +// runtime on Pico might use a different, standardized extension name. +#ifndef XR_PICO_CONTROLLER_INTERACTION_EXTENSION_NAME +#define XR_PICO_CONTROLLER_INTERACTION_EXTENSION_NAME "XR_PICO_controller_interaction" +#endif + +HashMap<String, bool *> OpenXRPicoControllerExtension::get_requested_extensions() { + HashMap<String, bool *> request_extensions; + + request_extensions[XR_PICO_CONTROLLER_INTERACTION_EXTENSION_NAME] = &available; + + return request_extensions; +} + +bool OpenXRPicoControllerExtension::is_available() { + return available; +} + +void OpenXRPicoControllerExtension::on_register_metadata() { + OpenXRInteractionProfileMetaData *metadata = OpenXRInteractionProfileMetaData::get_singleton(); + ERR_FAIL_NULL(metadata); + + // Pico controller (Pico 4 and Pico Neo 3 controllers) + metadata->register_interaction_profile("Pico controller", "/interaction_profiles/pico/neo3_controller", XR_PICO_CONTROLLER_INTERACTION_EXTENSION_NAME); + metadata->register_io_path("/interaction_profiles/pico/neo3_controller", "Grip pose", "/user/hand/left", "/user/hand/left/input/grip/pose", "", OpenXRAction::OPENXR_ACTION_POSE); + metadata->register_io_path("/interaction_profiles/pico/neo3_controller", "Grip pose", "/user/hand/right", "/user/hand/right/input/grip/pose", "", OpenXRAction::OPENXR_ACTION_POSE); + metadata->register_io_path("/interaction_profiles/pico/neo3_controller", "Aim pose", "/user/hand/left", "/user/hand/left/input/aim/pose", "", OpenXRAction::OPENXR_ACTION_POSE); + metadata->register_io_path("/interaction_profiles/pico/neo3_controller", "Aim pose", "/user/hand/right", "/user/hand/right/input/aim/pose", "", OpenXRAction::OPENXR_ACTION_POSE); + metadata->register_io_path("/interaction_profiles/pico/neo3_controller", "Palm pose", "/user/hand/left", "/user/hand/left/input/palm_ext/pose", XR_EXT_PALM_POSE_EXTENSION_NAME, OpenXRAction::OPENXR_ACTION_POSE); + metadata->register_io_path("/interaction_profiles/pico/neo3_controller", "Palm pose", "/user/hand/right", "/user/hand/right/input/palm_ext/pose", XR_EXT_PALM_POSE_EXTENSION_NAME, OpenXRAction::OPENXR_ACTION_POSE); + + metadata->register_io_path("/interaction_profiles/pico/neo3_controller", "Menu click", "/user/hand/left", "/user/hand/left/input/back/click", "", OpenXRAction::OPENXR_ACTION_BOOL); + metadata->register_io_path("/interaction_profiles/pico/neo3_controller", "Screenshot click", "/user/hand/right", "/user/hand/right/input/back/click", "", OpenXRAction::OPENXR_ACTION_BOOL); + metadata->register_io_path("/interaction_profiles/pico/neo3_controller", "System click", "/user/hand/left", "/user/hand/left/input/system/click", "", OpenXRAction::OPENXR_ACTION_BOOL); + metadata->register_io_path("/interaction_profiles/pico/neo3_controller", "System click", "/user/hand/right", "/user/hand/right/input/system/click", "", OpenXRAction::OPENXR_ACTION_BOOL); + + metadata->register_io_path("/interaction_profiles/pico/neo3_controller", "X click", "/user/hand/left", "/user/hand/left/input/x/click", "", OpenXRAction::OPENXR_ACTION_BOOL); + metadata->register_io_path("/interaction_profiles/pico/neo3_controller", "X touch", "/user/hand/left", "/user/hand/left/input/x/touch", "", OpenXRAction::OPENXR_ACTION_BOOL); + metadata->register_io_path("/interaction_profiles/pico/neo3_controller", "Y click", "/user/hand/left", "/user/hand/left/input/y/click", "", OpenXRAction::OPENXR_ACTION_BOOL); + metadata->register_io_path("/interaction_profiles/pico/neo3_controller", "Y touch", "/user/hand/left", "/user/hand/left/input/y/touch", "", OpenXRAction::OPENXR_ACTION_BOOL); + metadata->register_io_path("/interaction_profiles/pico/neo3_controller", "A click", "/user/hand/right", "/user/hand/right/input/a/click", "", OpenXRAction::OPENXR_ACTION_BOOL); + metadata->register_io_path("/interaction_profiles/pico/neo3_controller", "A touch", "/user/hand/right", "/user/hand/right/input/a/touch", "", OpenXRAction::OPENXR_ACTION_BOOL); + metadata->register_io_path("/interaction_profiles/pico/neo3_controller", "B click", "/user/hand/right", "/user/hand/right/input/b/click", "", OpenXRAction::OPENXR_ACTION_BOOL); + metadata->register_io_path("/interaction_profiles/pico/neo3_controller", "B touch", "/user/hand/right", "/user/hand/right/input/b/touch", "", OpenXRAction::OPENXR_ACTION_BOOL); + + metadata->register_io_path("/interaction_profiles/pico/neo3_controller", "Trigger", "/user/hand/left", "/user/hand/left/input/trigger/value", "", OpenXRAction::OPENXR_ACTION_FLOAT); + metadata->register_io_path("/interaction_profiles/pico/neo3_controller", "Trigger touch", "/user/hand/left", "/user/hand/left/input/trigger/touch", "", OpenXRAction::OPENXR_ACTION_BOOL); + metadata->register_io_path("/interaction_profiles/pico/neo3_controller", "Trigger", "/user/hand/right", "/user/hand/right/input/trigger/value", "", OpenXRAction::OPENXR_ACTION_FLOAT); + metadata->register_io_path("/interaction_profiles/pico/neo3_controller", "Trigger touch", "/user/hand/right", "/user/hand/right/input/trigger/touch", "", OpenXRAction::OPENXR_ACTION_BOOL); + + metadata->register_io_path("/interaction_profiles/pico/neo3_controller", "Squeeze", "/user/hand/left", "/user/hand/left/input/squeeze/value", "", OpenXRAction::OPENXR_ACTION_FLOAT); + metadata->register_io_path("/interaction_profiles/pico/neo3_controller", "Squeeze", "/user/hand/right", "/user/hand/right/input/squeeze/value", "", OpenXRAction::OPENXR_ACTION_FLOAT); + + metadata->register_io_path("/interaction_profiles/pico/neo3_controller", "Thumbstick", "/user/hand/left", "/user/hand/left/input/thumbstick", "", OpenXRAction::OPENXR_ACTION_VECTOR2); + metadata->register_io_path("/interaction_profiles/pico/neo3_controller", "Thumbstick click", "/user/hand/left", "/user/hand/left/input/thumbstick/click", "", OpenXRAction::OPENXR_ACTION_BOOL); + metadata->register_io_path("/interaction_profiles/pico/neo3_controller", "Thumbstick touch", "/user/hand/left", "/user/hand/left/input/thumbstick/touch", "", OpenXRAction::OPENXR_ACTION_BOOL); + metadata->register_io_path("/interaction_profiles/pico/neo3_controller", "Thumbstick", "/user/hand/right", "/user/hand/right/input/thumbstick", "", OpenXRAction::OPENXR_ACTION_VECTOR2); + metadata->register_io_path("/interaction_profiles/pico/neo3_controller", "Thumbstick click", "/user/hand/right", "/user/hand/right/input/thumbstick/click", "", OpenXRAction::OPENXR_ACTION_BOOL); + metadata->register_io_path("/interaction_profiles/pico/neo3_controller", "Thumbstick touch", "/user/hand/right", "/user/hand/right/input/thumbstick/touch", "", OpenXRAction::OPENXR_ACTION_BOOL); + + metadata->register_io_path("/interaction_profiles/pico/neo3_controller", "Haptic output", "/user/hand/left", "/user/hand/left/output/haptic", "", OpenXRAction::OPENXR_ACTION_HAPTIC); + metadata->register_io_path("/interaction_profiles/pico/neo3_controller", "Haptic output", "/user/hand/right", "/user/hand/right/output/haptic", "", OpenXRAction::OPENXR_ACTION_HAPTIC); +} diff --git a/modules/openxr/extensions/openxr_pico_controller_extension.h b/modules/openxr/extensions/openxr_pico_controller_extension.h new file mode 100644 index 0000000000..a2a1e2f3d3 --- /dev/null +++ b/modules/openxr/extensions/openxr_pico_controller_extension.h @@ -0,0 +1,48 @@ +/**************************************************************************/ +/* openxr_pico_controller_extension.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 OPENXR_PICO_CONTROLLER_EXTENSION_H +#define OPENXR_PICO_CONTROLLER_EXTENSION_H + +#include "openxr_extension_wrapper.h" + +class OpenXRPicoControllerExtension : public OpenXRExtensionWrapper { +public: + virtual HashMap<String, bool *> get_requested_extensions() override; + + bool is_available(); + + virtual void on_register_metadata() override; + +private: + bool available = false; +}; + +#endif // OPENXR_PICO_CONTROLLER_EXTENSION_H diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp index 550ed8052e..d556f475d2 100644 --- a/modules/openxr/openxr_api.cpp +++ b/modules/openxr/openxr_api.cpp @@ -217,7 +217,7 @@ bool OpenXRAPI::is_top_level_path_supported(const String &p_toplevel_path) { String required_extension = OpenXRInteractionProfileMetaData::get_singleton()->get_top_level_extension(p_toplevel_path); // If unsupported is returned we likely have a misspelled interaction profile path in our action map. Always output that as an error. - ERR_FAIL_COND_V_MSG(required_extension == XR_PATH_UNSUPPORTED_NAME, false, "OpenXR: Unsupported interaction profile " + p_toplevel_path); + ERR_FAIL_COND_V_MSG(required_extension == XR_PATH_UNSUPPORTED_NAME, false, "OpenXR: Unsupported toplevel path " + p_toplevel_path); if (required_extension == "") { // no extension needed, core top level are always "supported", they just won't be used if not really supported @@ -1224,8 +1224,12 @@ bool OpenXRAPI::resolve_instance_openxr_symbols() { return true; } +XrResult OpenXRAPI::try_get_instance_proc_addr(const char *p_name, PFN_xrVoidFunction *p_addr) { + return xrGetInstanceProcAddr(instance, p_name, p_addr); +} + XrResult OpenXRAPI::get_instance_proc_addr(const char *p_name, PFN_xrVoidFunction *p_addr) { - XrResult result = xrGetInstanceProcAddr(instance, p_name, p_addr); + XrResult result = try_get_instance_proc_addr(p_name, p_addr); if (result != XR_SUCCESS) { String error_message = String("Symbol ") + p_name + " not found in OpenXR instance."; diff --git a/modules/openxr/openxr_api.h b/modules/openxr/openxr_api.h index a697a5f90a..5fb8de660e 100644 --- a/modules/openxr/openxr_api.h +++ b/modules/openxr/openxr_api.h @@ -305,6 +305,7 @@ public: static bool openxr_is_enabled(bool p_check_run_in_editor = true); _FORCE_INLINE_ static OpenXRAPI *get_singleton() { return singleton; } + XrResult try_get_instance_proc_addr(const char *p_name, PFN_xrVoidFunction *p_addr); XrResult get_instance_proc_addr(const char *p_name, PFN_xrVoidFunction *p_addr); String get_error_string(XrResult result); String get_swapchain_format_name(int64_t p_swapchain_format) const; diff --git a/modules/openxr/register_types.cpp b/modules/openxr/register_types.cpp index 56c31883e6..4e2fe3dab5 100644 --- a/modules/openxr/register_types.cpp +++ b/modules/openxr/register_types.cpp @@ -53,6 +53,7 @@ #include "extensions/openxr_htc_vive_tracker_extension.h" #include "extensions/openxr_huawei_controller_extension.h" #include "extensions/openxr_palm_pose_extension.h" +#include "extensions/openxr_pico_controller_extension.h" #include "extensions/openxr_wmr_controller_extension.h" static OpenXRAPI *openxr_api = nullptr; @@ -92,6 +93,7 @@ void initialize_openxr_module(ModuleInitializationLevel p_level) { // register our other extensions OpenXRAPI::register_extension_wrapper(memnew(OpenXRPalmPoseExtension)); + OpenXRAPI::register_extension_wrapper(memnew(OpenXRPicoControllerExtension)); OpenXRAPI::register_extension_wrapper(memnew(OpenXRCompositionLayerDepthExtension)); OpenXRAPI::register_extension_wrapper(memnew(OpenXRHTCControllerExtension)); OpenXRAPI::register_extension_wrapper(memnew(OpenXRHTCViveTrackerExtension)); diff --git a/modules/openxr/util.h b/modules/openxr/util.h index f3fa187faa..6665d45007 100644 --- a/modules/openxr/util.h +++ b/modules/openxr/util.h @@ -53,6 +53,12 @@ #define EXT_INIT_XR_FUNC(name) INIT_XR_FUNC(OpenXRAPI::get_singleton(), name) #define OPENXR_API_INIT_XR_FUNC(name) INIT_XR_FUNC(this, name) +#define TRY_INIT_XR_FUNC(openxr_api, name) \ + openxr_api->try_get_instance_proc_addr(#name, (PFN_xrVoidFunction *)&name##_ptr) + +#define EXT_TRY_INIT_XR_FUNC(name) TRY_INIT_XR_FUNC(OpenXRAPI::get_singleton(), name) +#define OPENXR_TRY_API_INIT_XR_FUNC(name) TRY_INIT_XR_FUNC(this, name) + #define EXT_PROTO_XRRESULT_FUNC1(func_name, arg1_type, arg1) \ PFN_##func_name func_name##_ptr = nullptr; \ XRAPI_ATTR XrResult XRAPI_CALL func_name(UNPACK arg1_type p_##arg1) const { \ |