diff options
Diffstat (limited to 'modules')
38 files changed, 897 insertions, 237 deletions
diff --git a/modules/fbx/data/fbx_skeleton.cpp b/modules/fbx/data/fbx_skeleton.cpp index 622b589feb..1ac4922acf 100644 --- a/modules/fbx/data/fbx_skeleton.cpp +++ b/modules/fbx/data/fbx_skeleton.cpp @@ -69,7 +69,7 @@ void FBXSkeleton::init_skeleton(const ImportState &state) { // Make sure the bone name is unique. const String bone_name = bone->bone_name; int same_name_count = 0; - for (int y = x; y < skeleton_bone_count; y++) { + for (int y = x + 1; y < skeleton_bone_count; y++) { Ref<FBXBone> other_bone = skeleton_bones[y]; if (other_bone.is_valid()) { if (other_bone->bone_name == bone_name) { diff --git a/modules/fbx/tools/import_utils.h b/modules/fbx/tools/import_utils.h index 6261138812..bea28ffeda 100644 --- a/modules/fbx/tools/import_utils.h +++ b/modules/fbx/tools/import_utils.h @@ -339,7 +339,7 @@ public: // } else { // Ref<Texture> texture = ResourceLoader::load(p_path); // ERR_FAIL_COND_V(texture.is_null(), Ref<Image>()); - // Ref<Image> image = texture->get_data(); + // Ref<Image> image = texture->get_image(); // ERR_FAIL_COND_V(image.is_null(), Ref<Image>()); // state.path_to_image_cache.insert(p_path, image); // return image; diff --git a/modules/gdnative/include/pluginscript/godot_pluginscript.h b/modules/gdnative/include/pluginscript/godot_pluginscript.h index cbd65e3772..b76f89cc99 100644 --- a/modules/gdnative/include/pluginscript/godot_pluginscript.h +++ b/modules/gdnative/include/pluginscript/godot_pluginscript.h @@ -56,6 +56,7 @@ typedef struct { int p_argcount, godot_variant_call_error *r_error); void (*notification)(godot_pluginscript_instance_data *p_data, int p_notification); + godot_string (*to_string)(godot_pluginscript_instance_data *p_data, godot_bool *r_valid); //this is used by script languages that keep a reference counter of their own //you can make make Ref<> not die when it reaches zero, so deleting the reference diff --git a/modules/gdnative/include/text/godot_text.h b/modules/gdnative/include/text/godot_text.h index 86fc745134..99c78c72e6 100644 --- a/modules/gdnative/include/text/godot_text.h +++ b/modules/gdnative/include/text/godot_text.h @@ -118,6 +118,7 @@ typedef struct { godot_vector2 (*font_get_glyph_kerning)(void *, godot_rid *, uint32_t, uint32_t, int); godot_vector2 (*font_draw_glyph)(void *, godot_rid *, godot_rid *, int, const godot_vector2 *, uint32_t, const godot_color *); godot_vector2 (*font_draw_glyph_outline)(void *, godot_rid *, godot_rid *, int, int, const godot_vector2 *, uint32_t, const godot_color *); + bool (*font_get_glyph_contours)(void *, godot_rid *, int, uint32_t, godot_packed_vector3_array *, godot_packed_int32_array *, bool *); float (*font_get_oversampling)(void *); void (*font_set_oversampling)(void *, float); godot_packed_string_array (*get_system_fonts)(void *); diff --git a/modules/gdnative/nativescript/nativescript.cpp b/modules/gdnative/nativescript/nativescript.cpp index 0025f4bb06..5d2117d76c 100644 --- a/modules/gdnative/nativescript/nativescript.cpp +++ b/modules/gdnative/nativescript/nativescript.cpp @@ -1727,6 +1727,40 @@ void NativeScriptLanguage::unregister_script(NativeScript *script) { if (S->get().size() == 0) { library_script_users.erase(S); + Map<String, Map<StringName, NativeScriptDesc>>::Element *L = library_classes.find(script->lib_path); + if (L) { + Map<StringName, NativeScriptDesc> classes = L->get(); + + for (Map<StringName, NativeScriptDesc>::Element *C = classes.front(); C; C = C->next()) { + // free property stuff first + for (OrderedHashMap<StringName, NativeScriptDesc::Property>::Element P = C->get().properties.front(); P; P = P.next()) { + if (P.get().getter.free_func) { + P.get().getter.free_func(P.get().getter.method_data); + } + + if (P.get().setter.free_func) { + P.get().setter.free_func(P.get().setter.method_data); + } + } + + // free method stuff + for (Map<StringName, NativeScriptDesc::Method>::Element *M = C->get().methods.front(); M; M = M->next()) { + if (M->get().method.free_func) { + M->get().method.free_func(M->get().method.method_data); + } + } + + // free constructor/destructor + if (C->get().create_func.free_func) { + C->get().create_func.free_func(C->get().create_func.method_data); + } + + if (C->get().destroy_func.free_func) { + C->get().destroy_func.free_func(C->get().destroy_func.method_data); + } + } + } + Map<String, Ref<GDNative>>::Element *G = library_gdnatives.find(script->lib_path); if (G && G->get()->get_library()->is_reloadable()) { G->get()->terminate(); diff --git a/modules/gdnative/pluginscript/pluginscript_instance.cpp b/modules/gdnative/pluginscript/pluginscript_instance.cpp index 432aa80325..7f8dba0906 100644 --- a/modules/gdnative/pluginscript/pluginscript_instance.cpp +++ b/modules/gdnative/pluginscript/pluginscript_instance.cpp @@ -93,6 +93,13 @@ void PluginScriptInstance::notification(int p_notification) { _desc->notification(_data, p_notification); } +String PluginScriptInstance::to_string(bool *r_valid) { + godot_string ret = _desc->to_string(_data, r_valid); + String str_ret = *(String *)&ret; + godot_string_destroy(&ret); + return str_ret; +} + Vector<ScriptNetData> PluginScriptInstance::get_rpc_methods() const { return _script->get_rpc_methods(); } diff --git a/modules/gdnative/pluginscript/pluginscript_instance.h b/modules/gdnative/pluginscript/pluginscript_instance.h index 536eb550e0..b263c0e62c 100644 --- a/modules/gdnative/pluginscript/pluginscript_instance.h +++ b/modules/gdnative/pluginscript/pluginscript_instance.h @@ -63,6 +63,7 @@ public: virtual Variant call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error); virtual void notification(int p_notification); + virtual String to_string(bool *r_valid); virtual Ref<Script> get_script() const; diff --git a/modules/gdnative/text/text_server_gdnative.cpp b/modules/gdnative/text/text_server_gdnative.cpp index 7cd8de5f2e..7cb749309a 100644 --- a/modules/gdnative/text/text_server_gdnative.cpp +++ b/modules/gdnative/text/text_server_gdnative.cpp @@ -359,6 +359,12 @@ Vector2 TextServerGDNative::font_draw_glyph_outline(RID p_font, RID p_canvas, in return advance; } +bool TextServerGDNative::font_get_glyph_contours(RID p_font, int p_size, uint32_t p_index, Vector<Vector3> &r_points, Vector<int32_t> &r_contours, bool &r_orientation) const { + ERR_FAIL_COND_V(interface == nullptr, false); + ERR_FAIL_COND_V(interface->font_get_glyph_contours == nullptr, false); + return interface->font_get_glyph_contours(data, (godot_rid *)&p_font, p_size, p_index, (godot_packed_vector3_array *)&r_points, (godot_packed_int32_array *)&r_contours, (bool *)&r_orientation); +} + float TextServerGDNative::font_get_oversampling() const { ERR_FAIL_COND_V(interface == nullptr, 1.f); return interface->font_get_oversampling(data); diff --git a/modules/gdnative/text/text_server_gdnative.h b/modules/gdnative/text/text_server_gdnative.h index 931bb44885..7e42b16fe1 100644 --- a/modules/gdnative/text/text_server_gdnative.h +++ b/modules/gdnative/text/text_server_gdnative.h @@ -126,6 +126,8 @@ public: virtual Vector2 font_draw_glyph(RID p_font, RID p_canvas, int p_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color = Color(1, 1, 1)) const override; virtual Vector2 font_draw_glyph_outline(RID p_font, RID p_canvas, int p_size, int p_outline_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color = Color(1, 1, 1)) const override; + virtual bool font_get_glyph_contours(RID p_font, int p_size, uint32_t p_index, Vector<Vector3> &r_points, Vector<int32_t> &r_contours, bool &r_orientation) const override; + virtual float font_get_oversampling() const override; virtual void font_set_oversampling(float p_oversampling) override; diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index a129b73c1a..aea86cce54 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -1310,21 +1310,29 @@ bool GDScriptInstance::set(const StringName &p_name, const Variant &p_value) { return true; //function exists, call was successful } } 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; + 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; + } } - } else { - members.write[member->index] = p_value; } + members.write[member->index] = p_value; } return true; } diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index a6138cc564..bdca64c146 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -39,40 +39,6 @@ #include "gdscript.h" #include "gdscript_utility_functions.h" -// TODO: Move this to a central location (maybe core?). -static HashMap<StringName, StringName> underscore_map; -static const char *underscore_classes[] = { - "ClassDB", - "Directory", - "Engine", - "File", - "Geometry", - "GodotSharp", - "JSON", - "Marshalls", - "Mutex", - "OS", - "ResourceLoader", - "ResourceSaver", - "Semaphore", - "Thread", - "VisualScriptEditor", - nullptr, -}; -static StringName get_real_class_name(const StringName &p_source) { - if (underscore_map.is_empty()) { - const char **class_name = underscore_classes; - while (*class_name != nullptr) { - underscore_map[*class_name] = String("_") + *class_name; - class_name++; - } - } - if (underscore_map.has(p_source)) { - return underscore_map[p_source]; - } - return p_source; -} - static MethodInfo info_from_utility_func(const StringName &p_function) { ERR_FAIL_COND_V(!Variant::has_utility_function(p_function), MethodInfo()); @@ -106,10 +72,6 @@ static MethodInfo info_from_utility_func(const StringName &p_function) { return info; } -void GDScriptAnalyzer::cleanup() { - underscore_map.clear(); -} - static GDScriptParser::DataType make_callable_type(const MethodInfo &p_info) { GDScriptParser::DataType type; type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; @@ -150,7 +112,7 @@ static GDScriptParser::DataType make_native_enum_type(const StringName &p_native type.is_meta_type = true; List<StringName> enum_values; - StringName real_native_name = get_real_class_name(p_native_class); + StringName real_native_name = GDScriptParser::get_real_class_name(p_native_class); ClassDB::get_enum_constants(real_native_name, p_enum_name, &enum_values); for (const List<StringName>::Element *E = enum_values.front(); E != nullptr; E = E->next()) { @@ -267,7 +229,7 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, push_error(vformat(R"(Could not resolve super class inheritance from "%s".)", name), p_class); return err; } - } else if (class_exists(name) && ClassDB::can_instance(get_real_class_name(name))) { + } else if (class_exists(name) && ClassDB::can_instance(GDScriptParser::get_real_class_name(name))) { base.kind = GDScriptParser::DataType::NATIVE; base.native_type = name; } else { @@ -413,6 +375,14 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type } result.kind = GDScriptParser::DataType::BUILTIN; result.builtin_type = GDScriptParser::get_builtin_type(first); + + if (result.builtin_type == Variant::ARRAY) { + GDScriptParser::DataType container_type = resolve_datatype(p_type->container_type); + + if (container_type.kind != GDScriptParser::DataType::VARIANT) { + result.set_container_element_type(container_type); + } + } } else if (class_exists(first)) { // Native engine classes. result.kind = GDScriptParser::DataType::NATIVE; @@ -436,7 +406,7 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type return GDScriptParser::DataType(); } result = ref->get_parser()->head->get_datatype(); - } else if (ClassDB::has_enum(get_real_class_name(parser->current_class->base_type.native_type), first)) { + } else if (ClassDB::has_enum(GDScriptParser::get_real_class_name(parser->current_class->base_type.native_type), first)) { // Native enum in current class. result = make_native_enum_type(parser->current_class->base_type.native_type, first); } else { @@ -499,7 +469,7 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type } } else if (result.kind == GDScriptParser::DataType::NATIVE) { // Only enums allowed for native. - if (ClassDB::has_enum(get_real_class_name(result.native_type), p_type->type_chain[1]->name)) { + if (ClassDB::has_enum(GDScriptParser::get_real_class_name(result.native_type), p_type->type_chain[1]->name)) { if (p_type->type_chain.size() > 2) { push_error(R"(Enums cannot contain nested types.)", p_type->type_chain[2]); } else { @@ -513,6 +483,10 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type } } + if (result.builtin_type != Variant::ARRAY && p_type->container_type != nullptr) { + push_error("Only arrays can specify the collection element type.", p_type); + } + p_type->set_datatype(result); return result; } @@ -535,9 +509,23 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas datatype.kind = GDScriptParser::DataType::VARIANT; datatype.type_source = GDScriptParser::DataType::UNDETECTED; + GDScriptParser::DataType specified_type; + if (member.variable->datatype_specifier != nullptr) { + specified_type = resolve_datatype(member.variable->datatype_specifier); + specified_type.is_meta_type = false; + } + if (member.variable->initializer != nullptr) { member.variable->set_datatype(datatype); // Allow recursive usage. reduce_expression(member.variable->initializer); + if ((member.variable->infer_datatype || (member.variable->datatype_specifier != nullptr && specified_type.has_container_element_type())) && member.variable->initializer->type == GDScriptParser::Node::ARRAY) { + // Typed array. + GDScriptParser::ArrayNode *array = static_cast<GDScriptParser::ArrayNode *>(member.variable->initializer); + // Can only infer typed array if it has elements. + if ((member.variable->infer_datatype && array->elements.size() > 0) || member.variable->datatype_specifier != nullptr) { + update_array_literal_element_type(specified_type, array); + } + } datatype = member.variable->initializer->get_datatype(); if (datatype.type_source != GDScriptParser::DataType::UNDETECTED) { datatype.type_source = GDScriptParser::DataType::INFERRED; @@ -545,8 +533,7 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas } if (member.variable->datatype_specifier != nullptr) { - datatype = resolve_datatype(member.variable->datatype_specifier); - datatype.is_meta_type = false; + datatype = specified_type; if (member.variable->initializer != nullptr) { if (!is_type_compatible(datatype, member.variable->initializer->get_datatype(), true)) { @@ -582,37 +569,32 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas datatype.is_constant = false; member.variable->set_datatype(datatype); - if (!datatype.has_no_type()) { - // TODO: Move this out into a routine specific to validate annotations. - if (member.variable->export_info.hint == PROPERTY_HINT_TYPE_STRING) { - // @export annotation. - switch (datatype.kind) { - case GDScriptParser::DataType::BUILTIN: - member.variable->export_info.hint_string = Variant::get_type_name(datatype.builtin_type); - break; - case GDScriptParser::DataType::NATIVE: - if (ClassDB::is_parent_class(get_real_class_name(datatype.native_type), "Resource")) { - member.variable->export_info.hint = PROPERTY_HINT_RESOURCE_TYPE; - member.variable->export_info.hint_string = get_real_class_name(datatype.native_type); - } else { - push_error(R"(Export type can only be built-in or a resource.)", member.variable); - } - break; - default: - // TODO: Allow custom user resources. - push_error(R"(Export type can only be built-in or a resource.)", member.variable); - break; - } - } + + // Apply annotations. + for (List<GDScriptParser::AnnotationNode *>::Element *E = member.variable->annotations.front(); E; E = E->next()) { + E->get()->apply(parser, member.variable); } } break; case GDScriptParser::ClassNode::Member::CONSTANT: { reduce_expression(member.constant->initializer); + GDScriptParser::DataType specified_type; + + if (member.constant->datatype_specifier != nullptr) { + specified_type = resolve_datatype(member.constant->datatype_specifier); + specified_type.is_meta_type = false; + } + GDScriptParser::DataType datatype = member.constant->get_datatype(); if (member.constant->initializer) { if (member.constant->initializer->type == GDScriptParser::Node::ARRAY) { - const_fold_array(static_cast<GDScriptParser::ArrayNode *>(member.constant->initializer)); + GDScriptParser::ArrayNode *array = static_cast<GDScriptParser::ArrayNode *>(member.constant->initializer); + const_fold_array(array); + + // Can only infer typed array if it has elements. + if (array->elements.size() > 0 || (member.constant->datatype_specifier != nullptr && specified_type.has_container_element_type())) { + update_array_literal_element_type(specified_type, array); + } } else if (member.constant->initializer->type == GDScriptParser::Node::DICTIONARY) { const_fold_dictionary(static_cast<GDScriptParser::DictionaryNode *>(member.constant->initializer)); } @@ -622,8 +604,7 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas } if (member.constant->datatype_specifier != nullptr) { - datatype = resolve_datatype(member.constant->datatype_specifier); - datatype.is_meta_type = false; + datatype = specified_type; if (!is_type_compatible(datatype, member.constant->initializer->get_datatype(), true)) { push_error(vformat(R"(Value of type "%s" cannot be initialized to constant of type "%s".)", member.constant->initializer->get_datatype().to_string(), datatype.to_string()), member.constant->initializer); @@ -637,6 +618,11 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas datatype.is_constant = true; member.constant->set_datatype(datatype); + + // Apply annotations. + for (List<GDScriptParser::AnnotationNode *>::Element *E = member.constant->annotations.front(); E; E = E->next()) { + E->get()->apply(parser, member.constant); + } } break; case GDScriptParser::ClassNode::Member::SIGNAL: { for (int j = 0; j < member.signal->parameters.size(); j++) { @@ -651,6 +637,11 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas signal_type.builtin_type = Variant::SIGNAL; member.signal->set_datatype(signal_type); + + // Apply annotations. + for (List<GDScriptParser::AnnotationNode *>::Element *E = member.signal->annotations.front(); E; E = E->next()) { + E->get()->apply(parser, member.signal); + } } break; case GDScriptParser::ClassNode::Member::ENUM: { GDScriptParser::DataType enum_type; @@ -693,6 +684,11 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas current_enum = nullptr; member.m_enum->set_datatype(enum_type); + + // Apply annotations. + for (List<GDScriptParser::AnnotationNode *>::Element *E = member.m_enum->annotations.front(); E; E = E->next()) { + E->get()->apply(parser, member.m_enum); + } } break; case GDScriptParser::ClassNode::Member::FUNCTION: resolve_function_signature(member.function); @@ -761,6 +757,11 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class) { } resolve_function_body(member.function); + + // Apply annotations. + for (List<GDScriptParser::AnnotationNode *>::Element *E = member.function->annotations.front(); E; E = E->next()) { + E->get()->apply(parser, member.function); + } } parser->current_class = previous_class; @@ -1092,8 +1093,23 @@ void GDScriptAnalyzer::resolve_variable(GDScriptParser::VariableNode *p_variable GDScriptParser::DataType type; type.kind = GDScriptParser::DataType::VARIANT; // By default. + GDScriptParser::DataType specified_type; + if (p_variable->datatype_specifier != nullptr) { + specified_type = resolve_datatype(p_variable->datatype_specifier); + specified_type.is_meta_type = false; + } + if (p_variable->initializer != nullptr) { reduce_expression(p_variable->initializer); + if ((p_variable->infer_datatype || (p_variable->datatype_specifier != nullptr && specified_type.has_container_element_type())) && p_variable->initializer->type == GDScriptParser::Node::ARRAY) { + // Typed array. + GDScriptParser::ArrayNode *array = static_cast<GDScriptParser::ArrayNode *>(p_variable->initializer); + // Can only infer typed array if it has elements. + if ((p_variable->infer_datatype && array->elements.size() > 0) || p_variable->datatype_specifier != nullptr) { + update_array_literal_element_type(specified_type, array); + } + } + type = p_variable->initializer->get_datatype(); if (p_variable->infer_datatype) { @@ -1117,7 +1133,7 @@ void GDScriptAnalyzer::resolve_variable(GDScriptParser::VariableNode *p_variable } if (p_variable->datatype_specifier != nullptr) { - type = resolve_datatype(p_variable->datatype_specifier); + type = specified_type; type.is_meta_type = false; if (p_variable->initializer != nullptr) { @@ -1362,6 +1378,12 @@ 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 (parser->current_function->get_datatype().has_container_element_type() && p_return->return_value->type == GDScriptParser::Node::ARRAY) { + update_array_literal_element_type(parser->current_function->get_datatype(), static_cast<GDScriptParser::ArrayNode *>(p_return->return_value)); + } + } result = p_return->return_value->get_datatype(); } else { // Return type is null by default. @@ -1498,6 +1520,52 @@ void GDScriptAnalyzer::reduce_array(GDScriptParser::ArrayNode *p_array) { p_array->set_datatype(arr_type); } +// 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; + } + } + } + } + } + if (all_same_type) { + 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); + } + } + // Update the type on the value itself. + p_array_literal->set_datatype(array_type); +} + void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assignment) { reduce_expression(p_assignment->assignee); reduce_expression(p_assignment->assigned_value); @@ -1506,24 +1574,33 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig return; } - if (p_assignment->assignee->get_datatype().is_constant) { + GDScriptParser::DataType assignee_type = p_assignment->assignee->get_datatype(); + + // 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)); + } + + GDScriptParser::DataType assigned_value_type = p_assignment->assigned_value->get_datatype(); + + if (assignee_type.is_constant) { push_error("Cannot assign a new value to a constant.", p_assignment->assignee); } - if (!p_assignment->assignee->get_datatype().is_variant() && !p_assignment->assigned_value->get_datatype().is_variant()) { + if (!assignee_type.is_variant() && !assigned_value_type.is_variant()) { bool compatible = true; - GDScriptParser::DataType op_type = p_assignment->assigned_value->get_datatype(); + GDScriptParser::DataType op_type = assigned_value_type; if (p_assignment->operation != GDScriptParser::AssignmentNode::OP_NONE) { - op_type = get_operation_type(p_assignment->variant_op, p_assignment->assignee->get_datatype(), p_assignment->assigned_value->get_datatype(), compatible, p_assignment->assigned_value); + op_type = get_operation_type(p_assignment->variant_op, assignee_type, assigned_value_type, compatible, p_assignment->assigned_value); } if (compatible) { - compatible = is_type_compatible(p_assignment->assignee->get_datatype(), op_type, true); + compatible = is_type_compatible(assignee_type, op_type, true); if (!compatible) { - if (p_assignment->assignee->get_datatype().is_hard_type()) { + if (assignee_type.is_hard_type()) { // Try reverse test since it can be a masked subtype. - if (!is_type_compatible(op_type, p_assignment->assignee->get_datatype(), true)) { - push_error(vformat(R"(Cannot assign a value of type "%s" to a target of type "%s".)", p_assignment->assigned_value->get_datatype().to_string(), p_assignment->assignee->get_datatype().to_string()), p_assignment->assigned_value); + if (!is_type_compatible(op_type, assignee_type, true)) { + push_error(vformat(R"(Cannot assign a value of type "%s" to a target of type "%s".)", assigned_value_type.to_string(), assignee_type.to_string()), p_assignment->assigned_value); } else { // TODO: Add warning. mark_node_unsafe(p_assignment); @@ -1534,11 +1611,11 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig } } } else { - push_error(vformat(R"(Invalid operands "%s" and "%s" for assignment operator.)", p_assignment->assignee->get_datatype().to_string(), p_assignment->assigned_value->get_datatype().to_string()), p_assignment); + push_error(vformat(R"(Invalid operands "%s" and "%s" for assignment operator.)", assignee_type.to_string(), assigned_value_type.to_string()), p_assignment); } } - if (p_assignment->assignee->get_datatype().has_no_type() || p_assignment->assigned_value->get_datatype().is_variant()) { + if (assignee_type.has_no_type() || assigned_value_type.is_variant()) { mark_node_unsafe(p_assignment); } @@ -1558,7 +1635,7 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig case GDScriptParser::IdentifierNode::LOCAL_VARIABLE: { GDScriptParser::DataType id_type = identifier->variable_source->get_datatype(); if (!id_type.is_hard_type()) { - id_type = p_assignment->assigned_value->get_datatype(); + id_type = assigned_value_type; id_type.type_source = GDScriptParser::DataType::INFERRED; id_type.is_constant = false; identifier->variable_source->set_datatype(id_type); @@ -1567,7 +1644,7 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig case GDScriptParser::IdentifierNode::LOCAL_ITERATOR: { GDScriptParser::DataType id_type = identifier->bind_source->get_datatype(); if (!id_type.is_hard_type()) { - id_type = p_assignment->assigned_value->get_datatype(); + id_type = assigned_value_type; id_type.type_source = GDScriptParser::DataType::INFERRED; id_type.is_constant = false; identifier->variable_source->set_datatype(id_type); @@ -1579,12 +1656,10 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig } } - GDScriptParser::DataType assignee_type = p_assignment->assignee->get_datatype(); - GDScriptParser::DataType assigned_type = p_assignment->assigned_value->get_datatype(); #ifdef DEBUG_ENABLED - if (p_assignment->assigned_value->type == GDScriptParser::Node::CALL && assigned_type.kind == GDScriptParser::DataType::BUILTIN && assigned_type.builtin_type == Variant::NIL) { + if (p_assignment->assigned_value->type == GDScriptParser::Node::CALL && assigned_value_type.kind == GDScriptParser::DataType::BUILTIN && assigned_value_type.builtin_type == Variant::NIL) { parser->push_warning(p_assignment->assigned_value, GDScriptWarning::VOID_ASSIGNMENT, static_cast<GDScriptParser::CallNode *>(p_assignment->assigned_value)->function_name); - } else if (assignee_type.is_hard_type() && assignee_type.builtin_type == Variant::INT && assigned_type.builtin_type == Variant::FLOAT) { + } else if (assignee_type.is_hard_type() && assignee_type.builtin_type == Variant::INT && assigned_value_type.builtin_type == Variant::FLOAT) { parser->push_warning(p_assignment->assigned_value, GDScriptWarning::NARROWING_CONVERSION); } #endif @@ -1728,8 +1803,12 @@ void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_o void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_await) { bool all_is_constant = true; + Map<int, GDScriptParser::ArrayNode *> arrays; // For array literal to potentially type when passing. for (int i = 0; i < p_call->arguments.size(); i++) { reduce_expression(p_call->arguments[i]); + if (p_call->arguments[i]->type == GDScriptParser::Node::ARRAY) { + arrays[i] = static_cast<GDScriptParser::ArrayNode *>(p_call->arguments[i]); + } all_is_constant = all_is_constant && p_call->arguments[i]->is_constant; } @@ -2007,6 +2086,13 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa List<GDScriptParser::DataType> par_types; if (get_function_signature(p_call, base_type, p_call->function_name, return_type, par_types, default_arg_count, is_static, is_vararg)) { + // If the function require typed arrays we must make literals be typed. + for (Map<int, GDScriptParser::ArrayNode *>::Element *E = arrays.front(); E; E = E->next()) { + 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->get()); + } + } validate_call_arg(par_types, default_arg_count, is_vararg, p_call); if (is_self && parser->current_function != nullptr && parser->current_function->is_static && !is_static) { @@ -2131,7 +2217,7 @@ void GDScriptAnalyzer::reduce_get_node(GDScriptParser::GetNodeNode *p_get_node) result.native_type = "Node"; result.builtin_type = Variant::OBJECT; - if (!ClassDB::is_parent_class(get_real_class_name(parser->current_class->base_type.native_type), result.native_type)) { + if (!ClassDB::is_parent_class(GDScriptParser::get_real_class_name(parser->current_class->base_type.native_type), result.native_type)) { push_error(R"*(Cannot use shorthand "get_node()" notation ("$") on a class that isn't a node.)*", p_get_node); } @@ -2297,7 +2383,7 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod } // Check native members. - const StringName &native = get_real_class_name(base.native_type); + const StringName &native = GDScriptParser::get_real_class_name(base.native_type); if (class_exists(native)) { PropertyInfo prop_info; @@ -2752,11 +2838,20 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri case Variant::TRANSFORM: case Variant::PLANE: case Variant::COLOR: - case Variant::ARRAY: case Variant::DICTIONARY: result_type.kind = GDScriptParser::DataType::VARIANT; result_type.type_source = GDScriptParser::DataType::UNDETECTED; break; + // Can have an element type. + case Variant::ARRAY: + if (base_type.has_container_element_type()) { + result_type = base_type.get_container_element_type(); + result_type.type_source = base_type.type_source; + } else { + result_type.kind = GDScriptParser::DataType::VARIANT; + result_type.type_source = GDScriptParser::DataType::UNDETECTED; + } + break; // Here for completeness. case Variant::OBJECT: case Variant::VARIANT_MAX: @@ -2979,6 +3074,34 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_property(const PropertyInfo result.native_type = p_property.class_name == StringName() ? "Object" : p_property.class_name; } else { result.kind = GDScriptParser::DataType::BUILTIN; + result.builtin_type = p_property.type; + if (p_property.type == Variant::ARRAY && p_property.hint == PROPERTY_HINT_ARRAY_TYPE) { + // Check element type. + StringName elem_type_name = p_property.hint_string; + GDScriptParser::DataType elem_type; + elem_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + + Variant::Type elem_builtin_type = GDScriptParser::get_builtin_type(elem_type_name); + if (elem_builtin_type < Variant::VARIANT_MAX) { + // Builtin type. + elem_type.kind = GDScriptParser::DataType::BUILTIN; + elem_type.builtin_type = elem_builtin_type; + } else if (class_exists(elem_type_name)) { + elem_type.kind = GDScriptParser::DataType::NATIVE; + elem_type.builtin_type = Variant::OBJECT; + elem_type.native_type = p_property.hint_string; + } else if (ScriptServer::is_global_class(elem_type_name)) { + // Just load this as it shouldn't be a GDScript. + Ref<Script> script = ResourceLoader::load(ScriptServer::get_global_class_path(elem_type_name)); + elem_type.kind = GDScriptParser::DataType::SCRIPT; + elem_type.builtin_type = Variant::OBJECT; + elem_type.native_type = script->get_instance_base_type(); + elem_type.script_type = script; + } else { + ERR_FAIL_V_MSG(result, "Could not find element type from property hint of a typed array."); + } + result.set_container_element_type(elem_type); + } } return result; } @@ -3084,7 +3207,7 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, GD return true; } - StringName real_native = get_real_class_name(base_native); + StringName real_native = GDScriptParser::get_real_class_name(base_native); MethodInfo info; if (ClassDB::get_method_info(real_native, function_name, &info)) { @@ -3179,7 +3302,7 @@ bool GDScriptAnalyzer::is_shadowing(GDScriptParser::IdentifierNode *p_local, con StringName parent = base_native; while (parent != StringName()) { - StringName real_class_name = get_real_class_name(parent); + StringName real_class_name = GDScriptParser::get_real_class_name(parent); if (ClassDB::has_method(real_class_name, name, true)) { parser->push_warning(p_local, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_local->name, "method", parent); return true; @@ -3257,6 +3380,18 @@ bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_targ // Enum value is also integer. valid = true; } + 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(), false); + } + } + } return valid; } @@ -3329,14 +3464,14 @@ bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_targ } // Get underscore-prefixed version for some classes. - src_native = get_real_class_name(src_native); + src_native = GDScriptParser::get_real_class_name(src_native); switch (p_target.kind) { case GDScriptParser::DataType::NATIVE: { if (p_target.is_meta_type) { return ClassDB::is_parent_class(src_native, GDScriptNativeClass::get_class_static()); } - StringName tgt_native = get_real_class_name(p_target.native_type); + StringName tgt_native = GDScriptParser::get_real_class_name(p_target.native_type); return ClassDB::is_parent_class(src_native, tgt_native); } case GDScriptParser::DataType::SCRIPT: @@ -3385,8 +3520,8 @@ void GDScriptAnalyzer::mark_node_unsafe(const GDScriptParser::Node *p_node) { #endif } -bool GDScriptAnalyzer::class_exists(const StringName &p_class) { - StringName real_name = get_real_class_name(p_class); +bool GDScriptAnalyzer::class_exists(const StringName &p_class) const { + StringName real_name = GDScriptParser::get_real_class_name(p_class); return ClassDB::class_exists(real_name) && ClassDB::is_class_exposed(real_name); } diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h index dab5b032a3..8430d3f4a5 100644 --- a/modules/gdscript/gdscript_analyzer.h +++ b/modules/gdscript/gdscript_analyzer.h @@ -103,10 +103,11 @@ class GDScriptAnalyzer { bool validate_call_arg(const MethodInfo &p_method, const GDScriptParser::CallNode *p_call); 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_array_literal_element_type(const GDScriptParser::DataType &p_base_type, GDScriptParser::ArrayNode *p_array_literal); bool is_type_compatible(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion = false) const; void push_error(const String &p_message, const GDScriptParser::Node *p_origin); void mark_node_unsafe(const GDScriptParser::Node *p_node); - bool class_exists(const StringName &p_class); + bool class_exists(const StringName &p_class) const; Ref<GDScriptParserRef> get_parser_for(const String &p_path); #ifdef DEBUG_ENABLED bool is_shadowing(GDScriptParser::IdentifierNode *p_local, const String &p_context); @@ -119,8 +120,6 @@ public: Error analyze(); GDScriptAnalyzer(GDScriptParser *p_parser); - - static void cleanup(); }; #endif // GDSCRIPT_ANALYZER_H diff --git a/modules/gdscript/gdscript_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp index 58c6b31a77..e27329d18b 100644 --- a/modules/gdscript/gdscript_byte_codegen.cpp +++ b/modules/gdscript/gdscript_byte_codegen.cpp @@ -599,10 +599,16 @@ void GDScriptByteCodeGenerator::write_assign(const Address &p_target, const Addr // Typed assignment. switch (p_target.type.kind) { case GDScriptDataType::BUILTIN: { - append(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN, 2); - append(p_target); - append(p_source); - append(p_target.type.builtin_type); + if (p_target.type.builtin_type == Variant::ARRAY && p_target.type.has_container_element_type()) { + append(GDScriptFunction::OPCODE_ASSIGN_TYPED_ARRAY, 2); + append(p_target); + append(p_source); + } else { + append(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN, 2); + append(p_target); + append(p_source); + append(p_target.type.builtin_type); + } } break; case GDScriptDataType::NATIVE: { int class_idx = GDScriptLanguage::get_singleton()->get_global_map()[p_target.type.native_type]; @@ -633,7 +639,11 @@ void GDScriptByteCodeGenerator::write_assign(const Address &p_target, const Addr } } } else { - if (p_target.type.kind == GDScriptDataType::BUILTIN && p_source.type.kind == GDScriptDataType::BUILTIN && p_target.type.builtin_type != p_source.type.builtin_type) { + if (p_target.type.kind == GDScriptDataType::BUILTIN && p_target.type.builtin_type == Variant::ARRAY && p_target.type.has_container_element_type()) { + append(GDScriptFunction::OPCODE_ASSIGN_TYPED_ARRAY, 2); + append(p_target); + append(p_source); + } 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(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN, 2); append(p_target); @@ -980,6 +990,25 @@ void GDScriptByteCodeGenerator::write_construct_array(const Address &p_target, c append(p_arguments.size()); } +void GDScriptByteCodeGenerator::write_construct_typed_array(const Address &p_target, const GDScriptDataType &p_element_type, const Vector<Address> &p_arguments) { + append(GDScriptFunction::OPCODE_CONSTRUCT_TYPED_ARRAY, 2 + p_arguments.size()); + for (int i = 0; i < p_arguments.size(); i++) { + append(p_arguments[i]); + } + append(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_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS; + append(addr); + } else { + append(Address()); // null. + } + append(p_arguments.size()); + append(p_element_type.builtin_type); + append(p_element_type.native_type); +} + void GDScriptByteCodeGenerator::write_construct_dictionary(const Address &p_target, const Vector<Address> &p_arguments) { append(GDScriptFunction::OPCODE_CONSTRUCT_DICTIONARY, 1 + p_arguments.size()); for (int i = 0; i < p_arguments.size(); i++) { diff --git a/modules/gdscript/gdscript_byte_codegen.h b/modules/gdscript/gdscript_byte_codegen.h index 1e66af269a..52d631c430 100644 --- a/modules/gdscript/gdscript_byte_codegen.h +++ b/modules/gdscript/gdscript_byte_codegen.h @@ -445,6 +445,7 @@ public: virtual void write_call_script_function(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) override; virtual void write_construct(const Address &p_target, Variant::Type p_type, const Vector<Address> &p_arguments) override; virtual void write_construct_array(const Address &p_target, const Vector<Address> &p_arguments) override; + virtual void write_construct_typed_array(const Address &p_target, const GDScriptDataType &p_element_type, const Vector<Address> &p_arguments) override; virtual void write_construct_dictionary(const Address &p_target, const Vector<Address> &p_arguments) override; virtual void write_await(const Address &p_target, const Address &p_operand) override; virtual void write_if(const Address &p_condition) override; diff --git a/modules/gdscript/gdscript_codegen.h b/modules/gdscript/gdscript_codegen.h index d72bd12033..3c05f14cf7 100644 --- a/modules/gdscript/gdscript_codegen.h +++ b/modules/gdscript/gdscript_codegen.h @@ -137,6 +137,7 @@ public: virtual void write_call_script_function(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) = 0; virtual void write_construct(const Address &p_target, Variant::Type p_type, const Vector<Address> &p_arguments) = 0; virtual void write_construct_array(const Address &p_target, const Vector<Address> &p_arguments) = 0; + virtual void write_construct_typed_array(const Address &p_target, const GDScriptDataType &p_element_type, const Vector<Address> &p_arguments) = 0; virtual void write_construct_dictionary(const Address &p_target, const Vector<Address> &p_arguments) = 0; virtual void write_await(const Address &p_target, const Address &p_operand) = 0; virtual void write_if(const Address &p_condition) = 0; diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index 63ca34fc24..62bb6a9f9e 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -137,22 +137,22 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D } } } break; + case GDScriptParser::DataType::ENUM: case GDScriptParser::DataType::ENUM_VALUE: result.has_type = true; result.kind = GDScriptDataType::BUILTIN; result.builtin_type = Variant::INT; break; - case GDScriptParser::DataType::ENUM: - result.has_type = true; - result.kind = GDScriptDataType::BUILTIN; - result.builtin_type = Variant::DICTIONARY; - break; case GDScriptParser::DataType::UNRESOLVED: { ERR_PRINT("Parser bug: converting unresolved type."); return GDScriptDataType(); } } + if (p_datatype.has_container_element_type()) { + result.set_container_element_type(_gdtype_from_datatype(p_datatype.get_container_element_type())); + } + // Only hold strong reference to the script if it's not the owner of the // element qualified with this type, to avoid cyclic references (leaks). if (result.script_type && result.script_type == p_owner) { @@ -376,10 +376,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code Vector<GDScriptCodeGenerator::Address> values; // Create the result temporary first since it's the last to be killed. - GDScriptDataType array_type; - array_type.has_type = true; - array_type.kind = GDScriptDataType::BUILTIN; - array_type.builtin_type = Variant::ARRAY; + GDScriptDataType array_type = _gdtype_from_datatype(an->get_datatype()); GDScriptCodeGenerator::Address result = codegen.add_temporary(array_type); for (int i = 0; i < an->elements.size(); i++) { @@ -390,7 +387,11 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code values.push_back(val); } - gen->write_construct_array(result, values); + if (array_type.has_container_element_type()) { + gen->write_construct_typed_array(result, array_type.get_container_element_type(), values); + } else { + gen->write_construct_array(result, values); + } for (int i = 0; i < values.size(); i++) { if (values[i].mode == GDScriptCodeGenerator::Address::TEMPORARY) { @@ -1733,8 +1734,17 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui const GDScriptParser::VariableNode *lv = static_cast<const GDScriptParser::VariableNode *>(s); // Should be already in stack when the block began. GDScriptCodeGenerator::Address local = codegen.locals[lv->identifier->name]; + GDScriptParser::DataType local_type = lv->get_datatype(); if (lv->initializer != nullptr) { + // For typed arrays we need to make sure this is already initialized correctly so typed assignment work. + if (local_type.is_hard_type() && local_type.builtin_type == Variant::ARRAY) { + if (local_type.has_container_element_type()) { + codegen.generator->write_construct_typed_array(local, _gdtype_from_datatype(local_type.get_container_element_type(), codegen.script), Vector<GDScriptCodeGenerator::Address>()); + } else { + codegen.generator->write_construct_array(local, Vector<GDScriptCodeGenerator::Address>()); + } + } GDScriptCodeGenerator::Address src_address = _parse_expression(codegen, error, lv->initializer); if (error) { return error; @@ -1743,6 +1753,14 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui if (src_address.mode == GDScriptCodeGenerator::Address::TEMPORARY) { codegen.generator->pop_temporary(); } + } else if (lv->get_datatype().is_hard_type()) { + // Initialize with default for type. + if (local_type.has_container_element_type()) { + codegen.generator->write_construct_typed_array(local, _gdtype_from_datatype(local_type.get_container_element_type(), codegen.script), Vector<GDScriptCodeGenerator::Address>()); + } else if (local_type.kind == GDScriptParser::DataType::BUILTIN) { + codegen.generator->write_construct(local, local_type.builtin_type, Vector<GDScriptCodeGenerator::Address>()); + } + // The `else` branch is for objects, in such case we leave it as `null`. } } break; case GDScriptParser::Node::CONSTANT: { @@ -1844,21 +1862,41 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser continue; } + GDScriptParser::DataType field_type = field->get_datatype(); + + GDScriptCodeGenerator::Address dst_address(GDScriptCodeGenerator::Address::MEMBER, codegen.script->member_indices[field->identifier->name].index, _gdtype_from_datatype(field->get_datatype())); if (field->initializer) { // 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.is_hard_type() && field_type.builtin_type == Variant::ARRAY && field_type.has_container_element_type()) { + if (field_type.has_container_element_type()) { + codegen.generator->write_construct_typed_array(dst_address, _gdtype_from_datatype(field_type.get_container_element_type(), codegen.script), Vector<GDScriptCodeGenerator::Address>()); + } else { + codegen.generator->write_construct_array(dst_address, Vector<GDScriptCodeGenerator::Address>()); + } + } GDScriptCodeGenerator::Address src_address = _parse_expression(codegen, error, field->initializer, false, true); if (error) { memdelete(codegen.generator); return error; } - GDScriptCodeGenerator::Address dst_address(GDScriptCodeGenerator::Address::MEMBER, codegen.script->member_indices[field->identifier->name].index, _gdtype_from_datatype(field->get_datatype())); codegen.generator->write_assign(dst_address, src_address); if (src_address.mode == GDScriptCodeGenerator::Address::TEMPORARY) { codegen.generator->pop_temporary(); } + } else if (field->get_datatype().is_hard_type()) { + codegen.generator->write_newline(field->start_line); + + // Initialize with default for type. + if (field_type.has_container_element_type()) { + codegen.generator->write_construct_typed_array(dst_address, _gdtype_from_datatype(field_type.get_container_element_type(), codegen.script), Vector<GDScriptCodeGenerator::Address>()); + } else if (field_type.kind == GDScriptParser::DataType::BUILTIN) { + codegen.generator->write_construct(dst_address, field_type.builtin_type, Vector<GDScriptCodeGenerator::Address>()); + } + // The `else` branch is for objects, in such case we leave it as `null`. } } } @@ -2176,9 +2214,8 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar prop_info.hint = export_info.hint; prop_info.hint_string = export_info.hint_string; prop_info.usage = export_info.usage; - } else { - prop_info.usage = PROPERTY_USAGE_SCRIPT_VARIABLE; } + prop_info.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE; #ifdef TOOLS_ENABLED p_script->doc_variables[name] = variable->doc_description; #endif diff --git a/modules/gdscript/gdscript_disassembler.cpp b/modules/gdscript/gdscript_disassembler.cpp index 17cb5e3c96..32adca29ed 100644 --- a/modules/gdscript/gdscript_disassembler.cpp +++ b/modules/gdscript/gdscript_disassembler.cpp @@ -322,6 +322,14 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { incr += 4; } break; + case OPCODE_ASSIGN_TYPED_ARRAY: { + text += "assign typed array "; + text += DADDR(1); + text += " = "; + text += DADDR(2); + + incr += 3; + } break; case OPCODE_ASSIGN_TYPED_NATIVE: { text += "assign typed native ("; text += DADDR(3); @@ -426,6 +434,39 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { incr += 3 + argc; } break; + case OPCODE_CONSTRUCT_TYPED_ARRAY: { + int argc = _code_ptr[ip + 1 + instr_var_args]; + + Ref<Script> script_type = get_constant(_code_ptr[ip + argc + 2]); + Variant::Type builtin_type = (Variant::Type)_code_ptr[ip + argc + 4]; + StringName native_type = get_global_name(_code_ptr[ip + argc + 5]); + + String type_name; + if (script_type.is_valid() && script_type->is_valid()) { + type_name = script_type->get_path(); + } else if (native_type != StringName()) { + type_name = native_type; + } else { + type_name = Variant::get_type_name(builtin_type); + } + + text += " make_typed_array ("; + text += type_name; + text += ") "; + + text += DADDR(1 + argc); + text += " = ["; + + for (int i = 0; i < argc; i++) { + if (i > 0) + text += ", "; + text += DADDR(1 + i); + } + + text += "]"; + + incr += 3 + argc; + } break; case OPCODE_CONSTRUCT_DICTIONARY: { int argc = _code_ptr[ip + 1 + instr_var_args]; text += "make_dict "; diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index a9975c8602..d560718dda 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -500,36 +500,6 @@ struct GDScriptCompletionIdentifier { const GDScriptParser::ExpressionNode *assigned_expression = nullptr; }; -// TODO: Move this to a central location (maybe core?). -static const char *underscore_classes[] = { - "ClassDB", - "Directory", - "Engine", - "File", - "Geometry", - "GodotSharp", - "JSON", - "Marshalls", - "Mutex", - "OS", - "ResourceLoader", - "ResourceSaver", - "Semaphore", - "Thread", - "VisualScriptEditor", - nullptr, -}; -static StringName _get_real_class_name(const StringName &p_source) { - const char **class_name = underscore_classes; - while (*class_name != nullptr) { - if (p_source == *class_name) { - return String("_") + p_source; - } - class_name++; - } - return p_source; -} - static String _get_visual_datatype(const PropertyInfo &p_info, bool p_is_arg = true) { if (p_info.usage & PROPERTY_USAGE_CLASS_IS_ENUM) { String enum_name = p_info.class_name; @@ -930,7 +900,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base } } break; case GDScriptParser::DataType::NATIVE: { - StringName type = _get_real_class_name(base_type.native_type); + StringName type = GDScriptParser::get_real_class_name(base_type.native_type); if (!ClassDB::class_exists(type)) { return; } @@ -1783,7 +1753,7 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, base_type = GDScriptParser::DataType(); break; } - StringName real_native = _get_real_class_name(base_type.native_type); + StringName real_native = GDScriptParser::get_real_class_name(base_type.native_type); MethodInfo info; if (ClassDB::get_method_info(real_native, p_context.current_function->identifier->name, &info)) { for (const List<PropertyInfo>::Element *E = info.arguments.front(); E; E = E->next()) { @@ -1854,7 +1824,7 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, } // Check ClassDB. - StringName class_name = _get_real_class_name(p_identifier); + StringName class_name = GDScriptParser::get_real_class_name(p_identifier); if (ClassDB::class_exists(class_name) && ClassDB::is_class_exposed(class_name)) { r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; r_type.type.kind = GDScriptParser::DataType::NATIVE; @@ -1970,7 +1940,7 @@ static bool _guess_identifier_type_from_base(GDScriptParser::CompletionContext & } } break; case GDScriptParser::DataType::NATIVE: { - StringName class_name = _get_real_class_name(base_type.native_type); + StringName class_name = GDScriptParser::get_real_class_name(base_type.native_type); if (!ClassDB::class_exists(class_name)) { return false; } @@ -2133,7 +2103,7 @@ static bool _guess_method_return_type_from_base(GDScriptParser::CompletionContex } } break; case GDScriptParser::DataType::NATIVE: { - StringName native = _get_real_class_name(base_type.native_type); + StringName native = GDScriptParser::get_real_class_name(base_type.native_type); if (!ClassDB::class_exists(native)) { return false; } @@ -2230,7 +2200,7 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c base_type = base_type.class_type->base_type; } break; case GDScriptParser::DataType::NATIVE: { - StringName class_name = _get_real_class_name(base_type.native_type); + StringName class_name = GDScriptParser::get_real_class_name(base_type.native_type); if (!ClassDB::class_exists(class_name)) { base_type.kind = GDScriptParser::DataType::UNRESOLVED; break; @@ -2615,7 +2585,7 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path break; } - StringName class_name = _get_real_class_name(native_type.native_type); + StringName class_name = GDScriptParser::get_real_class_name(native_type.native_type); if (!ClassDB::class_exists(class_name)) { break; } @@ -2844,7 +2814,7 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co } } break; case GDScriptParser::DataType::NATIVE: { - StringName class_name = _get_real_class_name(base_type.native_type); + StringName class_name = GDScriptParser::get_real_class_name(base_type.native_type); if (!ClassDB::class_exists(class_name)) { base_type.kind = GDScriptParser::DataType::UNRESOLVED; break; diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h index e64630a743..41080ef371 100644 --- a/modules/gdscript/gdscript_function.h +++ b/modules/gdscript/gdscript_function.h @@ -43,7 +43,11 @@ class GDScriptInstance; class GDScript; -struct GDScriptDataType { +class GDScriptDataType { +private: + GDScriptDataType *container_element_type = nullptr; + +public: enum Kind { UNINITIALIZED, BUILTIN, @@ -71,7 +75,24 @@ struct GDScriptDataType { case BUILTIN: { Variant::Type var_type = p_variant.get_type(); bool valid = builtin_type == var_type; - if (!valid && p_allow_implicit_conversion) { + if (valid && builtin_type == Variant::ARRAY && has_container_element_type()) { + Array array = p_variant; + if (array.is_typed()) { + Variant::Type array_builtin_type = (Variant::Type)array.get_typed_builtin(); + StringName array_native_type = array.get_typed_class_name(); + Ref<Script> array_script_type_ref = array.get_typed_script(); + + if (array_script_type_ref.is_valid()) { + valid = (container_element_type->kind == SCRIPT || container_element_type->kind == GDSCRIPT) && container_element_type->script_type == array_script_type_ref.ptr(); + } else if (array_native_type != StringName()) { + valid = container_element_type->kind == NATIVE && container_element_type->native_type == array_native_type; + } else { + valid = container_element_type->kind == BUILTIN && container_element_type->builtin_type == array_builtin_type; + } + } else { + valid = false; + } + } else if (!valid && p_allow_implicit_conversion) { valid = Variant::can_convert_strict(var_type, builtin_type); } return valid; @@ -153,7 +174,49 @@ struct GDScriptDataType { return info; } - GDScriptDataType() {} + void set_container_element_type(const GDScriptDataType &p_element_type) { + container_element_type = memnew(GDScriptDataType(p_element_type)); + } + + GDScriptDataType get_container_element_type() const { + ERR_FAIL_COND_V(container_element_type == nullptr, GDScriptDataType()); + return *container_element_type; + } + + bool has_container_element_type() const { + return container_element_type != nullptr; + } + + void unset_container_element_type() { + if (container_element_type) { + memdelete(container_element_type); + } + container_element_type = nullptr; + } + + GDScriptDataType() = default; + + GDScriptDataType &operator=(const GDScriptDataType &p_other) { + kind = p_other.kind; + has_type = p_other.has_type; + builtin_type = p_other.builtin_type; + native_type = p_other.native_type; + script_type = p_other.script_type; + script_type_ref = p_other.script_type_ref; + unset_container_element_type(); + if (p_other.has_container_element_type()) { + set_container_element_type(p_other.get_container_element_type()); + } + return *this; + } + + GDScriptDataType(const GDScriptDataType &p_other) { + *this = p_other; + } + + ~GDScriptDataType() { + unset_container_element_type(); + } }; class GDScriptFunction { @@ -179,6 +242,7 @@ public: OPCODE_ASSIGN_TRUE, OPCODE_ASSIGN_FALSE, OPCODE_ASSIGN_TYPED_BUILTIN, + OPCODE_ASSIGN_TYPED_ARRAY, OPCODE_ASSIGN_TYPED_NATIVE, OPCODE_ASSIGN_TYPED_SCRIPT, OPCODE_CAST_TO_BUILTIN, @@ -187,6 +251,7 @@ public: OPCODE_CONSTRUCT, // Only for basic types! OPCODE_CONSTRUCT_VALIDATED, // Only for basic types! OPCODE_CONSTRUCT_ARRAY, + OPCODE_CONSTRUCT_TYPED_ARRAY, OPCODE_CONSTRUCT_DICTIONARY, OPCODE_CALL, OPCODE_CALL_RETURN, diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 7f3dd6b2e5..695154e9a9 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -94,8 +94,43 @@ Variant::Type GDScriptParser::get_builtin_type(const StringName &p_type) { return Variant::VARIANT_MAX; } +// TODO: Move this to a central location (maybe core?). +static HashMap<StringName, StringName> underscore_map; +static const char *underscore_classes[] = { + "ClassDB", + "Directory", + "Engine", + "File", + "Geometry", + "GodotSharp", + "JSON", + "Marshalls", + "Mutex", + "OS", + "ResourceLoader", + "ResourceSaver", + "Semaphore", + "Thread", + "VisualScriptEditor", + nullptr, +}; +StringName GDScriptParser::get_real_class_name(const StringName &p_source) { + if (underscore_map.is_empty()) { + const char **class_name = underscore_classes; + while (*class_name != nullptr) { + underscore_map[*class_name] = String("_") + *class_name; + class_name++; + } + } + if (underscore_map.has(p_source)) { + return underscore_map[p_source]; + } + return p_source; +} + void GDScriptParser::cleanup() { builtin_types.clear(); + underscore_map.clear(); } void GDScriptParser::get_annotation_list(List<MethodInfo> *r_annotations) const { @@ -109,12 +144,11 @@ void GDScriptParser::get_annotation_list(List<MethodInfo> *r_annotations) const GDScriptParser::GDScriptParser() { // Register valid annotations. // TODO: Should this be static? - // TODO: Validate applicable types (e.g. a VARIABLE annotation that only applies to string variables). register_annotation(MethodInfo("@tool"), AnnotationInfo::SCRIPT, &GDScriptParser::tool_annotation); register_annotation(MethodInfo("@icon", { Variant::STRING, "icon_path" }), AnnotationInfo::SCRIPT, &GDScriptParser::icon_annotation); register_annotation(MethodInfo("@onready"), AnnotationInfo::VARIABLE, &GDScriptParser::onready_annotation); // Export annotations. - register_annotation(MethodInfo("@export"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_TYPE_STRING, Variant::NIL>); + register_annotation(MethodInfo("@export"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_NONE, Variant::NIL>); register_annotation(MethodInfo("@export_enum", { Variant::STRING, "names" }), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_ENUM, Variant::INT>, 0, true); register_annotation(MethodInfo("@export_file", { Variant::STRING, "filter" }), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_FILE, Variant::STRING>, 1, true); register_annotation(MethodInfo("@export_dir"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_DIR, Variant::STRING>); @@ -680,7 +714,6 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)() while (!annotation_stack.is_empty()) { AnnotationNode *last_annotation = annotation_stack.back()->get(); if (last_annotation->applies_to(p_target)) { - last_annotation->apply(this, member); member->annotations.push_front(last_annotation); annotation_stack.pop_back(); } else { @@ -811,6 +844,9 @@ GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_allow_proper if (match(GDScriptTokenizer::Token::EQUAL)) { // Initializer. variable->initializer = parse_expression(false); + if (variable->initializer == nullptr) { + push_error(R"(Expected expression for variable initial value after "=".)"); + } variable->assignments++; } @@ -2674,6 +2710,19 @@ GDScriptParser::TypeNode *GDScriptParser::parse_type(bool p_allow_void) { type->type_chain.push_back(type_element); + if (match(GDScriptTokenizer::Token::BRACKET_OPEN)) { + // Typed collection (like Array[int]). + type->container_type = parse_type(false); // Don't allow void for array element type. + if (type->container_type == nullptr) { + push_error(R"(Expected type for collection after "[".)"); + type = nullptr; + } else if (type->container_type->container_type != nullptr) { + push_error("Nested typed collections are not supported."); + } + consume(GDScriptTokenizer::Token::BRACKET_CLOSE, R"(Expected closing "]" after collection type.)"); + return type; + } + int chain_index = 1; while (match(GDScriptTokenizer::Token::PERIOD)) { make_completion_context(COMPLETION_TYPE_ATTRIBUTE, type, chain_index++); @@ -3160,29 +3209,10 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node } variable->exported = true; - // TODO: Improving setting type, especially for range hints, which can be int or float. + variable->export_info.type = t_type; variable->export_info.hint = t_hint; - if (p_annotation->name == "@export") { - if (variable->datatype_specifier == nullptr) { - if (variable->initializer == nullptr) { - push_error(R"(Cannot use "@export" annotation with variable without type or initializer, since type can't be inferred.)", p_annotation); - return false; - } - if (variable->initializer->type == Node::LITERAL) { - variable->export_info.type = static_cast<LiteralNode *>(variable->initializer)->value.get_type(); - } else if (variable->initializer->type == Node::ARRAY) { - variable->export_info.type = Variant::ARRAY; - } else if (variable->initializer->type == Node::DICTIONARY) { - variable->export_info.type = Variant::DICTIONARY; - } else { - push_error(R"(To use "@export" annotation with type-less variable, the default value must be a literal.)", p_annotation); - return false; - } - } // else: Actual type will be set by the analyzer, which can infer the proper type. - } - String hint_string; for (int i = 0; i < p_annotation->resolved_arguments.size(); i++) { if (i > 0) { @@ -3193,6 +3223,86 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node variable->export_info.hint_string = hint_string; + // This is called after tne analyzer is done finding the type, so this should be set here. + DataType export_type = variable->get_datatype(); + + if (p_annotation->name == "@export") { + if (variable->datatype_specifier == nullptr && variable->initializer == nullptr) { + push_error(R"(Cannot use simple "@export" annotation with variable without type or initializer, since type can't be inferred.)", p_annotation); + return false; + } + + bool is_array = false; + + if (export_type.builtin_type == Variant::ARRAY && export_type.has_container_element_type()) { + export_type = export_type.get_container_element_type(); // Use inner type for. + is_array = true; + } + + if (export_type.is_variant() || export_type.has_no_type()) { + push_error(R"(Cannot use simple "@export" annotation because the type of the initialized value can't be inferred.)", p_annotation); + return false; + } + + switch (export_type.kind) { + case GDScriptParser::DataType::BUILTIN: + variable->export_info.type = export_type.builtin_type; + variable->export_info.hint = PROPERTY_HINT_NONE; + variable->export_info.hint_string = Variant::get_type_name(export_type.builtin_type); + break; + case GDScriptParser::DataType::NATIVE: + if (ClassDB::is_parent_class(get_real_class_name(export_type.native_type), "Resource")) { + variable->export_info.type = Variant::OBJECT; + variable->export_info.hint = PROPERTY_HINT_RESOURCE_TYPE; + variable->export_info.hint_string = get_real_class_name(export_type.native_type); + } else { + push_error(R"(Export type can only be built-in, a resource, or an enum.)", variable); + return false; + } + break; + case GDScriptParser::DataType::ENUM: { + variable->export_info.type = Variant::INT; + variable->export_info.hint = PROPERTY_HINT_ENUM; + + String enum_hint_string; + for (const Map<StringName, int>::Element *E = export_type.enum_values.front(); E; E = E->next()) { + enum_hint_string += E->key().operator String().camelcase_to_underscore(true).capitalize().xml_escape(); + enum_hint_string += ":"; + enum_hint_string += String::num_int64(E->get()).xml_escape(); + + if (E->next()) { + enum_hint_string += ","; + } + } + + variable->export_info.hint_string = enum_hint_string; + } break; + default: + // TODO: Allow custom user resources. + push_error(R"(Export type can only be built-in, a resource, or an enum.)", variable); + break; + } + + if (is_array) { + String hint_prefix = itos(variable->export_info.type); + if (variable->export_info.hint) { + hint_prefix += "/" + itos(variable->export_info.hint); + } + variable->export_info.hint = PROPERTY_HINT_TYPE_STRING; + variable->export_info.hint_string = hint_prefix + ":" + variable->export_info.hint_string; + variable->export_info.type = Variant::ARRAY; + } + } else { + // Validate variable type with export. + if (!export_type.is_variant() && (export_type.kind != DataType::BUILTIN || export_type.builtin_type != t_type)) { + // Allow float/int conversion. + if ((t_type != Variant::FLOAT || export_type.builtin_type != Variant::INT) && (t_type != Variant::INT || export_type.builtin_type != Variant::FLOAT)) { + push_error(vformat(R"("%s" annotation requires a variable of type "%s" but type "%s" was given instead.)", p_annotation->name.operator String(), Variant::get_type_name(t_type), export_type.to_string()), variable); + return false; + } + } + } + return true; } @@ -3278,6 +3388,9 @@ String GDScriptParser::DataType::to_string() const { if (builtin_type == Variant::NIL) { return "null"; } + if (builtin_type == Variant::ARRAY && has_container_element_type()) { + return vformat("Array[%s]", container_element_type->to_string()); + } return Variant::get_type_name(builtin_type); case NATIVE: if (is_meta_type) { diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index a4b1d4c866..272d21ffce 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -94,7 +94,12 @@ public: struct VariableNode; struct WhileNode; - struct DataType { + class DataType { + private: + // Private access so we can control memory management. + DataType *container_element_type = nullptr; + + public: enum Kind { BUILTIN, NATIVE, @@ -104,7 +109,6 @@ public: ENUM_VALUE, // Value from enumeration. VARIANT, // Can be any type. UNRESOLVED, - // TODO: Enum }; Kind kind = UNRESOLVED; @@ -128,7 +132,7 @@ public: ClassNode *class_type = nullptr; MethodInfo method_info; // For callable/signals. - HashMap<StringName, int> enum_values; // For enums. + Map<StringName, int> enum_values; // For enums. _FORCE_INLINE_ bool is_set() const { return kind != UNRESOLVED; } _FORCE_INLINE_ bool has_no_type() const { return type_source == UNDETECTED; } @@ -136,6 +140,26 @@ public: _FORCE_INLINE_ bool is_hard_type() const { return type_source > INFERRED; } String to_string() const; + _FORCE_INLINE_ void set_container_element_type(const DataType &p_type) { + container_element_type = memnew(DataType(p_type)); + } + + _FORCE_INLINE_ DataType get_container_element_type() const { + ERR_FAIL_COND_V(container_element_type == nullptr, DataType()); + return *container_element_type; + } + + _FORCE_INLINE_ bool has_container_element_type() const { + return container_element_type != nullptr; + } + + _FORCE_INLINE_ void unset_container_element_type() { + if (container_element_type) { + memdelete(container_element_type); + }; + container_element_type = nullptr; + } + bool operator==(const DataType &p_other) const { if (type_source == UNDETECTED || p_other.type_source == UNDETECTED) { return true; // Can be consireded equal for parsing purposes. @@ -173,6 +197,37 @@ public: bool operator!=(const DataType &p_other) const { return !(this->operator==(p_other)); } + + DataType &operator=(const DataType &p_other) { + kind = p_other.kind; + type_source = p_other.type_source; + is_constant = p_other.is_constant; + is_meta_type = p_other.is_meta_type; + is_coroutine = p_other.is_coroutine; + builtin_type = p_other.builtin_type; + native_type = p_other.native_type; + enum_type = p_other.enum_type; + script_type = p_other.script_type; + script_path = p_other.script_path; + class_type = p_other.class_type; + method_info = p_other.method_info; + enum_values = p_other.enum_values; + unset_container_element_type(); + if (p_other.has_container_element_type()) { + set_container_element_type(p_other.get_container_element_type()); + } + return *this; + } + + DataType() = default; + + DataType(const DataType &p_other) { + *this = p_other; + } + + ~DataType() { + unset_container_element_type(); + } }; struct ParserError { @@ -987,6 +1042,7 @@ public: struct TypeNode : public Node { Vector<IdentifierNode *> type_chain; + TypeNode *container_type = nullptr; TypeNode() { type = TYPE; @@ -1313,6 +1369,7 @@ public: ClassNode *get_tree() const { return head; } bool is_tool() const { return _is_tool; } static Variant::Type get_builtin_type(const StringName &p_type); + static StringName get_real_class_name(const StringName &p_source); CompletionContext get_completion_context() const { return completion_context; } CompletionCall get_completion_call() const { return completion_call; } diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp index 2216fcab2d..ffc871bdcb 100644 --- a/modules/gdscript/gdscript_vm.cpp +++ b/modules/gdscript/gdscript_vm.cpp @@ -127,6 +127,14 @@ Variant *GDScriptFunction::_get_variant(int p_address, GDScriptInstance *p_insta } #ifdef DEBUG_ENABLED +static String _get_script_name(const Ref<Script> p_script) { + if (p_script->get_name().is_empty()) { + return p_script->get_path().get_file(); + } else { + return p_script->get_name(); + } +} + static String _get_var_type(const Variant *p_var) { String basestr; @@ -140,15 +148,30 @@ static String _get_var_type(const Variant *p_var) { basestr = "previously freed"; } } else { + basestr = bobj->get_class(); if (bobj->get_script_instance()) { - basestr = bobj->get_class() + " (" + bobj->get_script_instance()->get_script()->get_path().get_file() + ")"; - } else { - basestr = bobj->get_class(); + basestr += " (" + _get_script_name(bobj->get_script_instance()->get_script()) + ")"; } } } else { - basestr = Variant::get_type_name(p_var->get_type()); + if (p_var->get_type() == Variant::ARRAY) { + 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) + "]"; + } + } else { + basestr = Variant::get_type_name(p_var->get_type()); + } } return basestr; @@ -207,6 +230,7 @@ String GDScriptFunction::_get_call_error(const Callable::CallError &p_err, const &&OPCODE_ASSIGN_TRUE, \ &&OPCODE_ASSIGN_FALSE, \ &&OPCODE_ASSIGN_TYPED_BUILTIN, \ + &&OPCODE_ASSIGN_TYPED_ARRAY, \ &&OPCODE_ASSIGN_TYPED_NATIVE, \ &&OPCODE_ASSIGN_TYPED_SCRIPT, \ &&OPCODE_CAST_TO_BUILTIN, \ @@ -215,6 +239,7 @@ String GDScriptFunction::_get_call_error(const Callable::CallError &p_err, const &&OPCODE_CONSTRUCT, \ &&OPCODE_CONSTRUCT_VALIDATED, \ &&OPCODE_CONSTRUCT_ARRAY, \ + &&OPCODE_CONSTRUCT_TYPED_ARRAY, \ &&OPCODE_CONSTRUCT_DICTIONARY, \ &&OPCODE_CALL, \ &&OPCODE_CALL_RETURN, \ @@ -1077,6 +1102,31 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } DISPATCH_OPCODE; + OPCODE(OPCODE_ASSIGN_TYPED_ARRAY) { + CHECK_SPACE(3); + GET_INSTRUCTION_ARG(dst, 0); + GET_INSTRUCTION_ARG(src, 1); + + Array *dst_arr = VariantInternal::get_array(dst); + + 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 '" + +"'."; + OPCODE_BREAK; +#endif + } + if (!dst_arr->typed_assign(*src)) { +#ifdef DEBUG_ENABLED + err_text = "Trying to assign a typed array with an array of different type.'"; + OPCODE_BREAK; +#endif + } + + ip += 3; + } + DISPATCH_OPCODE; + OPCODE(OPCODE_ASSIGN_TYPED_NATIVE) { CHECK_SPACE(4); GET_INSTRUCTION_ARG(dst, 0); @@ -1308,6 +1358,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; @@ -1315,6 +1366,35 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } DISPATCH_OPCODE; + OPCODE(OPCODE_CONSTRUCT_TYPED_ARRAY) { + CHECK_SPACE(3 + instr_arg_count); + ip += instr_arg_count; + + int argc = _code_ptr[ip + 1]; + + GET_INSTRUCTION_ARG(script_type, argc + 1); + Variant::Type builtin_type = (Variant::Type)_code_ptr[ip + 2]; + int native_type_idx = _code_ptr[ip + 3]; + GD_ERR_BREAK(native_type_idx < 0 || native_type_idx >= _global_names_count); + 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]); + } + + GET_INSTRUCTION_ARG(dst, argc); + *dst = Variant(); // Clear potential previous typed array. + + *dst = array; + + ip += 4; + } + DISPATCH_OPCODE; + OPCODE(OPCODE_CONSTRUCT_DICTIONARY) { CHECK_SPACE(2 + instr_arg_count); diff --git a/modules/gdscript/register_types.cpp b/modules/gdscript/register_types.cpp index 93b709a613..19fd3daf20 100644 --- a/modules/gdscript/register_types.cpp +++ b/modules/gdscript/register_types.cpp @@ -158,7 +158,6 @@ void unregister_gdscript_types() { #endif // TOOLS_ENABLED GDScriptParser::cleanup(); - GDScriptAnalyzer::cleanup(); GDScriptUtilityFunctions::unregister_functions(); } diff --git a/modules/gltf/doc_classes/GLTFTexture.xml b/modules/gltf/doc_classes/GLTFTexture.xml index ece5cf3fe3..33bd8fddeb 100644 --- a/modules/gltf/doc_classes/GLTFTexture.xml +++ b/modules/gltf/doc_classes/GLTFTexture.xml @@ -9,7 +9,7 @@ <methods> </methods> <members> - <member name="src_image" type="int" setter="set_src_image" getter="get_src_image" default="212600976"> + <member name="src_image" type="int" setter="set_src_image" getter="get_src_image" default="0"> </member> </members> <constants> diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp index d6e67a78d7..8fe83436e0 100644 --- a/modules/gltf/gltf_document.cpp +++ b/modules/gltf/gltf_document.cpp @@ -2821,7 +2821,7 @@ Error GLTFDocument::_serialize_images(Ref<GLTFState> state, const String &p_path ERR_CONTINUE(state->images[i].is_null()); - Ref<Image> image = state->images[i]->get_data(); + Ref<Image> image = state->images[i]->get_image(); ERR_CONTINUE(image.is_null()); if (p_path.to_lower().ends_with("glb")) { @@ -2838,7 +2838,7 @@ Error GLTFDocument::_serialize_images(Ref<GLTFState> state, const String &p_path Vector<uint8_t> buffer; Ref<ImageTexture> img_tex = image; if (img_tex.is_valid()) { - image = img_tex->get_data(); + image = img_tex->get_image(); } Error err = PNGDriverCommon::image_to_png(image, buffer); ERR_FAIL_COND_V_MSG(err, err, "Can't convert image to PNG."); @@ -3068,7 +3068,7 @@ GLTFTextureIndex GLTFDocument::_set_texture(Ref<GLTFState> state, Ref<Texture2D> ERR_FAIL_COND_V(p_texture.is_null(), -1); Ref<GLTFTexture> gltf_texture; gltf_texture.instance(); - ERR_FAIL_COND_V(p_texture->get_data().is_null(), -1); + ERR_FAIL_COND_V(p_texture->get_image().is_null(), -1); GLTFImageIndex gltf_src_image_i = state->images.size(); state->images.push_back(p_texture); gltf_texture->set_src_image(gltf_src_image_i); @@ -3115,7 +3115,7 @@ Error GLTFDocument::_serialize_materials(Ref<GLTFState> state) { Ref<Texture2D> albedo_texture = material->get_texture(BaseMaterial3D::TEXTURE_ALBEDO); GLTFTextureIndex gltf_texture_index = -1; - if (albedo_texture.is_valid() && albedo_texture->get_data().is_valid()) { + if (albedo_texture.is_valid() && albedo_texture->get_image().is_valid()) { albedo_texture->set_name(material->get_name() + "_albedo"); gltf_texture_index = _set_texture(state, albedo_texture); } @@ -3128,9 +3128,9 @@ Error GLTFDocument::_serialize_materials(Ref<GLTFState> state) { mr["metallicFactor"] = material->get_metallic(); mr["roughnessFactor"] = material->get_roughness(); - bool has_roughness = material->get_texture(BaseMaterial3D::TEXTURE_ROUGHNESS).is_valid() && material->get_texture(BaseMaterial3D::TEXTURE_ROUGHNESS)->get_data().is_valid(); + bool has_roughness = material->get_texture(BaseMaterial3D::TEXTURE_ROUGHNESS).is_valid() && material->get_texture(BaseMaterial3D::TEXTURE_ROUGHNESS)->get_image().is_valid(); bool has_ao = material->get_feature(BaseMaterial3D::FEATURE_AMBIENT_OCCLUSION) && material->get_texture(BaseMaterial3D::TEXTURE_AMBIENT_OCCLUSION).is_valid(); - bool has_metalness = material->get_texture(BaseMaterial3D::TEXTURE_METALLIC).is_valid() && material->get_texture(BaseMaterial3D::TEXTURE_METALLIC)->get_data().is_valid(); + bool has_metalness = material->get_texture(BaseMaterial3D::TEXTURE_METALLIC).is_valid() && material->get_texture(BaseMaterial3D::TEXTURE_METALLIC)->get_image().is_valid(); if (has_ao || has_roughness || has_metalness) { Dictionary mrt; Ref<Texture2D> roughness_texture = material->get_texture(BaseMaterial3D::TEXTURE_ROUGHNESS); @@ -3149,10 +3149,10 @@ Error GLTFDocument::_serialize_materials(Ref<GLTFState> state) { if (has_ao) { height = ao_texture->get_height(); width = ao_texture->get_width(); - ao_image = ao_texture->get_data(); + ao_image = ao_texture->get_image(); Ref<ImageTexture> img_tex = ao_image; if (img_tex.is_valid()) { - ao_image = img_tex->get_data(); + ao_image = img_tex->get_image(); } if (ao_image->is_compressed()) { ao_image->decompress(); @@ -3162,10 +3162,10 @@ Error GLTFDocument::_serialize_materials(Ref<GLTFState> state) { if (has_roughness) { height = roughness_texture->get_height(); width = roughness_texture->get_width(); - roughness_image = roughness_texture->get_data(); + roughness_image = roughness_texture->get_image(); Ref<ImageTexture> img_tex = roughness_image; if (img_tex.is_valid()) { - roughness_image = img_tex->get_data(); + roughness_image = img_tex->get_image(); } if (roughness_image->is_compressed()) { roughness_image->decompress(); @@ -3175,17 +3175,17 @@ Error GLTFDocument::_serialize_materials(Ref<GLTFState> state) { if (has_metalness) { height = metallic_texture->get_height(); width = metallic_texture->get_width(); - metallness_image = metallic_texture->get_data(); + metallness_image = metallic_texture->get_image(); Ref<ImageTexture> img_tex = metallness_image; if (img_tex.is_valid()) { - metallness_image = img_tex->get_data(); + metallness_image = img_tex->get_image(); } if (metallness_image->is_compressed()) { metallness_image->decompress(); } } Ref<Texture2D> albedo_texture = material->get_texture(BaseMaterial3D::TEXTURE_ALBEDO); - if (albedo_texture.is_valid() && albedo_texture->get_data().is_valid()) { + if (albedo_texture.is_valid() && albedo_texture->get_image().is_valid()) { height = albedo_texture->get_height(); width = albedo_texture->get_width(); } @@ -3266,10 +3266,10 @@ Error GLTFDocument::_serialize_materials(Ref<GLTFState> state) { { Ref<Texture2D> normal_texture = material->get_texture(BaseMaterial3D::TEXTURE_NORMAL); // Code for uncompressing RG normal maps - Ref<Image> img = normal_texture->get_data(); + Ref<Image> img = normal_texture->get_image(); Ref<ImageTexture> img_tex = img; if (img_tex.is_valid()) { - img = img_tex->get_data(); + img = img_tex->get_image(); } img->decompress(); img->convert(Image::FORMAT_RGBA8); @@ -3288,7 +3288,7 @@ Error GLTFDocument::_serialize_materials(Ref<GLTFState> state) { } Ref<Texture2D> normal_texture = material->get_texture(BaseMaterial3D::TEXTURE_NORMAL); GLTFTextureIndex gltf_texture_index = -1; - if (tex.is_valid() && tex->get_data().is_valid()) { + if (tex.is_valid() && tex->get_image().is_valid()) { tex->set_name(material->get_name() + "_normal"); gltf_texture_index = _set_texture(state, tex); } @@ -3311,7 +3311,7 @@ Error GLTFDocument::_serialize_materials(Ref<GLTFState> state) { Dictionary et; Ref<Texture2D> emission_texture = material->get_texture(BaseMaterial3D::TEXTURE_EMISSION); GLTFTextureIndex gltf_texture_index = -1; - if (emission_texture.is_valid() && emission_texture->get_data().is_valid()) { + if (emission_texture.is_valid() && emission_texture->get_image().is_valid()) { emission_texture->set_name(material->get_name() + "_emission"); gltf_texture_index = _set_texture(state, emission_texture); } @@ -3370,7 +3370,7 @@ Error GLTFDocument::_parse_materials(Ref<GLTFState> state) { if (diffuse_texture_dict.has("index")) { Ref<Texture2D> diffuse_texture = _get_texture(state, diffuse_texture_dict["index"]); if (diffuse_texture.is_valid()) { - spec_gloss->diffuse_img = diffuse_texture->get_data(); + spec_gloss->diffuse_img = diffuse_texture->get_image(); material->set_texture(BaseMaterial3D::TEXTURE_ALBEDO, diffuse_texture); } } @@ -3398,7 +3398,7 @@ Error GLTFDocument::_parse_materials(Ref<GLTFState> state) { if (spec_gloss_texture.has("index")) { const Ref<Texture2D> orig_texture = _get_texture(state, spec_gloss_texture["index"]); if (orig_texture.is_valid()) { - spec_gloss->spec_gloss_img = orig_texture->get_data(); + spec_gloss->spec_gloss_img = orig_texture->get_image(); } } } diff --git a/modules/opensimplex/doc_classes/NoiseTexture.xml b/modules/opensimplex/doc_classes/NoiseTexture.xml index 86e7f9cc08..38c5138482 100644 --- a/modules/opensimplex/doc_classes/NoiseTexture.xml +++ b/modules/opensimplex/doc_classes/NoiseTexture.xml @@ -6,11 +6,12 @@ <description> Uses an [OpenSimplexNoise] to fill the texture data. You can specify the texture size but keep in mind that larger textures will take longer to generate and seamless noise only works with square sized textures. NoiseTexture can also generate normal map textures. - The class uses [Thread]s to generate the texture data internally, so [method Texture2D.get_data] may return [code]null[/code] if the generation process has not completed yet. In that case, you need to wait for the texture to be generated before accessing the data: + The class uses [Thread]s to generate the texture data internally, so [method Texture2D.get_image] may return [code]null[/code] if the generation process has not completed yet. In that case, you need to wait for the texture to be generated before accessing the image and the generated byte data: [codeblock] var texture = preload("res://noise.tres") yield(texture, "changed") - var image = texture.get_data() + var image = texture.get_image() + var data = image.get_data() [/codeblock] </description> <tutorials> diff --git a/modules/opensimplex/noise_texture.cpp b/modules/opensimplex/noise_texture.cpp index f5d401b058..7272d32fac 100644 --- a/modules/opensimplex/noise_texture.cpp +++ b/modules/opensimplex/noise_texture.cpp @@ -81,9 +81,9 @@ void NoiseTexture::_validate_property(PropertyInfo &property) const { } } -void NoiseTexture::_set_texture_data(const Ref<Image> &p_image) { - data = p_image; - if (data.is_valid()) { +void NoiseTexture::_set_texture_image(const Ref<Image> &p_image) { + image = p_image; + if (image.is_valid()) { if (texture.is_valid()) { RID new_texture = RS::get_singleton()->texture_2d_create(p_image); RS::get_singleton()->texture_replace(texture, new_texture); @@ -95,7 +95,7 @@ void NoiseTexture::_set_texture_data(const Ref<Image> &p_image) { } void NoiseTexture::_thread_done(const Ref<Image> &p_image) { - _set_texture_data(p_image); + _set_texture_image(p_image); noise_thread.wait_to_finish(); if (regen_queued) { noise_thread.start(_thread_function, this); @@ -159,7 +159,7 @@ void NoiseTexture::_update_texture() { } else { Ref<Image> image = _generate_texture(); - _set_texture_data(image); + _set_texture_image(image); } update_queued = false; } @@ -253,6 +253,6 @@ RID NoiseTexture::get_rid() const { return texture; } -Ref<Image> NoiseTexture::get_data() const { - return data; +Ref<Image> NoiseTexture::get_image() const { + return image; } diff --git a/modules/opensimplex/noise_texture.h b/modules/opensimplex/noise_texture.h index e89479d962..6983ae18fe 100644 --- a/modules/opensimplex/noise_texture.h +++ b/modules/opensimplex/noise_texture.h @@ -43,7 +43,7 @@ class NoiseTexture : public Texture2D { GDCLASS(NoiseTexture, Texture2D); private: - Ref<Image> data; + Ref<Image> image; Thread noise_thread; @@ -66,7 +66,7 @@ private: void _queue_update(); Ref<Image> _generate_texture(); void _update_texture(); - void _set_texture_data(const Ref<Image> &p_image); + void _set_texture_image(const Ref<Image> &p_image); protected: static void _bind_methods(); @@ -94,7 +94,7 @@ public: virtual RID get_rid() const override; virtual bool has_alpha() const override { return false; } - virtual Ref<Image> get_data() const override; + virtual Ref<Image> get_image() const override; NoiseTexture(); virtual ~NoiseTexture(); diff --git a/modules/text_server_adv/dynamic_font_adv.cpp b/modules/text_server_adv/dynamic_font_adv.cpp index 2521e68dda..19d04bdb02 100644 --- a/modules/text_server_adv/dynamic_font_adv.cpp +++ b/modules/text_server_adv/dynamic_font_adv.cpp @@ -997,6 +997,29 @@ Vector2 DynamicFontDataAdvanced::draw_glyph_outline(RID p_canvas, int p_size, in return advance; } +bool DynamicFontDataAdvanced::get_glyph_contours(int p_size, uint32_t p_index, Vector<Vector3> &r_points, Vector<int32_t> &r_contours, bool &r_orientation) const { + _THREAD_SAFE_METHOD_ + DataAtSize *fds = const_cast<DynamicFontDataAdvanced *>(this)->get_data_for_size(p_size); + ERR_FAIL_COND_V(fds == nullptr, false); + + int error = FT_Load_Glyph(fds->face, p_index, FT_LOAD_NO_BITMAP | (force_autohinter ? FT_LOAD_FORCE_AUTOHINT : 0)); + ERR_FAIL_COND_V(error, false); + + r_points.clear(); + r_contours.clear(); + + float h = fds->ascent; + float scale = (1.0 / 64.0) / oversampling * fds->scale_color_font; + for (short i = 0; i < fds->face->glyph->outline.n_points; i++) { + r_points.push_back(Vector3(fds->face->glyph->outline.points[i].x * scale, h - fds->face->glyph->outline.points[i].y * scale, FT_CURVE_TAG(fds->face->glyph->outline.tags[i]))); + } + for (short i = 0; i < fds->face->glyph->outline.n_contours; i++) { + r_contours.push_back(fds->face->glyph->outline.contours[i]); + } + r_orientation = (FT_Outline_Get_Orientation(&fds->face->glyph->outline) == FT_ORIENTATION_FILL_RIGHT); + return true; +} + DynamicFontDataAdvanced::~DynamicFontDataAdvanced() { clear_cache(); if (library != nullptr) { diff --git a/modules/text_server_adv/dynamic_font_adv.h b/modules/text_server_adv/dynamic_font_adv.h index d69a30b321..1292966f0c 100644 --- a/modules/text_server_adv/dynamic_font_adv.h +++ b/modules/text_server_adv/dynamic_font_adv.h @@ -186,6 +186,8 @@ public: virtual Vector2 draw_glyph(RID p_canvas, int p_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color) const override; virtual Vector2 draw_glyph_outline(RID p_canvas, int p_size, int p_outline_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color) const override; + virtual bool get_glyph_contours(int p_size, uint32_t p_index, Vector<Vector3> &r_points, Vector<int32_t> &r_contours, bool &r_orientation) const override; + virtual ~DynamicFontDataAdvanced() override; }; diff --git a/modules/text_server_adv/font_adv.h b/modules/text_server_adv/font_adv.h index 2b6d977451..4fadefc569 100644 --- a/modules/text_server_adv/font_adv.h +++ b/modules/text_server_adv/font_adv.h @@ -92,8 +92,8 @@ struct FontDataAdvanced { virtual bool has_outline() const = 0; virtual float get_base_size() const = 0; - virtual bool is_lang_supported(const String &p_lang) const { return false; }; - virtual bool is_script_supported(uint32_t p_script) const { return false; }; + virtual bool is_lang_supported(const String &p_lang) const { return true; }; + virtual bool is_script_supported(uint32_t p_script) const { return true; }; virtual bool has_char(char32_t p_char) const = 0; virtual String get_supported_chars() const = 0; @@ -107,6 +107,8 @@ struct FontDataAdvanced { virtual Vector2 draw_glyph(RID p_canvas, int p_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color) const = 0; virtual Vector2 draw_glyph_outline(RID p_canvas, int p_size, int p_outline_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color) const = 0; + virtual bool get_glyph_contours(int p_size, uint32_t p_index, Vector<Vector3> &r_points, Vector<int32_t> &r_contours, bool &r_orientation) const { return false; }; + virtual ~FontDataAdvanced(){}; }; diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp index 43b8f18101..9007c696eb 100644 --- a/modules/text_server_adv/text_server_adv.cpp +++ b/modules/text_server_adv/text_server_adv.cpp @@ -906,6 +906,13 @@ Vector2 TextServerAdvanced::font_draw_glyph_outline(RID p_font, RID p_canvas, in return fd->draw_glyph_outline(p_canvas, p_size, p_outline_size, p_pos, p_index, p_color); } +bool TextServerAdvanced::font_get_glyph_contours(RID p_font, int p_size, uint32_t p_index, Vector<Vector3> &r_points, Vector<int32_t> &r_contours, bool &r_orientation) const { + _THREAD_SAFE_METHOD_ + const FontDataAdvanced *fd = font_owner.getornull(p_font); + ERR_FAIL_COND_V(!fd, false); + return fd->get_glyph_contours(p_size, p_index, r_points, r_contours, r_orientation); +} + float TextServerAdvanced::font_get_oversampling() const { return oversampling; } diff --git a/modules/text_server_adv/text_server_adv.h b/modules/text_server_adv/text_server_adv.h index b53b5716e5..4ad23ca059 100644 --- a/modules/text_server_adv/text_server_adv.h +++ b/modules/text_server_adv/text_server_adv.h @@ -188,6 +188,8 @@ public: virtual Vector2 font_draw_glyph(RID p_font, RID p_canvas, int p_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color = Color(1, 1, 1)) const override; virtual Vector2 font_draw_glyph_outline(RID p_font, RID p_canvas, int p_size, int p_outline_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color = Color(1, 1, 1)) const override; + virtual bool font_get_glyph_contours(RID p_font, int p_size, uint32_t p_index, Vector<Vector3> &r_points, Vector<int32_t> &r_contours, bool &r_orientation) const override; + virtual float font_get_oversampling() const override; virtual void font_set_oversampling(float p_oversampling) override; diff --git a/modules/text_server_fb/dynamic_font_fb.cpp b/modules/text_server_fb/dynamic_font_fb.cpp index 66d36bc885..dec1d6f83f 100644 --- a/modules/text_server_fb/dynamic_font_fb.cpp +++ b/modules/text_server_fb/dynamic_font_fb.cpp @@ -680,6 +680,29 @@ Vector2 DynamicFontDataFallback::draw_glyph_outline(RID p_canvas, int p_size, in return advance; } +bool DynamicFontDataFallback::get_glyph_contours(int p_size, uint32_t p_index, Vector<Vector3> &r_points, Vector<int32_t> &r_contours, bool &r_orientation) const { + _THREAD_SAFE_METHOD_ + DataAtSize *fds = const_cast<DynamicFontDataFallback *>(this)->get_data_for_size(p_size); + ERR_FAIL_COND_V(fds == nullptr, false); + + int error = FT_Load_Glyph(fds->face, p_index, FT_LOAD_NO_BITMAP | (force_autohinter ? FT_LOAD_FORCE_AUTOHINT : 0)); + ERR_FAIL_COND_V(error, false); + + r_points.clear(); + r_contours.clear(); + + float h = fds->ascent; + float scale = (1.0 / 64.0) / oversampling * fds->scale_color_font; + for (short i = 0; i < fds->face->glyph->outline.n_points; i++) { + r_points.push_back(Vector3(fds->face->glyph->outline.points[i].x * scale, h - fds->face->glyph->outline.points[i].y * scale, FT_CURVE_TAG(fds->face->glyph->outline.tags[i]))); + } + for (short i = 0; i < fds->face->glyph->outline.n_contours; i++) { + r_contours.push_back(fds->face->glyph->outline.contours[i]); + } + r_orientation = (FT_Outline_Get_Orientation(&fds->face->glyph->outline) == FT_ORIENTATION_FILL_RIGHT); + return true; +} + DynamicFontDataFallback::~DynamicFontDataFallback() { clear_cache(); if (library != nullptr) { diff --git a/modules/text_server_fb/dynamic_font_fb.h b/modules/text_server_fb/dynamic_font_fb.h index eb70f46666..b34c8cbed5 100644 --- a/modules/text_server_fb/dynamic_font_fb.h +++ b/modules/text_server_fb/dynamic_font_fb.h @@ -164,6 +164,8 @@ public: virtual Vector2 draw_glyph(RID p_canvas, int p_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color) const override; virtual Vector2 draw_glyph_outline(RID p_canvas, int p_size, int p_outline_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color) const override; + virtual bool get_glyph_contours(int p_size, uint32_t p_index, Vector<Vector3> &r_points, Vector<int32_t> &r_contours, bool &r_orientation) const override; + virtual ~DynamicFontDataFallback() override; }; diff --git a/modules/text_server_fb/font_fb.h b/modules/text_server_fb/font_fb.h index 218f3df03a..fe9888b7f4 100644 --- a/modules/text_server_fb/font_fb.h +++ b/modules/text_server_fb/font_fb.h @@ -93,6 +93,8 @@ struct FontDataFallback { virtual Vector2 draw_glyph(RID p_canvas, int p_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color) const = 0; virtual Vector2 draw_glyph_outline(RID p_canvas, int p_size, int p_outline_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color) const = 0; + virtual bool get_glyph_contours(int p_size, uint32_t p_index, Vector<Vector3> &r_points, Vector<int32_t> &r_contours, bool &r_orientation) const { return false; }; + virtual ~FontDataFallback(){}; }; diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp index f46f96d30d..98a67ef309 100644 --- a/modules/text_server_fb/text_server_fb.cpp +++ b/modules/text_server_fb/text_server_fb.cpp @@ -452,6 +452,13 @@ Vector2 TextServerFallback::font_draw_glyph_outline(RID p_font, RID p_canvas, in return fd->draw_glyph_outline(p_canvas, p_size, p_outline_size, p_pos, p_index, p_color); } +bool TextServerFallback::font_get_glyph_contours(RID p_font, int p_size, uint32_t p_index, Vector<Vector3> &r_points, Vector<int32_t> &r_contours, bool &r_orientation) const { + _THREAD_SAFE_METHOD_ + const FontDataFallback *fd = font_owner.getornull(p_font); + ERR_FAIL_COND_V(!fd, false); + return fd->get_glyph_contours(p_size, p_index, r_points, r_contours, r_orientation); +} + float TextServerFallback::font_get_oversampling() const { return oversampling; } diff --git a/modules/text_server_fb/text_server_fb.h b/modules/text_server_fb/text_server_fb.h index b10369d172..8f5eb1d315 100644 --- a/modules/text_server_fb/text_server_fb.h +++ b/modules/text_server_fb/text_server_fb.h @@ -137,6 +137,8 @@ public: virtual Vector2 font_draw_glyph(RID p_font, RID p_canvas, int p_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color = Color(1, 1, 1)) const override; virtual Vector2 font_draw_glyph_outline(RID p_font, RID p_canvas, int p_size, int p_outline_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color = Color(1, 1, 1)) const override; + virtual bool font_get_glyph_contours(RID p_font, int p_size, uint32_t p_index, Vector<Vector3> &r_points, Vector<int32_t> &r_contours, bool &r_orientation) const override; + virtual float font_get_oversampling() const override; virtual void font_set_oversampling(float p_oversampling) override; |