summaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/csg/csg_shape.cpp4
-rw-r--r--modules/gdscript/doc_classes/@GDScript.xml13
-rw-r--r--modules/gdscript/gdscript.cpp145
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp446
-rw-r--r--modules/gdscript/gdscript_analyzer.h24
-rw-r--r--modules/gdscript/gdscript_byte_codegen.cpp34
-rw-r--r--modules/gdscript/gdscript_compiler.cpp34
-rw-r--r--modules/gdscript/gdscript_disassembler.cpp6
-rw-r--r--modules/gdscript/gdscript_parser.cpp82
-rw-r--r--modules/gdscript/gdscript_parser.h5
-rw-r--r--modules/gdscript/gdscript_utility_functions.cpp23
-rw-r--r--modules/gdscript/gdscript_vm.cpp87
-rw-r--r--modules/gdscript/gdscript_warning.cpp12
-rw-r--r--modules/gdscript/gdscript_warning.h2
-rw-r--r--modules/gdscript/tests/gdscript_test_runner.cpp32
-rw-r--r--modules/gdscript/tests/gdscript_test_runner.h4
-rw-r--r--modules/gdscript/tests/gdscript_test_runner_suite.h3
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/invalid_array_index.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/typed_array_assign_differently_typed.gd4
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/typed_array_assign_differently_typed.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/typed_array_assignment.gd (renamed from modules/gdscript/tests/scripts/analyzer/typed_array_assignment.gd)0
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/typed_array_assignment.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/typed_array_init_with_unconvertable_in_literal.gd4
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/typed_array_init_with_unconvertable_in_literal.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/typed_array_pass_differently_to_typed.gd7
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/typed_array_pass_differently_to_typed.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/allow_void_function_to_return_void.gd20
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/allow_void_function_to_return_void.out10
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/typed_array_as_default_parameter.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd204
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/typed_array_assignment.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/class_name_after_annotation.gd2
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/typed_array_assign_basic_to_typed.gd4
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/typed_array_assign_basic_to_typed.out6
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/typed_array_assign_differently_typed.gd4
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/typed_array_assign_differently_typed.out6
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/typed_array_pass_basic_to_typed.gd7
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/typed_array_pass_basic_to_typed.out6
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/typed_array_pass_differently_to_typed.gd7
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/typed_array_pass_differently_to_typed.out6
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/typed_array_init_with_untyped_in_literal.gd6
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/typed_array_init_with_untyped_in_literal.out2
-rw-r--r--modules/gltf/doc_classes/GLTFDocumentExtension.xml16
-rw-r--r--modules/gltf/doc_classes/GLTFState.xml6
-rw-r--r--modules/gltf/editor/editor_import_blend_runner.cpp314
-rw-r--r--modules/gltf/editor/editor_import_blend_runner.h69
-rw-r--r--modules/gltf/editor/editor_scene_importer_blend.cpp131
-rw-r--r--modules/gltf/editor/editor_scene_importer_gltf.cpp6
-rw-r--r--modules/gltf/extensions/gltf_document_extension.cpp32
-rw-r--r--modules/gltf/extensions/gltf_document_extension.h16
-rw-r--r--modules/gltf/gltf_document.cpp98
-rw-r--r--modules/gltf/gltf_state.cpp3
-rw-r--r--modules/gltf/gltf_state.h1
-rw-r--r--modules/gltf/register_types.cpp11
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/PackedSceneExtensions.cs4
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Rid.cs76
-rw-r--r--modules/openxr/doc_classes/OpenXRInterface.xml21
-rw-r--r--modules/openxr/openxr_interface.cpp36
-rw-r--r--modules/openxr/openxr_interface.h4
-rw-r--r--modules/theora/doc_classes/VideoStreamTheora.xml15
-rw-r--r--modules/theora/video_stream_theora.cpp31
-rw-r--r--modules/theora/video_stream_theora.h15
-rw-r--r--modules/webrtc/webrtc_multiplayer_peer.cpp1
-rw-r--r--modules/websocket/websocket_multiplayer_peer.cpp1
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.
}