diff options
77 files changed, 1265 insertions, 279 deletions
diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp index 352486bd09..75eea2ef50 100644 --- a/core/config/project_settings.cpp +++ b/core/config/project_settings.cpp @@ -1330,8 +1330,8 @@ void ProjectSettings::load_scene_groups_cache() { for (const String &E : scene_paths) { Array scene_groups = cf->get_value(E, "groups", Array()); HashSet<StringName> cache; - for (int i = 0; i < scene_groups.size(); ++i) { - cache.insert(scene_groups[i]); + for (const Variant &scene_group : scene_groups) { + cache.insert(scene_group); } add_scene_groups_cache(E, cache); } diff --git a/core/extension/extension_api_dump.cpp b/core/extension/extension_api_dump.cpp index 543dabfb16..69be2d2a8f 100644 --- a/core/extension/extension_api_dump.cpp +++ b/core/extension/extension_api_dump.cpp @@ -1313,9 +1313,7 @@ static bool compare_value(const String &p_path, const String &p_field, const Var } else if (p_old_value.get_type() == Variant::DICTIONARY && p_new_value.get_type() == Variant::DICTIONARY) { Dictionary old_dict = p_old_value; Dictionary new_dict = p_new_value; - Array old_keys = old_dict.keys(); - for (int i = 0; i < old_keys.size(); i++) { - Variant key = old_keys[i]; + for (const Variant &key : old_dict.keys()) { if (!new_dict.has(key)) { failed = true; print_error(vformat("Validate extension JSON: Error: Field '%s': %s was removed.", p_path, key)); @@ -1328,9 +1326,7 @@ static bool compare_value(const String &p_path, const String &p_field, const Var failed = true; } } - Array new_keys = old_dict.keys(); - for (int i = 0; i < new_keys.size(); i++) { - Variant key = new_keys[i]; + for (const Variant &key : old_dict.keys()) { if (!old_dict.has(key)) { failed = true; print_error(vformat("Validate extension JSON: Error: Field '%s': %s was added with value %s.", p_path, key, new_dict[key])); @@ -1356,8 +1352,8 @@ static bool compare_dict_array(const Dictionary &p_old_api, const Dictionary &p_ Array new_api = p_new_api[p_base_array]; HashMap<String, Dictionary> new_api_assoc; - for (int i = 0; i < new_api.size(); i++) { - Dictionary elem = new_api[i]; + for (const Variant &var : new_api) { + Dictionary elem = var; ERR_FAIL_COND_V_MSG(!elem.has(p_name_field), false, vformat("Validate extension JSON: Element of base_array '%s' is missing field '%s'. This is a bug.", base_array, p_name_field)); String name = elem[p_name_field]; if (p_compare_operators && elem.has("right_type")) { @@ -1367,8 +1363,8 @@ static bool compare_dict_array(const Dictionary &p_old_api, const Dictionary &p_ } Array old_api = p_old_api[p_base_array]; - for (int i = 0; i < old_api.size(); i++) { - Dictionary old_elem = old_api[i]; + for (const Variant &var : old_api) { + Dictionary old_elem = var; if (!old_elem.has(p_name_field)) { failed = true; print_error(vformat("Validate extension JSON: JSON file: element of base array '%s' is missing the field: '%s'.", base_array, p_name_field)); @@ -1508,16 +1504,16 @@ static bool compare_sub_dict_array(HashSet<String> &r_removed_classes_registered Array new_api = p_new_api[p_outer]; HashMap<String, Dictionary> new_api_assoc; - for (int i = 0; i < new_api.size(); i++) { - Dictionary elem = new_api[i]; + for (const Variant &var : new_api) { + Dictionary elem = var; ERR_FAIL_COND_V_MSG(!elem.has(p_outer_name), false, vformat("Validate extension JSON: Element of base_array '%s' is missing field '%s'. This is a bug.", p_outer, p_outer_name)); new_api_assoc.insert(elem[p_outer_name], elem); } Array old_api = p_old_api[p_outer]; - for (int i = 0; i < old_api.size(); i++) { - Dictionary old_elem = old_api[i]; + for (const Variant &var : old_api) { + Dictionary old_elem = var; if (!old_elem.has(p_outer_name)) { failed = true; print_error(vformat("Validate extension JSON: JSON file: element of base array '%s' is missing the field: '%s'.", p_outer, p_outer_name)); diff --git a/core/io/json.cpp b/core/io/json.cpp index 496400a5ea..5a1fe45f70 100644 --- a/core/io/json.cpp +++ b/core/io/json.cpp @@ -86,7 +86,7 @@ String JSON::_stringify(const Variant &p_var, const String &p_indent, int p_cur_ case Variant::PACKED_STRING_ARRAY: case Variant::ARRAY: { Array a = p_var; - if (a.size() == 0) { + if (a.is_empty()) { return "[]"; } String s = "["; @@ -95,12 +95,15 @@ String JSON::_stringify(const Variant &p_var, const String &p_indent, int p_cur_ ERR_FAIL_COND_V_MSG(p_markers.has(a.id()), "\"[...]\"", "Converting circular structure to JSON."); p_markers.insert(a.id()); - for (int i = 0; i < a.size(); i++) { - if (i > 0) { + bool first = true; + for (const Variant &var : a) { + if (first) { + first = false; + } else { s += ","; s += end_statement; } - s += _make_indent(p_indent, p_cur_indent + 1) + _stringify(a[i], p_indent, p_cur_indent + 1, p_sort_keys, p_markers); + s += _make_indent(p_indent, p_cur_indent + 1) + _stringify(var, p_indent, p_cur_indent + 1, p_sort_keys, p_markers); } s += end_statement + _make_indent(p_indent, p_cur_indent) + "]"; p_markers.erase(a.id()); diff --git a/core/io/marshalls.cpp b/core/io/marshalls.cpp index f0bbf00a1d..4487b8e472 100644 --- a/core/io/marshalls.cpp +++ b/core/io/marshalls.cpp @@ -1729,9 +1729,9 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo } r_len += 4; - for (int i = 0; i < array.size(); i++) { + for (const Variant &var : array) { int len; - Error err = encode_variant(array.get(i), buf, len, p_full_objects, p_depth + 1); + Error err = encode_variant(var, buf, len, p_full_objects, p_depth + 1); ERR_FAIL_COND_V(err, err); ERR_FAIL_COND_V(len % 4, ERR_BUG); if (buf) { diff --git a/core/io/resource_format_binary.cpp b/core/io/resource_format_binary.cpp index 5c8f4746b3..d0a8200546 100644 --- a/core/io/resource_format_binary.cpp +++ b/core/io/resource_format_binary.cpp @@ -1844,8 +1844,8 @@ void ResourceFormatSaverBinaryInstance::write_variant(Ref<FileAccess> f, const V f->store_32(VARIANT_ARRAY); Array a = p_property; f->store_32(uint32_t(a.size())); - for (int i = 0; i < a.size(); i++) { - write_variant(f, a[i], resource_map, external_resources, string_map); + for (const Variant &var : a) { + write_variant(f, var, resource_map, external_resources, string_map); } } break; @@ -2017,9 +2017,7 @@ void ResourceFormatSaverBinaryInstance::_find_resources(const Variant &p_variant case Variant::ARRAY: { Array varray = p_variant; _find_resources(varray.get_typed_script()); - int len = varray.size(); - for (int i = 0; i < len; i++) { - const Variant &v = varray.get(i); + for (const Variant &v : varray) { _find_resources(v); } diff --git a/core/io/resource_loader.cpp b/core/io/resource_loader.cpp index 191abee315..eea6357084 100644 --- a/core/io/resource_loader.cpp +++ b/core/io/resource_loader.cpp @@ -1035,8 +1035,9 @@ void ResourceLoader::load_translation_remaps() { Array langs = remaps[E]; Vector<String> lang_remaps; lang_remaps.resize(langs.size()); - for (int i = 0; i < langs.size(); i++) { - lang_remaps.write[i] = langs[i]; + String *lang_remaps_ptrw = lang_remaps.ptrw(); + for (const Variant &lang : langs) { + *lang_remaps_ptrw++ = lang; } translation_remaps[String(E)] = lang_remaps; diff --git a/core/object/object.cpp b/core/object/object.cpp index 8b6fd587e0..06f6e8e9e6 100644 --- a/core/object/object.cpp +++ b/core/object/object.cpp @@ -142,16 +142,16 @@ MethodInfo MethodInfo::from_dict(const Dictionary &p_dict) { args = p_dict["args"]; } - for (int i = 0; i < args.size(); i++) { - Dictionary d = args[i]; + for (const Variant &arg : args) { + Dictionary d = arg; mi.arguments.push_back(PropertyInfo::from_dict(d)); } Array defargs; if (p_dict.has("default_args")) { defargs = p_dict["default_args"]; } - for (int i = 0; i < defargs.size(); i++) { - mi.default_arguments.push_back(defargs[i]); + for (const Variant &defarg : defargs) { + mi.default_arguments.push_back(defarg); } if (p_dict.has("return")) { @@ -1233,8 +1233,8 @@ void Object::_add_user_signal(const String &p_name, const Array &p_args) { MethodInfo mi; mi.name = p_name; - for (int i = 0; i < p_args.size(); i++) { - Dictionary d = p_args[i]; + for (const Variant &arg : p_args) { + Dictionary d = arg; PropertyInfo param; if (d.has("name")) { @@ -1585,8 +1585,8 @@ void Object::_clear_internal_resource_paths(const Variant &p_var) { } break; case Variant::ARRAY: { Array a = p_var; - for (int i = 0; i < a.size(); i++) { - _clear_internal_resource_paths(a[i]); + for (const Variant &var : a) { + _clear_internal_resource_paths(var); } } break; diff --git a/core/object/script_language.cpp b/core/object/script_language.cpp index 73da0ba2af..660e13e819 100644 --- a/core/object/script_language.cpp +++ b/core/object/script_language.cpp @@ -251,8 +251,8 @@ void ScriptServer::init_languages() { if (ProjectSettings::get_singleton()->has_setting("_global_script_classes")) { Array script_classes = GLOBAL_GET("_global_script_classes"); - for (int i = 0; i < script_classes.size(); i++) { - Dictionary c = script_classes[i]; + for (const Variant &script_class : script_classes) { + Dictionary c = script_class; if (!c.has("class") || !c.has("language") || !c.has("path") || !c.has("base")) { continue; } @@ -263,8 +263,8 @@ void ScriptServer::init_languages() { #endif Array script_classes = ProjectSettings::get_singleton()->get_global_class_list(); - for (int i = 0; i < script_classes.size(); i++) { - Dictionary c = script_classes[i]; + for (const Variant &script_class : script_classes) { + Dictionary c = script_class; if (!c.has("class") || !c.has("language") || !c.has("path") || !c.has("base")) { continue; } @@ -463,8 +463,8 @@ void ScriptServer::save_global_classes() { Dictionary class_icons; Array script_classes = ProjectSettings::get_singleton()->get_global_class_list(); - for (int i = 0; i < script_classes.size(); i++) { - Dictionary d = script_classes[i]; + for (const Variant &script_class : script_classes) { + Dictionary d = script_class; if (!d.has("name") || !d.has("icon")) { continue; } diff --git a/core/object/script_language_extension.h b/core/object/script_language_extension.h index 1db322526d..cc6b729ae8 100644 --- a/core/object/script_language_extension.h +++ b/core/object/script_language_extension.h @@ -319,8 +319,8 @@ public: } if (r_errors != nullptr && ret.has("errors")) { Array errors = ret["errors"]; - for (int i = 0; i < errors.size(); i++) { - Dictionary err = errors[i]; + for (const Variant &error : errors) { + Dictionary err = error; ERR_CONTINUE(!err.has("line")); ERR_CONTINUE(!err.has("column")); ERR_CONTINUE(!err.has("message")); @@ -339,8 +339,8 @@ public: if (r_warnings != nullptr && ret.has("warnings")) { ERR_FAIL_COND_V(!ret.has("warnings"), false); Array warnings = ret["warnings"]; - for (int i = 0; i < warnings.size(); i++) { - Dictionary warn = warnings[i]; + for (const Variant &warning : warnings) { + Dictionary warn = warning; ERR_CONTINUE(!warn.has("start_line")); ERR_CONTINUE(!warn.has("end_line")); ERR_CONTINUE(!warn.has("leftmost_column")); @@ -402,8 +402,8 @@ public: if (r_options != nullptr && ret.has("options")) { Array options = ret["options"]; - for (int i = 0; i < options.size(); i++) { - Dictionary op = options[i]; + for (const Variant &var : options) { + Dictionary op = var; CodeCompletionOption option; ERR_CONTINUE(!op.has("kind")); option.kind = CodeCompletionKind(int(op["kind"])); @@ -502,8 +502,8 @@ public: } if (p_values != nullptr && ret.has("values")) { Array values = ret["values"]; - for (int i = 0; i < values.size(); i++) { - p_values->push_back(values[i]); + for (const Variant &value : values) { + p_values->push_back(value); } } } @@ -522,8 +522,8 @@ public: } if (p_values != nullptr && ret.has("values")) { Array values = ret["values"]; - for (int i = 0; i < values.size(); i++) { - p_values->push_back(values[i]); + for (const Variant &value : values) { + p_values->push_back(value); } } } @@ -549,8 +549,8 @@ public: } if (p_values != nullptr && ret.has("values")) { Array values = ret["values"]; - for (int i = 0; i < values.size(); i++) { - p_values->push_back(values[i]); + for (const Variant &value : values) { + p_values->push_back(value); } } } @@ -562,9 +562,9 @@ public: TypedArray<Dictionary> ret; GDVIRTUAL_REQUIRED_CALL(_debug_get_current_stack_info, ret); Vector<StackInfo> sret; - for (int i = 0; i < ret.size(); i++) { + for (const Variant &var : ret) { StackInfo si; - Dictionary d = ret[i]; + Dictionary d = var; ERR_CONTINUE(!d.has("file")); ERR_CONTINUE(!d.has("func")); ERR_CONTINUE(!d.has("line")); @@ -595,8 +595,8 @@ public: virtual void get_public_functions(List<MethodInfo> *p_functions) const override { TypedArray<Dictionary> ret; GDVIRTUAL_REQUIRED_CALL(_get_public_functions, ret); - for (int i = 0; i < ret.size(); i++) { - MethodInfo mi = MethodInfo::from_dict(ret[i]); + for (const Variant &var : ret) { + MethodInfo mi = MethodInfo::from_dict(var); p_functions->push_back(mi); } } @@ -615,8 +615,8 @@ public: virtual void get_public_annotations(List<MethodInfo> *p_annotations) const override { TypedArray<Dictionary> ret; GDVIRTUAL_REQUIRED_CALL(_get_public_annotations, ret); - for (int i = 0; i < ret.size(); i++) { - MethodInfo mi = MethodInfo::from_dict(ret[i]); + for (const Variant &var : ret) { + MethodInfo mi = MethodInfo::from_dict(var); p_annotations->push_back(mi); } } diff --git a/core/variant/array.cpp b/core/variant/array.cpp index 5d6fbb8bed..3685515db5 100644 --- a/core/variant/array.cpp +++ b/core/variant/array.cpp @@ -81,6 +81,22 @@ void Array::_unref() const { _p = nullptr; } +Array::Iterator Array::begin() { + return Iterator(_p->array.ptrw(), _p->read_only); +} + +Array::Iterator Array::end() { + return Iterator(_p->array.ptrw() + _p->array.size(), _p->read_only); +} + +Array::ConstIterator Array::begin() const { + return ConstIterator(_p->array.ptr(), _p->read_only); +} + +Array::ConstIterator Array::end() const { + return ConstIterator(_p->array.ptr() + _p->array.size(), _p->read_only); +} + Variant &Array::operator[](int p_idx) { if (unlikely(_p->read_only)) { *_p->read_only = _p->array[p_idx]; diff --git a/core/variant/array.h b/core/variant/array.h index 8b1f8c0678..d45a6887e2 100644 --- a/core/variant/array.h +++ b/core/variant/array.h @@ -46,6 +46,70 @@ class Array { void _unref() const; public: + struct ConstIterator { + _FORCE_INLINE_ const Variant &operator*() const; + _FORCE_INLINE_ const Variant *operator->() const; + + _FORCE_INLINE_ ConstIterator &operator++(); + _FORCE_INLINE_ ConstIterator &operator--(); + + _FORCE_INLINE_ bool operator==(const ConstIterator &p_other) const { return element_ptr == p_other.element_ptr; } + _FORCE_INLINE_ bool operator!=(const ConstIterator &p_other) const { return element_ptr == p_other.element_ptr; } + + _FORCE_INLINE_ ConstIterator(const Variant *p_element_ptr, Variant *p_read_only = nullptr) : + element_ptr(p_element_ptr), read_only(p_read_only) {} + _FORCE_INLINE_ ConstIterator() {} + _FORCE_INLINE_ ConstIterator(const ConstIterator &p_other) : + element_ptr(p_other.element_ptr), read_only(p_other.read_only) {} + + _FORCE_INLINE_ ConstIterator &operator=(const ConstIterator &p_other) { + element_ptr = p_other.element_ptr; + read_only = p_other.read_only; + return *this; + } + + private: + const Variant *element_ptr = nullptr; + Variant *read_only = nullptr; + }; + + struct Iterator { + _FORCE_INLINE_ Variant &operator*() const; + _FORCE_INLINE_ Variant *operator->() const; + + _FORCE_INLINE_ Iterator &operator++(); + _FORCE_INLINE_ Iterator &operator--(); + + _FORCE_INLINE_ bool operator==(const Iterator &p_other) const { return element_ptr == p_other.element_ptr; } + _FORCE_INLINE_ bool operator!=(const Iterator &p_other) const { return element_ptr != p_other.element_ptr; } + + _FORCE_INLINE_ Iterator(Variant *p_element_ptr, Variant *p_read_only = nullptr) : + element_ptr(p_element_ptr), read_only(p_read_only) {} + _FORCE_INLINE_ Iterator() {} + _FORCE_INLINE_ Iterator(const Iterator &p_other) : + element_ptr(p_other.element_ptr), read_only(p_other.read_only) {} + + _FORCE_INLINE_ Iterator &operator=(const Iterator &p_other) { + element_ptr = p_other.element_ptr; + read_only = p_other.read_only; + return *this; + } + + operator ConstIterator() const { + return ConstIterator(element_ptr, read_only); + } + + private: + Variant *element_ptr = nullptr; + Variant *read_only = nullptr; + }; + + Iterator begin(); + Iterator end(); + + ConstIterator begin() const; + ConstIterator end() const; + void _ref(const Array &p_from) const; Variant &operator[](int p_idx); diff --git a/core/variant/variant.h b/core/variant/variant.h index fc4030ac5a..10f8dc3c7f 100644 --- a/core/variant/variant.h +++ b/core/variant/variant.h @@ -865,4 +865,56 @@ Callable Callable::bind(VarArgs... p_args) const { return bindp(sizeof...(p_args) == 0 ? nullptr : (const Variant **)argptrs, sizeof...(p_args)); } +Variant &Array::Iterator::operator*() const { + if (unlikely(read_only)) { + *read_only = *element_ptr; + return *read_only; + } + return *element_ptr; +} + +Variant *Array::Iterator::operator->() const { + if (unlikely(read_only)) { + *read_only = *element_ptr; + return read_only; + } + return element_ptr; +} + +Array::Iterator &Array::Iterator::operator++() { + element_ptr++; + return *this; +} + +Array::Iterator &Array::Iterator::operator--() { + element_ptr--; + return *this; +} + +const Variant &Array::ConstIterator::operator*() const { + if (unlikely(read_only)) { + *read_only = *element_ptr; + return *read_only; + } + return *element_ptr; +} + +const Variant *Array::ConstIterator::operator->() const { + if (unlikely(read_only)) { + *read_only = *element_ptr; + return read_only; + } + return element_ptr; +} + +Array::ConstIterator &Array::ConstIterator::operator++() { + element_ptr++; + return *this; +} + +Array::ConstIterator &Array::ConstIterator::operator--() { + element_ptr--; + return *this; +} + #endif // VARIANT_H diff --git a/core/variant/variant_call.cpp b/core/variant/variant_call.cpp index ba7c44e405..d0d940c47d 100644 --- a/core/variant/variant_call.cpp +++ b/core/variant/variant_call.cpp @@ -1799,7 +1799,7 @@ static void _register_variant_builtin_methods() { bind_method(Vector2, dot, sarray("with"), varray()); bind_method(Vector2, slide, sarray("n"), varray()); bind_method(Vector2, bounce, sarray("n"), varray()); - bind_method(Vector2, reflect, sarray("n"), varray()); + bind_method(Vector2, reflect, sarray("line"), varray()); bind_method(Vector2, cross, sarray("with"), varray()); bind_method(Vector2, abs, sarray(), varray()); bind_method(Vector2, sign, sarray(), varray()); @@ -1896,7 +1896,7 @@ static void _register_variant_builtin_methods() { bind_method(Vector3, project, sarray("b"), varray()); bind_method(Vector3, slide, sarray("n"), varray()); bind_method(Vector3, bounce, sarray("n"), varray()); - bind_method(Vector3, reflect, sarray("n"), varray()); + bind_method(Vector3, reflect, sarray("direction"), varray()); bind_method(Vector3, sign, sarray(), varray()); bind_method(Vector3, octahedron_encode, sarray(), varray()); bind_static_method(Vector3, octahedron_decode, sarray("uv"), varray()); diff --git a/core/variant/variant_parser.cpp b/core/variant/variant_parser.cpp index fa91758fff..dcb94b16b1 100644 --- a/core/variant/variant_parser.cpp +++ b/core/variant/variant_parser.cpp @@ -30,6 +30,7 @@ #include "variant_parser.h" +#include "core/crypto/crypto_core.h" #include "core/input/input_event.h" #include "core/io/resource_loader.h" #include "core/object/script_language.h" @@ -595,6 +596,82 @@ Error VariantParser::_parse_construct(Stream *p_stream, Vector<T> &r_construct, return OK; } +Error VariantParser::_parse_byte_array(Stream *p_stream, Vector<uint8_t> &r_construct, int &line, String &r_err_str) { + Token token; + get_token(p_stream, token, line, r_err_str); + if (token.type != TK_PARENTHESIS_OPEN) { + r_err_str = "Expected '(' in constructor"; + return ERR_PARSE_ERROR; + } + + get_token(p_stream, token, line, r_err_str); + if (token.type == TK_STRING) { + // Base64 encoded array. + String base64_encoded_string = token.value; + int strlen = base64_encoded_string.length(); + CharString cstr = base64_encoded_string.ascii(); + + size_t arr_len = 0; + r_construct.resize(strlen / 4 * 3 + 1); + uint8_t *w = r_construct.ptrw(); + Error err = CryptoCore::b64_decode(&w[0], r_construct.size(), &arr_len, (unsigned char *)cstr.get_data(), strlen); + if (err) { + r_err_str = "Invalid base64-encoded string"; + return ERR_PARSE_ERROR; + } + r_construct.resize(arr_len); + + get_token(p_stream, token, line, r_err_str); + if (token.type != TK_PARENTHESIS_CLOSE) { + r_err_str = "Expected ')' in constructor"; + return ERR_PARSE_ERROR; + } + + } else if (token.type == TK_NUMBER || token.type == TK_IDENTIFIER) { + // Individual elements. + while (true) { + if (token.type != TK_NUMBER) { + bool valid = false; + if (token.type == TK_IDENTIFIER) { + double real = stor_fix(token.value); + if (real != -1) { + token.type = TK_NUMBER; + token.value = real; + valid = true; + } + } + if (!valid) { + r_err_str = "Expected number in constructor"; + return ERR_PARSE_ERROR; + } + } + + r_construct.push_back(token.value); + + get_token(p_stream, token, line, r_err_str); + + if (token.type == TK_COMMA) { + //do none + } else if (token.type == TK_PARENTHESIS_CLOSE) { + break; + } else { + r_err_str = "Expected ',' or ')' in constructor"; + return ERR_PARSE_ERROR; + } + + get_token(p_stream, token, line, r_err_str); + } + } else if (token.type == TK_PARENTHESIS_CLOSE) { + // Empty array. + return OK; + } else { + r_err_str = "Expected base64 string, or list of numbers in constructor"; + return ERR_PARSE_ERROR; + } + + return OK; +} + Error VariantParser::parse_value(Token &token, Variant &value, Stream *p_stream, int &line, String &r_err_str, ResourceParser *p_res_parser) { if (token.type == TK_CURLY_BRACKET_OPEN) { Dictionary d; @@ -1148,7 +1225,7 @@ Error VariantParser::parse_value(Token &token, Variant &value, Stream *p_stream, value = array; } else if (id == "PackedByteArray" || id == "PoolByteArray" || id == "ByteArray") { Vector<uint8_t> args; - Error err = _parse_construct<uint8_t>(p_stream, args, line, r_err_str); + Error err = _parse_byte_array(p_stream, args, line, r_err_str); if (err) { return err; } @@ -2012,12 +2089,14 @@ Error VariantWriter::write(const Variant &p_variant, StoreStringFunc p_store_str p_recursion_count++; p_store_string_func(p_store_string_ud, "["); - int len = array.size(); - for (int i = 0; i < len; i++) { - if (i > 0) { + bool first = true; + for (const Variant &var : array) { + if (first) { + first = false; + } else { p_store_string_func(p_store_string_ud, ", "); } - write(array[i], p_store_string_func, p_store_string_ud, p_encode_res_func, p_encode_res_ud, p_recursion_count); + write(var, p_store_string_func, p_store_string_ud, p_encode_res_func, p_encode_res_ud, p_recursion_count); } p_store_string_func(p_store_string_ud, "]"); @@ -2031,17 +2110,11 @@ Error VariantWriter::write(const Variant &p_variant, StoreStringFunc p_store_str case Variant::PACKED_BYTE_ARRAY: { p_store_string_func(p_store_string_ud, "PackedByteArray("); Vector<uint8_t> data = p_variant; - int len = data.size(); - const uint8_t *ptr = data.ptr(); - - for (int i = 0; i < len; i++) { - if (i > 0) { - p_store_string_func(p_store_string_ud, ", "); - } - - p_store_string_func(p_store_string_ud, itos(ptr[i])); + if (data.size() > 0) { + p_store_string_func(p_store_string_ud, "\""); + p_store_string_func(p_store_string_ud, CryptoCore::b64_encode_str(data.ptr(), data.size())); + p_store_string_func(p_store_string_ud, "\""); } - p_store_string_func(p_store_string_ud, ")"); } break; case Variant::PACKED_INT32_ARRAY: { diff --git a/core/variant/variant_parser.h b/core/variant/variant_parser.h index 18448100e0..2f8974849f 100644 --- a/core/variant/variant_parser.h +++ b/core/variant/variant_parser.h @@ -141,6 +141,7 @@ private: template <typename T> static Error _parse_construct(Stream *p_stream, Vector<T> &r_construct, int &line, String &r_err_str); + static Error _parse_byte_array(Stream *p_stream, Vector<uint8_t> &r_construct, int &line, String &r_err_str); static Error _parse_enginecfg(Stream *p_stream, Vector<String> &strings, int &line, String &r_err_str); static Error _parse_dictionary(Dictionary &object, Stream *p_stream, int &line, String &r_err_str, ResourceParser *p_res_parser = nullptr); static Error _parse_array(Array &array, Stream *p_stream, int &line, String &r_err_str, ResourceParser *p_res_parser = nullptr); diff --git a/doc/classes/Area3D.xml b/doc/classes/Area3D.xml index 4ee16d499d..8eedd3cdf2 100644 --- a/doc/classes/Area3D.xml +++ b/doc/classes/Area3D.xml @@ -124,12 +124,15 @@ </member> <member name="wind_attenuation_factor" type="float" setter="set_wind_attenuation_factor" getter="get_wind_attenuation_factor" default="0.0"> The exponential rate at which wind force decreases with distance from its origin. + [b]Note:[/b] This wind force only applies to [SoftBody3D] nodes. Other physics bodies are currently not affected by wind. </member> <member name="wind_force_magnitude" type="float" setter="set_wind_force_magnitude" getter="get_wind_force_magnitude" default="0.0"> The magnitude of area-specific wind force. + [b]Note:[/b] This wind force only applies to [SoftBody3D] nodes. Other physics bodies are currently not affected by wind. </member> <member name="wind_source_path" type="NodePath" setter="set_wind_source_path" getter="get_wind_source_path" default="NodePath("")"> The [Node3D] which is used to specify the direction and origin of an area-specific wind force. The direction is opposite to the z-axis of the [Node3D]'s local transform, and its origin is the origin of the [Node3D]'s local transform. + [b]Note:[/b] This wind force only applies to [SoftBody3D] nodes. Other physics bodies are currently not affected by wind. </member> </members> <signals> diff --git a/doc/classes/CameraServer.xml b/doc/classes/CameraServer.xml index 983bd6cd91..020b5d887b 100644 --- a/doc/classes/CameraServer.xml +++ b/doc/classes/CameraServer.xml @@ -6,7 +6,7 @@ <description> The [CameraServer] keeps track of different cameras accessible in Godot. These are external cameras such as webcams or the cameras on your phone. It is notably used to provide AR modules with a video feed from the camera. - [b]Note:[/b] This class is currently only implemented on macOS and iOS. On other platforms, no [CameraFeed]s will be available. + [b]Note:[/b] This class is currently only implemented on macOS and iOS. To get a [CameraFeed] on iOS, the camera plugin from [url=https://github.com/godotengine/godot-ios-plugins]godot-ios-plugins[/url] is required. On other platforms, no [CameraFeed]s will be available. </description> <tutorials> </tutorials> diff --git a/doc/classes/CharacterBody2D.xml b/doc/classes/CharacterBody2D.xml index df409cb3c4..ede4d63cfc 100644 --- a/doc/classes/CharacterBody2D.xml +++ b/doc/classes/CharacterBody2D.xml @@ -30,7 +30,8 @@ <method name="get_floor_normal" qualifiers="const"> <return type="Vector2" /> <description> - Returns the surface normal of the floor at the last collision point. Only valid after calling [method move_and_slide] and when [method is_on_floor] returns [code]true[/code]. + Returns the collision normal of the floor at the last collision point. Only valid after calling [method move_and_slide] and when [method is_on_floor] returns [code]true[/code]. + [b]Warning:[/b] The collision normal is not always the same as the surface normal. </description> </method> <method name="get_last_motion" qualifiers="const"> @@ -94,7 +95,8 @@ <method name="get_wall_normal" qualifiers="const"> <return type="Vector2" /> <description> - Returns the surface normal of the wall at the last collision point. Only valid after calling [method move_and_slide] and when [method is_on_wall] returns [code]true[/code]. + Returns the collision normal of the wall at the last collision point. Only valid after calling [method move_and_slide] and when [method is_on_wall] returns [code]true[/code]. + [b]Warning:[/b] The collision normal is not always the same as the surface normal. </description> </method> <method name="is_on_ceiling" qualifiers="const"> diff --git a/doc/classes/CharacterBody3D.xml b/doc/classes/CharacterBody3D.xml index 498149b9c0..474adfc6ff 100644 --- a/doc/classes/CharacterBody3D.xml +++ b/doc/classes/CharacterBody3D.xml @@ -87,7 +87,8 @@ <method name="get_wall_normal" qualifiers="const"> <return type="Vector3" /> <description> - Returns the surface normal of the wall at the last collision point. Only valid after calling [method move_and_slide] and when [method is_on_wall] returns [code]true[/code]. + Returns the collision normal of the wall at the last collision point. Only valid after calling [method move_and_slide] and when [method is_on_wall] returns [code]true[/code]. + [b]Warning:[/b] The collision normal is not always the same as the surface normal. </description> </method> <method name="is_on_ceiling" qualifiers="const"> diff --git a/doc/classes/MeshInstance3D.xml b/doc/classes/MeshInstance3D.xml index f56b03bdc4..e5187cf7a1 100644 --- a/doc/classes/MeshInstance3D.xml +++ b/doc/classes/MeshInstance3D.xml @@ -70,6 +70,12 @@ Returns the value of the blend shape at the given [param blend_shape_idx]. Returns [code]0.0[/code] and produces an error if [member mesh] is [code]null[/code] or doesn't have a blend shape at that index. </description> </method> + <method name="get_skin_reference" qualifiers="const"> + <return type="SkinReference" /> + <description> + Returns the internal [SkinReference] containing the skeleton's [RID] attached to this RID. See also [method Resource.get_rid], [method SkinReference.get_skeleton], and [method RenderingServer.instance_attach_skeleton]. + </description> + </method> <method name="get_surface_override_material" qualifiers="const"> <return type="Material" /> <param index="0" name="surface" type="int" /> diff --git a/doc/classes/Node.xml b/doc/classes/Node.xml index d9138b059c..aea4082dbe 100644 --- a/doc/classes/Node.xml +++ b/doc/classes/Node.xml @@ -795,9 +795,8 @@ <return type="void" /> <param index="0" name="node" type="Node" /> <param index="1" name="keep_groups" type="bool" default="false" /> - <param index="2" name="keep_children" type="bool" default="true" /> <description> - Replaces this node by the given [param node]. If [param keep_children] is [code]true[/code] all children of this node are moved to [param node]. + Replaces this node by the given [param node]. All children of this node are moved to [param node]. If [param keep_groups] is [code]true[/code], the [param node] is added to the same groups that the replaced node is in (see [method add_to_group]). [b]Warning:[/b] The replaced node is removed from the tree, but it is [b]not[/b] deleted. To prevent memory leaks, store a reference to the node in a variable, or use [method Object.free]. </description> diff --git a/doc/classes/PhysicsServer3D.xml b/doc/classes/PhysicsServer3D.xml index 7dcb185834..4a4a1ad025 100644 --- a/doc/classes/PhysicsServer3D.xml +++ b/doc/classes/PhysicsServer3D.xml @@ -1628,7 +1628,7 @@ Constant to set/get the priority (order of processing) of an area. </constant> <constant name="AREA_PARAM_WIND_FORCE_MAGNITUDE" value="10" enum="AreaParameter"> - Constant to set/get the magnitude of area-specific wind force. + Constant to set/get the magnitude of area-specific wind force. This wind force only applies to [SoftBody3D] nodes. Other physics bodies are currently not affected by wind. </constant> <constant name="AREA_PARAM_WIND_SOURCE" value="11" enum="AreaParameter"> Constant to set/get the 3D vector that specifies the origin from which an area-specific wind blows. diff --git a/doc/classes/SkinReference.xml b/doc/classes/SkinReference.xml index 466dbe2500..cb0c44cefa 100644 --- a/doc/classes/SkinReference.xml +++ b/doc/classes/SkinReference.xml @@ -1,8 +1,14 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="SkinReference" inherits="RefCounted" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> + A reference-counted holder object for a skeleton RID used in the [RenderingServer]. </brief_description> <description> + An internal object containing a mapping from a [Skin] used within the context of a particular [MeshInstance3D] to refer to the skeleton's [RID] in the RenderingServer. + See also [method MeshInstance3D.get_skin_reference] and [method RenderingServer.instance_attach_skeleton]. + Note that despite the similar naming, the skeleton RID used in the [RenderingServer] does not have a direct one-to-one correspondence to a [Skeleton3D] node. + In particular, a [Skeleton3D] node with no [MeshInstance3D] children may be unknown to the [RenderingServer]. + On the other hand, a [Skeleton3D] with multiple [MeshInstance3D] nodes which each have different [member MeshInstance3D.skin] objects may have multiple SkinReference instances (and hence, multiple skeleton [RID]s). </description> <tutorials> </tutorials> @@ -10,11 +16,14 @@ <method name="get_skeleton" qualifiers="const"> <return type="RID" /> <description> + Returns the [RID] owned by this SkinReference, as returned by [method RenderingServer.skeleton_create]. </description> </method> <method name="get_skin" qualifiers="const"> <return type="Skin" /> <description> + Returns the [Skin] connected to this SkinReference. In the case of [MeshInstance3D] with no [member MeshInstance3D.skin] assigned, this will reference an internal default [Skin] owned by that [MeshInstance3D]. + Note that a single [Skin] may have more than one [SkinReference] in the case that it is shared by meshes across multiple [Skeleton3D] nodes. </description> </method> </methods> diff --git a/doc/classes/SoftBody3D.xml b/doc/classes/SoftBody3D.xml index a4d80a7c3e..195196b78c 100644 --- a/doc/classes/SoftBody3D.xml +++ b/doc/classes/SoftBody3D.xml @@ -5,6 +5,7 @@ </brief_description> <description> A deformable 3D physics mesh. Used to create elastic or deformable objects such as cloth, rubber, or other flexible materials. + Additionally, [SoftBody3D] is subject to wind forces defined in [Area3D] (see [member Area3D.wind_source_path], [member Area3D.wind_force_magnitude], and [member Area3D.wind_attenuation_factor]). [b]Note:[/b] There are many known bugs in [SoftBody3D]. Therefore, it's not recommended to use them for things that can affect gameplay (such as trampolines). </description> <tutorials> diff --git a/doc/classes/Vector2.xml b/doc/classes/Vector2.xml index 0b39743a72..7b166a4fb0 100644 --- a/doc/classes/Vector2.xml +++ b/doc/classes/Vector2.xml @@ -110,7 +110,8 @@ <return type="Vector2" /> <param index="0" name="n" type="Vector2" /> <description> - Returns a new vector "bounced off" from a plane defined by the given normal. + Returns the vector "bounced off" from a line defined by the given normal [param n] perpendicular to the line. + [b]Note:[/b] [method bounce] performs the operation that most engines and frameworks call [code skip-lint]reflect()[/code]. </description> </method> <method name="ceil" qualifiers="const"> @@ -321,9 +322,10 @@ </method> <method name="reflect" qualifiers="const"> <return type="Vector2" /> - <param index="0" name="n" type="Vector2" /> + <param index="0" name="line" type="Vector2" /> <description> - Returns the result of reflecting the vector from a line defined by the given direction vector [param n]. + Returns the result of reflecting the vector from a line defined by the given direction vector [param line]. + [b]Note:[/b] [method reflect] differs from what other engines and frameworks call [code skip-lint]reflect()[/code]. In other engines, [code skip-lint]reflect()[/code] takes a normal direction which is a direction perpendicular to the line. In Godot, you specify the direction of the line directly. See also [method bounce] which does what most engines call [code skip-lint]reflect()[/code]. </description> </method> <method name="rotated" qualifiers="const"> diff --git a/doc/classes/Vector3.xml b/doc/classes/Vector3.xml index af1383fe22..031d91af78 100644 --- a/doc/classes/Vector3.xml +++ b/doc/classes/Vector3.xml @@ -86,7 +86,8 @@ <return type="Vector3" /> <param index="0" name="n" type="Vector3" /> <description> - Returns the vector "bounced off" from a plane defined by the given normal. + Returns the vector "bounced off" from a plane defined by the given normal [param n]. + [b]Note:[/b] [method bounce] performs the operation that most engines and frameworks call [code skip-lint]reflect()[/code]. </description> </method> <method name="ceil" qualifiers="const"> @@ -306,9 +307,10 @@ </method> <method name="reflect" qualifiers="const"> <return type="Vector3" /> - <param index="0" name="n" type="Vector3" /> + <param index="0" name="direction" type="Vector3" /> <description> - Returns the result of reflecting the vector from a plane defined by the given normal [param n]. + Returns the result of reflecting the vector from a plane defined by the given direction vector [param direction]. + [b]Note:[/b] [method reflect] differs from what other engines and frameworks call [code skip-lint]reflect()[/code]. In other engines, [code skip-lint]reflect()[/code] takes a normal direction which is a direction perpendicular to the plane. In Godot, you specify a direction parallel to the plane. See also [method bounce] which does what most engines call [code skip-lint]reflect()[/code]. </description> </method> <method name="rotated" qualifiers="const"> diff --git a/drivers/gles3/storage/light_storage.h b/drivers/gles3/storage/light_storage.h index 8107a3ad4d..b6e64c9492 100644 --- a/drivers/gles3/storage/light_storage.h +++ b/drivers/gles3/storage/light_storage.h @@ -441,6 +441,29 @@ public: virtual void light_instance_set_shadow_transform(RID p_light_instance, const Projection &p_projection, const Transform3D &p_transform, float p_far, float p_split, int p_pass, float p_shadow_texel_size, float p_bias_scale = 1.0, float p_range_begin = 0, const Vector2 &p_uv_scale = Vector2()) override; virtual void light_instance_mark_visible(RID p_light_instance) override; + virtual bool light_instance_is_shadow_visible_at_position(RID p_light_instance, const Vector3 &p_position) const override { + const LightInstance *light_instance = light_instance_owner.get_or_null(p_light_instance); + ERR_FAIL_NULL_V(light_instance, false); + const Light *light = light_owner.get_or_null(light_instance->light); + ERR_FAIL_NULL_V(light, false); + + if (!light->shadow) { + return false; + } + + if (!light->distance_fade) { + return true; + } + + real_t distance = p_position.distance_to(light_instance->transform.origin); + + if (distance > light->distance_fade_shadow + light->distance_fade_length) { + return false; + } + + return true; + } + _FORCE_INLINE_ RID light_instance_get_base_light(RID p_light_instance) { LightInstance *li = light_instance_owner.get_or_null(p_light_instance); return li->light; diff --git a/editor/debugger/editor_file_server.cpp b/editor/debugger/editor_file_server.cpp index 428b5f4c26..e84eb14636 100644 --- a/editor/debugger/editor_file_server.cpp +++ b/editor/debugger/editor_file_server.cpp @@ -201,7 +201,7 @@ void EditorFileServer::poll() { // Scan files to send. _scan_files_changed(EditorFileSystem::get_singleton()->get_filesystem(), tags, files_to_send, cached_files); // Add forced export files - Vector<String> forced_export = EditorExportPlatform::get_main_pack_forced_export_files(); + Vector<String> forced_export = EditorExportPlatform::get_forced_export_files(); for (int i = 0; i < forced_export.size(); i++) { _add_custom_file(forced_export[i], files_to_send, cached_files); } diff --git a/editor/editor_build_profile.cpp b/editor/editor_build_profile.cpp index 67bc55fdd6..c6ecaba230 100644 --- a/editor/editor_build_profile.cpp +++ b/editor/editor_build_profile.cpp @@ -861,7 +861,7 @@ EditorBuildProfileManager::EditorBuildProfileManager() { class_list->connect("item_edited", callable_mp(this, &EditorBuildProfileManager::_class_list_item_edited), CONNECT_DEFERRED); class_list->connect("item_collapsed", callable_mp(this, &EditorBuildProfileManager::_class_list_item_collapsed)); // It will be displayed once the user creates or chooses a profile. - main_vbc->add_margin_child(TTR("Configure Engine Build Profile:"), class_list, true); + main_vbc->add_margin_child(TTR("Configure Engine Compilation Profile:"), class_list, true); description_bit = memnew(EditorHelpBit); description_bit->set_custom_minimum_size(Size2(0, 80) * EDSCALE); @@ -875,7 +875,7 @@ EditorBuildProfileManager::EditorBuildProfileManager() { import_profile = memnew(EditorFileDialog); add_child(import_profile); import_profile->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE); - import_profile->add_filter("*.build", TTR("Engine Build Profile")); + import_profile->add_filter("*.build", TTR("Engine Compilation Profile")); import_profile->connect("files_selected", callable_mp(this, &EditorBuildProfileManager::_import_profile)); import_profile->set_title(TTR("Load Profile")); import_profile->set_access(EditorFileDialog::ACCESS_FILESYSTEM); @@ -883,7 +883,7 @@ EditorBuildProfileManager::EditorBuildProfileManager() { export_profile = memnew(EditorFileDialog); add_child(export_profile); export_profile->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE); - export_profile->add_filter("*.build", TTR("Engine Build Profile")); + export_profile->add_filter("*.build", TTR("Engine Compilation Profile")); export_profile->connect("file_selected", callable_mp(this, &EditorBuildProfileManager::_export_profile)); export_profile->set_title(TTR("Export Profile")); export_profile->set_access(EditorFileDialog::ACCESS_FILESYSTEM); @@ -892,7 +892,7 @@ EditorBuildProfileManager::EditorBuildProfileManager() { main_vbc->add_margin_child(TTR("Forced Classes on Detect:"), force_detect_classes); force_detect_classes->connect("text_changed", callable_mp(this, &EditorBuildProfileManager::_force_detect_classes_changed)); - set_title(TTR("Edit Build Configuration Profile")); + set_title(TTR("Edit Compilation Configuration Profile")); singleton = this; } diff --git a/editor/editor_data.cpp b/editor/editor_data.cpp index 72225fd454..bdc6504417 100644 --- a/editor/editor_data.cpp +++ b/editor/editor_data.cpp @@ -722,8 +722,7 @@ bool EditorData::check_and_update_scene(int p_idx) { new_scene->set_scene_file_path(edited_scene[p_idx].root->get_scene_file_path()); Node *old_root = edited_scene[p_idx].root; - edited_scene.write[p_idx].root = new_scene; - old_root->replace_by(new_scene, false, false); + EditorNode::get_singleton()->set_edited_scene(new_scene); memdelete(old_root); edited_scene.write[p_idx].selection = new_selection; diff --git a/editor/editor_help.cpp b/editor/editor_help.cpp index cd9e873038..7fded0b807 100644 --- a/editor/editor_help.cpp +++ b/editor/editor_help.cpp @@ -3575,8 +3575,11 @@ void EditorHelpHighlighter::reset_cache() { } EditorHelpHighlighter::EditorHelpHighlighter() { + const Color text_color = EDITOR_GET("text_editor/theme/highlighting/text_color"); + #ifdef MODULE_GDSCRIPT_ENABLED TextEdit *gdscript_text_edit = memnew(TextEdit); + gdscript_text_edit->add_theme_color_override("font_color", text_color); Ref<GDScript> gdscript; gdscript.instantiate(); @@ -3593,6 +3596,7 @@ EditorHelpHighlighter::EditorHelpHighlighter() { #ifdef MODULE_MONO_ENABLED TextEdit *csharp_text_edit = memnew(TextEdit); + csharp_text_edit->add_theme_color_override("font_color", text_color); // See GH-89610. //Ref<CSharpScript> csharp; diff --git a/editor/editor_layouts_dialog.cpp b/editor/editor_layouts_dialog.cpp index db6fa2c035..52047c569d 100644 --- a/editor/editor_layouts_dialog.cpp +++ b/editor/editor_layouts_dialog.cpp @@ -31,8 +31,6 @@ #include "editor_layouts_dialog.h" #include "core/io/config_file.h" -#include "core/object/class_db.h" -#include "core/os/keyboard.h" #include "editor/editor_settings.h" #include "editor/themes/editor_scale.h" #include "scene/gui/item_list.h" @@ -60,7 +58,7 @@ void EditorLayoutsDialog::_update_ok_disable_state() { if (layout_names->is_anything_selected()) { get_ok_button()->set_disabled(false); } else { - get_ok_button()->set_disabled(!name->is_visible() || name->get_text().is_empty()); + get_ok_button()->set_disabled(!name->is_visible() || name->get_text().strip_edges().is_empty()); } } @@ -80,8 +78,8 @@ void EditorLayoutsDialog::ok_pressed() { for (int i = 0; i < selected_items.size(); ++i) { emit_signal(SNAME("name_confirmed"), layout_names->get_item_text(selected_items[i])); } - } else if (name->is_visible() && !name->get_text().is_empty()) { - emit_signal(SNAME("name_confirmed"), name->get_text()); + } else if (name->is_visible() && !name->get_text().strip_edges().is_empty()) { + emit_signal(SNAME("name_confirmed"), name->get_text().strip_edges()); } } diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 0e6c814b44..4fb1a86ce2 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -2136,10 +2136,6 @@ void EditorNode::_dialog_action(String p_file) { } break; case SETTINGS_LAYOUT_DELETE: { - if (p_file.is_empty()) { - return; - } - Ref<ConfigFile> config; config.instantiate(); Error err = config->load(EditorSettings::get_singleton()->get_editor_layouts_config()); @@ -2896,9 +2892,6 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { } } } break; - case TOOLS_BUILD_PROFILE_MANAGER: { - build_profile_manager->popup_centered_clamped(Size2(700, 800) * EDSCALE, 0.8); - } break; case RUN_USER_DATA_FOLDER: { // Ensure_user_data_dir() to prevent the edge case: "Open User Data Folder" won't work after the project was renamed in ProjectSettingsEditor unless the project is saved. OS::get_singleton()->ensure_user_data_dir(); @@ -3203,6 +3196,9 @@ void EditorNode::_tool_menu_option(int p_idx) { case TOOLS_ORPHAN_RESOURCES: { orphan_resources->show(); } break; + case TOOLS_BUILD_PROFILE_MANAGER: { + build_profile_manager->popup_centered_clamped(Size2(700, 800) * EDSCALE, 0.8); + } break; case TOOLS_SURFACE_UPGRADE: { surface_upgrade_dialog->popup_on_demand(); } break; @@ -3674,9 +3670,7 @@ void EditorNode::set_edited_scene(Node *p_scene) { if (old_edited_scene_root->get_parent() == scene_root) { scene_root->remove_child(old_edited_scene_root); } - if (old_edited_scene_root->is_connected("replacing_by", callable_mp(this, &EditorNode::set_edited_scene))) { - old_edited_scene_root->disconnect(SNAME("replacing_by"), callable_mp(this, &EditorNode::set_edited_scene)); - } + old_edited_scene_root->disconnect(SNAME("replacing_by"), callable_mp(this, &EditorNode::set_edited_scene)); } get_editor_data().set_edited_scene_root(p_scene); @@ -6783,13 +6777,12 @@ EditorNode::EditorNode() { #endif project_menu->add_separator(); - project_menu->add_item(TTR("Customize Engine Build Configuration..."), TOOLS_BUILD_PROFILE_MANAGER); - project_menu->add_separator(); tool_menu = memnew(PopupMenu); tool_menu->connect("index_pressed", callable_mp(this, &EditorNode::_tool_menu_option)); project_menu->add_submenu_node_item(TTR("Tools"), tool_menu); tool_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/orphan_resource_explorer", TTR("Orphan Resource Explorer...")), TOOLS_ORPHAN_RESOURCES); + tool_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/engine_compilation_configuration_editor", TTR("Engine Compilation Configuration Editor...")), TOOLS_BUILD_PROFILE_MANAGER); tool_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/upgrade_mesh_surfaces", TTR("Upgrade Mesh Surfaces...")), TOOLS_SURFACE_UPGRADE); project_menu->add_separator(); diff --git a/editor/export/editor_export_platform.cpp b/editor/export/editor_export_platform.cpp index d2a187bcfd..33fdd7418e 100644 --- a/editor/export/editor_export_platform.cpp +++ b/editor/export/editor_export_platform.cpp @@ -864,19 +864,23 @@ String EditorExportPlatform::_get_script_encryption_key(const Ref<EditorExportPr return p_preset->get_script_encryption_key().to_lower(); } -Vector<String> EditorExportPlatform::get_main_pack_forced_export_files() { - // First, get files required by any PCK. - Vector<String> files = get_forced_export_files(); +Vector<String> EditorExportPlatform::get_forced_export_files() { + Vector<String> files; + + files.push_back(ProjectSettings::get_singleton()->get_global_class_list_path()); String icon = GLOBAL_GET("application/config/icon"); + String splash = GLOBAL_GET("application/boot_splash/image"); if (!icon.is_empty() && FileAccess::exists(icon)) { files.push_back(icon); } - - String splash = GLOBAL_GET("application/boot_splash/image"); - if (!splash.is_empty() && icon != splash && FileAccess::exists(splash)) { + if (!splash.is_empty() && FileAccess::exists(splash) && icon != splash) { files.push_back(splash); } + String resource_cache_file = ResourceUID::get_cache_file(); + if (FileAccess::exists(resource_cache_file)) { + files.push_back(resource_cache_file); + } String extension_list_config_file = GDExtension::get_extension_list_config_file(); if (FileAccess::exists(extension_list_config_file)) { @@ -909,19 +913,7 @@ Vector<String> EditorExportPlatform::get_main_pack_forced_export_files() { return files; } -Vector<String> EditorExportPlatform::get_forced_export_files() { - Vector<String> files; - files.push_back(ProjectSettings::get_singleton()->get_global_class_list_path()); - - String resource_cache_file = ResourceUID::get_cache_file(); - if (FileAccess::exists(resource_cache_file)) { - files.push_back(resource_cache_file); - } - - return files; -} - -Error EditorExportPlatform::export_project_files(bool p_main_pack, const Ref<EditorExportPreset> &p_preset, bool p_debug, EditorExportSaveFunction p_func, void *p_udata, EditorExportSaveSharedObject p_so_func) { +Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> &p_preset, bool p_debug, EditorExportSaveFunction p_func, void *p_udata, EditorExportSaveSharedObject p_so_func) { //figure out paths of files that will be exported HashSet<String> paths; Vector<String> path_remaps; @@ -968,11 +960,9 @@ Error EditorExportPlatform::export_project_files(bool p_main_pack, const Ref<Edi } } - if (p_main_pack) { - // Add native icons to non-resource include list. - _edit_filter_list(paths, String("*.icns"), false); - _edit_filter_list(paths, String("*.ico"), false); - } + //add native icons to non-resource include list + _edit_filter_list(paths, String("*.icns"), false); + _edit_filter_list(paths, String("*.ico"), false); _edit_filter_list(paths, p_preset->get_include_filter(), false); _edit_filter_list(paths, p_preset->get_exclude_filter(), true); @@ -1404,12 +1394,7 @@ Error EditorExportPlatform::export_project_files(bool p_main_pack, const Ref<Edi } } - Vector<String> forced_export; - if (p_main_pack) { - forced_export = get_main_pack_forced_export_files(); - } else { - forced_export = get_forced_export_files(); - } + Vector<String> forced_export = get_forced_export_files(); for (int i = 0; i < forced_export.size(); i++) { Vector<uint8_t> array = FileAccess::get_file_as_bytes(forced_export[i]); err = p_func(p_udata, forced_export[i], array, idx, total, enc_in_filters, enc_ex_filters, key); @@ -1418,19 +1403,13 @@ Error EditorExportPlatform::export_project_files(bool p_main_pack, const Ref<Edi } } - if (p_main_pack) { - String config_file = "project.binary"; - String engine_cfb = EditorPaths::get_singleton()->get_cache_dir().path_join("tmp" + config_file); - ProjectSettings::get_singleton()->save_custom(engine_cfb, custom_map, custom_list); - Vector<uint8_t> data = FileAccess::get_file_as_bytes(engine_cfb); - DirAccess::remove_file_or_error(engine_cfb); - err = p_func(p_udata, "res://" + config_file, data, idx, total, enc_in_filters, enc_ex_filters, key); - if (err != OK) { - return err; - } - } + String config_file = "project.binary"; + String engine_cfb = EditorPaths::get_singleton()->get_cache_dir().path_join("tmp" + config_file); + ProjectSettings::get_singleton()->save_custom(engine_cfb, custom_map, custom_list); + Vector<uint8_t> data = FileAccess::get_file_as_bytes(engine_cfb); + DirAccess::remove_file_or_error(engine_cfb); - return OK; + return p_func(p_udata, "res://" + config_file, data, idx, total, enc_in_filters, enc_ex_filters, key); } Error EditorExportPlatform::_add_shared_object(void *p_userdata, const SharedObject &p_so) { @@ -1559,7 +1538,7 @@ void EditorExportPlatform::zip_folder_recursive(zipFile &p_zip, const String &p_ da->list_dir_end(); } -Error EditorExportPlatform::save_pack(bool p_main_pack, const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, Vector<SharedObject> *p_so_files, bool p_embed, int64_t *r_embedded_start, int64_t *r_embedded_size) { +Error EditorExportPlatform::save_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, Vector<SharedObject> *p_so_files, bool p_embed, int64_t *r_embedded_start, int64_t *r_embedded_size) { EditorProgress ep("savepack", TTR("Packing"), 102, true); // Create the temporary export directory if it doesn't exist. @@ -1578,7 +1557,7 @@ Error EditorExportPlatform::save_pack(bool p_main_pack, const Ref<EditorExportPr pd.f = ftmp; pd.so_files = p_so_files; - Error err = export_project_files(p_main_pack, p_preset, p_debug, _save_pack_file, &pd, _add_shared_object); + Error err = export_project_files(p_preset, p_debug, _save_pack_file, &pd, _add_shared_object); // Close temp file. pd.f.unref(); @@ -1796,7 +1775,7 @@ Error EditorExportPlatform::save_zip(const Ref<EditorExportPreset> &p_preset, bo zd.ep = &ep; zd.zip = zip; - Error err = export_project_files(false, p_preset, p_debug, _save_zip_file, &zd); + Error err = export_project_files(p_preset, p_debug, _save_zip_file, &zd); if (err != OK && err != ERR_SKIP) { add_message(EXPORT_MESSAGE_ERROR, TTR("Save ZIP"), TTR("Failed to export project files.")); } @@ -1808,7 +1787,7 @@ Error EditorExportPlatform::save_zip(const Ref<EditorExportPreset> &p_preset, bo Error EditorExportPlatform::export_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) { ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags); - return save_pack(false, p_preset, p_debug, p_path); + return save_pack(p_preset, p_debug, p_path); } Error EditorExportPlatform::export_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) { diff --git a/editor/export/editor_export_platform.h b/editor/export/editor_export_platform.h index 4c4e64b1a6..26e1f86c8b 100644 --- a/editor/export/editor_export_platform.h +++ b/editor/export/editor_export_platform.h @@ -203,7 +203,6 @@ public: return worst_type; } - static Vector<String> get_main_pack_forced_export_files(); static Vector<String> get_forced_export_files(); virtual bool fill_log_messages(RichTextLabel *p_log, Error p_err); @@ -217,9 +216,9 @@ public: virtual String get_name() const = 0; virtual Ref<Texture2D> get_logo() const = 0; - Error export_project_files(bool p_main_pack, const Ref<EditorExportPreset> &p_preset, bool p_debug, EditorExportSaveFunction p_func, void *p_udata, EditorExportSaveSharedObject p_so_func = nullptr); + Error export_project_files(const Ref<EditorExportPreset> &p_preset, bool p_debug, EditorExportSaveFunction p_func, void *p_udata, EditorExportSaveSharedObject p_so_func = nullptr); - Error save_pack(bool p_main_pack, const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, Vector<SharedObject> *p_so_files = nullptr, bool p_embed = false, int64_t *r_embedded_start = nullptr, int64_t *r_embedded_size = nullptr); + Error save_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, Vector<SharedObject> *p_so_files = nullptr, bool p_embed = false, int64_t *r_embedded_start = nullptr, int64_t *r_embedded_size = nullptr); Error save_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path); virtual bool poll_export() { return false; } diff --git a/editor/export/editor_export_platform_pc.cpp b/editor/export/editor_export_platform_pc.cpp index f583c0ec02..cdaf18b346 100644 --- a/editor/export/editor_export_platform_pc.cpp +++ b/editor/export/editor_export_platform_pc.cpp @@ -194,7 +194,7 @@ Error EditorExportPlatformPC::export_project_data(const Ref<EditorExportPreset> int64_t embedded_pos; int64_t embedded_size; - Error err = save_pack(true, p_preset, p_debug, pck_path, &so_files, p_preset->get("binary_format/embed_pck"), &embedded_pos, &embedded_size); + Error err = save_pack(p_preset, p_debug, pck_path, &so_files, p_preset->get("binary_format/embed_pck"), &embedded_pos, &embedded_size); if (err == OK && p_preset->get("binary_format/embed_pck")) { if (embedded_size >= 0x100000000 && String(p_preset->get("binary_format/architecture")).contains("32")) { add_message(EXPORT_MESSAGE_ERROR, TTR("PCK Embedding"), TTR("On 32-bit exports the embedded PCK cannot be bigger than 4 GiB.")); diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp index 2087c8cee6..3b6ce8d396 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -2531,6 +2531,14 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected } } break; + case FILE_COPY_ABSOLUTE_PATH: { + if (!p_selected.is_empty()) { + const String &fpath = p_selected[0]; + const String absolute_path = ProjectSettings::get_singleton()->globalize_path(fpath); + DisplayServer::get_singleton()->clipboard_set(absolute_path); + } + } break; + case FILE_COPY_UID: { if (!p_selected.is_empty()) { ResourceUID::ID uid = ResourceLoader::get_resource_uid(p_selected[0]); @@ -3193,6 +3201,7 @@ void FileSystemDock::_file_and_folders_fill_popup(PopupMenu *p_popup, const Vect if (p_paths.size() == 1) { p_popup->add_icon_shortcut(get_editor_theme_icon(SNAME("ActionCopy")), ED_GET_SHORTCUT("filesystem_dock/copy_path"), FILE_COPY_PATH); + p_popup->add_shortcut(ED_GET_SHORTCUT("filesystem_dock/copy_absolute_path"), FILE_COPY_ABSOLUTE_PATH); if (ResourceLoader::get_resource_uid(p_paths[0]) != ResourceUID::INVALID_ID) { p_popup->add_icon_shortcut(get_editor_theme_icon(SNAME("Instance")), ED_GET_SHORTCUT("filesystem_dock/copy_uid"), FILE_COPY_UID); } @@ -3481,6 +3490,8 @@ void FileSystemDock::_tree_gui_input(Ref<InputEvent> p_event) { _tree_rmb_option(FILE_DUPLICATE); } else if (ED_IS_SHORTCUT("filesystem_dock/copy_path", p_event)) { _tree_rmb_option(FILE_COPY_PATH); + } else if (ED_IS_SHORTCUT("filesystem_dock/copy_absolute_path", p_event)) { + _tree_rmb_option(FILE_COPY_ABSOLUTE_PATH); } else if (ED_IS_SHORTCUT("filesystem_dock/copy_uid", p_event)) { _tree_rmb_option(FILE_COPY_UID); } else if (ED_IS_SHORTCUT("filesystem_dock/delete", p_event)) { @@ -3553,6 +3564,8 @@ void FileSystemDock::_file_list_gui_input(Ref<InputEvent> p_event) { _file_list_rmb_option(FILE_DUPLICATE); } else if (ED_IS_SHORTCUT("filesystem_dock/copy_path", p_event)) { _file_list_rmb_option(FILE_COPY_PATH); + } else if (ED_IS_SHORTCUT("filesystem_dock/copy_absolute_path", p_event)) { + _file_list_rmb_option(FILE_COPY_ABSOLUTE_PATH); } else if (ED_IS_SHORTCUT("filesystem_dock/delete", p_event)) { _file_list_rmb_option(FILE_REMOVE); } else if (ED_IS_SHORTCUT("filesystem_dock/rename", p_event)) { @@ -3856,6 +3869,7 @@ FileSystemDock::FileSystemDock() { // `KeyModifierMask::CMD_OR_CTRL | Key::C` conflicts with other editor shortcuts. ED_SHORTCUT("filesystem_dock/copy_path", TTR("Copy Path"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::C); + ED_SHORTCUT("filesystem_dock/copy_absolute_path", TTR("Copy Absolute Path")); ED_SHORTCUT("filesystem_dock/copy_uid", TTR("Copy UID")); ED_SHORTCUT("filesystem_dock/duplicate", TTR("Duplicate..."), KeyModifierMask::CMD_OR_CTRL | Key::D); ED_SHORTCUT("filesystem_dock/delete", TTR("Delete"), Key::KEY_DELETE); diff --git a/editor/filesystem_dock.h b/editor/filesystem_dock.h index b950075928..c9fe3a8baf 100644 --- a/editor/filesystem_dock.h +++ b/editor/filesystem_dock.h @@ -129,6 +129,7 @@ private: FILE_OPEN_EXTERNAL, FILE_OPEN_IN_TERMINAL, FILE_COPY_PATH, + FILE_COPY_ABSOLUTE_PATH, FILE_COPY_UID, FOLDER_EXPAND_ALL, FOLDER_COLLAPSE_ALL, diff --git a/editor/gui/scene_tree_editor.h b/editor/gui/scene_tree_editor.h index b4d9644f16..9ae1e99a27 100644 --- a/editor/gui/scene_tree_editor.h +++ b/editor/gui/scene_tree_editor.h @@ -160,6 +160,7 @@ public: void set_marked(const HashSet<Node *> &p_marked, bool p_selectable = true, bool p_children_selectable = true); void set_marked(Node *p_marked, bool p_selectable = true, bool p_children_selectable = true); + bool has_marked() const { return !marked.is_empty(); } void set_selected(Node *p_node, bool p_emit_selected = true); Node *get_selected(); void set_can_rename(bool p_can_rename) { can_rename = p_can_rename; } diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index 26ddec6603..757b9e72ea 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -76,6 +76,43 @@ void SceneTreeDock::_quick_open() { instantiate_scenes(quick_open->get_selected_files(), scene_tree->get_selected()); } +void SceneTreeDock::_inspect_hovered_node() { + scene_tree->set_selected(node_hovered_now); + scene_tree->set_marked(node_hovered_now); + Tree *tree = scene_tree->get_scene_tree(); + TreeItem *item = tree->get_item_at_position(tree->get_local_mouse_position()); + if (item) { + item->set_as_cursor(0); + } + InspectorDock::get_inspector_singleton()->edit(node_hovered_now); + InspectorDock::get_inspector_singleton()->propagate_notification(NOTIFICATION_DRAG_BEGIN); // Enable inspector drag preview after it updated. + InspectorDock::get_singleton()->update(node_hovered_now); + EditorNode::get_singleton()->hide_unused_editors(); +} + +void SceneTreeDock::_handle_hover_to_inspect() { + Tree *tree = scene_tree->get_scene_tree(); + TreeItem *item = tree->get_item_at_position(tree->get_local_mouse_position()); + + if (item) { + const NodePath &np = item->get_metadata(0); + node_hovered_now = get_node_or_null(np); + if (node_hovered_previously != node_hovered_now) { + inspect_hovered_node_delay->start(); + } + node_hovered_previously = node_hovered_now; + } else { + _reset_hovering_timer(); + } +} + +void SceneTreeDock::_reset_hovering_timer() { + if (!inspect_hovered_node_delay->is_stopped()) { + inspect_hovered_node_delay->stop(); + } + node_hovered_previously = nullptr; +} + void SceneTreeDock::input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); @@ -92,6 +129,17 @@ void SceneTreeDock::input(const Ref<InputEvent> &p_event) { _push_item(pending_click_select); pending_click_select = nullptr; } + + if (mb->is_released()) { + if (scene_tree->has_marked()) { + scene_tree->set_marked(nullptr); + } + _reset_hovering_timer(); + } + } + + if (tree_clicked && get_viewport()->gui_is_dragging()) { + _handle_hover_to_inspect(); } } @@ -1539,6 +1587,10 @@ void SceneTreeDock::_notification(int p_what) { } } } break; + + case NOTIFICATION_DRAG_END: { + _reset_hovering_timer(); + } break; } } @@ -4300,6 +4352,7 @@ SceneTreeDock::SceneTreeDock(Node *p_scene_root, EditorSelection *p_editor_selec scene_tree->connect("files_dropped", callable_mp(this, &SceneTreeDock::_files_dropped)); scene_tree->connect("script_dropped", callable_mp(this, &SceneTreeDock::_script_dropped)); scene_tree->connect("nodes_dragged", callable_mp(this, &SceneTreeDock::_nodes_drag_begin)); + scene_tree->connect("mouse_exited", callable_mp(this, &SceneTreeDock::_reset_hovering_timer)); scene_tree->get_scene_tree()->connect("gui_input", callable_mp(this, &SceneTreeDock::_scene_tree_gui_input)); scene_tree->get_scene_tree()->connect("item_icon_double_clicked", callable_mp(this, &SceneTreeDock::_focus_node)); @@ -4309,6 +4362,11 @@ SceneTreeDock::SceneTreeDock(Node *p_scene_root, EditorSelection *p_editor_selec scene_tree->set_as_scene_tree_dock(); scene_tree->set_editor_selection(editor_selection); + inspect_hovered_node_delay = memnew(Timer); + inspect_hovered_node_delay->connect("timeout", callable_mp(this, &SceneTreeDock::_inspect_hovered_node)); + inspect_hovered_node_delay->set_one_shot(true); + add_child(inspect_hovered_node_delay); + create_dialog = memnew(CreateDialog); create_dialog->set_base_type("Node"); add_child(create_dialog); diff --git a/editor/scene_tree_dock.h b/editor/scene_tree_dock.h index 4c1eb5715a..86e9ff8a47 100644 --- a/editor/scene_tree_dock.h +++ b/editor/scene_tree_dock.h @@ -234,6 +234,14 @@ class SceneTreeDock : public VBoxContainer { void _node_prerenamed(Node *p_node, const String &p_new_name); void _nodes_drag_begin(); + + void _handle_hover_to_inspect(); + void _inspect_hovered_node(); + void _reset_hovering_timer(); + Timer *inspect_hovered_node_delay = nullptr; + Node *node_hovered_now = nullptr; + Node *node_hovered_previously = nullptr; + virtual void input(const Ref<InputEvent> &p_event) override; virtual void shortcut_input(const Ref<InputEvent> &p_event) override; void _scene_tree_gui_input(Ref<InputEvent> p_event); diff --git a/misc/extension_api_validation/4.2-stable.expected b/misc/extension_api_validation/4.2-stable.expected index bb10b422dc..9cd732813e 100644 --- a/misc/extension_api_validation/4.2-stable.expected +++ b/misc/extension_api_validation/4.2-stable.expected @@ -241,13 +241,6 @@ Validate extension JSON: Error: Field 'classes/AcceptDialog/methods/remove_butto Changed argument type to the more specific one actually expected by the method. Compatibility method registered. -GH-89992 --------- -Validate extension JSON: Error: Field 'classes/Node/methods/replace_by/arguments': size changed value in new API, from 2 to 3. - -Added optional argument to prevent children to be reparented during replace_by. Compatibility method registered. - - GH-88047 -------- Validate extension JSON: Error: Field 'classes/AStar2D/methods/get_id_path/arguments': size changed value in new API, from 2 to 3. diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index dd9171c670..a30a87f3a1 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -1973,8 +1973,6 @@ void GDScriptAnalyzer::resolve_variable(GDScriptParser::VariableNode *p_variable if (p_is_local) { if (p_variable->usages == 0 && !String(p_variable->identifier->name).begins_with("_")) { parser->push_warning(p_variable, GDScriptWarning::UNUSED_VARIABLE, p_variable->identifier->name); - } else if (p_variable->assignments == 0) { - parser->push_warning(p_variable, GDScriptWarning::UNASSIGNED_VARIABLE, p_variable->identifier->name); } } is_shadowing(p_variable->identifier, kind, p_is_local); @@ -2615,9 +2613,21 @@ void GDScriptAnalyzer::update_array_literal_element_type(GDScriptParser::ArrayNo } void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assignment) { - reduce_expression(p_assignment->assignee); reduce_expression(p_assignment->assigned_value); +#ifdef DEBUG_ENABLED + // Increment assignment count for local variables. + // Before we reduce the assignee because we don't want to warn about not being assigned when performing the assignment. + if (p_assignment->assignee->type == GDScriptParser::Node::IDENTIFIER) { + GDScriptParser::IdentifierNode *id = static_cast<GDScriptParser::IdentifierNode *>(p_assignment->assignee); + if (id->source == GDScriptParser::IdentifierNode::LOCAL_VARIABLE && id->variable_source) { + id->variable_source->assignments++; + } + } +#endif + + reduce_expression(p_assignment->assignee); + if (p_assignment->assigned_value == nullptr || p_assignment->assignee == nullptr) { return; } @@ -2754,6 +2764,14 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig 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); } + // Check for assignment with operation before assignment. + if (p_assignment->operation != GDScriptParser::AssignmentNode::OP_NONE && p_assignment->assignee->type == GDScriptParser::Node::IDENTIFIER) { + GDScriptParser::IdentifierNode *id = static_cast<GDScriptParser::IdentifierNode *>(p_assignment->assignee); + // Use == 1 here because this assignment was already counted in the beginning of the function. + if (id->source == GDScriptParser::IdentifierNode::LOCAL_VARIABLE && id->variable_source && id->variable_source->assignments == 1) { + parser->push_warning(p_assignment, GDScriptWarning::UNASSIGNED_VARIABLE_OP_ASSIGN, id->name); + } + } #endif } @@ -2846,6 +2864,11 @@ void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_o result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; result.kind = GDScriptParser::DataType::BUILTIN; result.builtin_type = Variant::BOOL; + } else if (p_binary_op->variant_op == Variant::OP_MODULE && left_type.builtin_type == Variant::STRING) { + // The modulo operator (%) on string acts as formatting and will always return a string. + result.type_source = left_type.type_source; + result.kind = GDScriptParser::DataType::BUILTIN; + result.builtin_type = Variant::STRING; } else if (left_type.is_variant() || right_type.is_variant()) { // Cannot infer type because one operand can be anything. result.kind = GDScriptParser::DataType::VARIANT; @@ -3924,6 +3947,11 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident case GDScriptParser::IdentifierNode::LOCAL_VARIABLE: p_identifier->set_datatype(p_identifier->variable_source->get_datatype()); found_source = true; +#ifdef DEBUG_ENABLED + if (p_identifier->variable_source && p_identifier->variable_source->assignments == 0 && !(p_identifier->get_datatype().is_hard_type() && p_identifier->get_datatype().kind == GDScriptParser::DataType::BUILTIN)) { + parser->push_warning(p_identifier, GDScriptWarning::UNASSIGNED_VARIABLE, p_identifier->name); + } +#endif break; case GDScriptParser::IdentifierNode::LOCAL_ITERATOR: p_identifier->set_datatype(p_identifier->bind_source->get_datatype()); diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index d706f4e9a3..173bff575a 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -2769,10 +2769,6 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_assignment(ExpressionNode return parse_expression(false); // Return the following expression. } -#ifdef DEBUG_ENABLED - VariableNode *source_variable = nullptr; -#endif - switch (p_previous_operand->type) { case Node::IDENTIFIER: { #ifdef DEBUG_ENABLED @@ -2781,8 +2777,6 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_assignment(ExpressionNode IdentifierNode *id = static_cast<IdentifierNode *>(p_previous_operand); switch (id->source) { case IdentifierNode::LOCAL_VARIABLE: - - source_variable = id->variable_source; id->variable_source->usages--; break; case IdentifierNode::LOCAL_CONSTANT: @@ -2813,16 +2807,10 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_assignment(ExpressionNode update_extents(assignment); make_completion_context(COMPLETION_ASSIGN, assignment); -#ifdef DEBUG_ENABLED - bool has_operator = true; -#endif switch (previous.type) { case GDScriptTokenizer::Token::EQUAL: assignment->operation = AssignmentNode::OP_NONE; assignment->variant_op = Variant::OP_MAX; -#ifdef DEBUG_ENABLED - has_operator = false; -#endif break; case GDScriptTokenizer::Token::PLUS_EQUAL: assignment->operation = AssignmentNode::OP_ADDITION; @@ -2878,16 +2866,6 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_assignment(ExpressionNode } complete_extents(assignment); -#ifdef DEBUG_ENABLED - if (source_variable != nullptr) { - if (has_operator && source_variable->assignments == 0) { - push_warning(assignment, GDScriptWarning::UNASSIGNED_VARIABLE_OP_ASSIGN, source_variable->identifier->name); - } - - source_variable->assignments += 1; - } -#endif - return assignment; } diff --git a/modules/gdscript/gdscript_warning.cpp b/modules/gdscript/gdscript_warning.cpp index ca84c7fff6..708966a0a8 100644 --- a/modules/gdscript/gdscript_warning.cpp +++ b/modules/gdscript/gdscript_warning.cpp @@ -40,7 +40,7 @@ String GDScriptWarning::get_message() const { switch (code) { case UNASSIGNED_VARIABLE: CHECK_SYMBOLS(1); - return vformat(R"(The variable "%s" was used but never assigned a value.)", symbols[0]); + return vformat(R"(The variable "%s" was used before being assigned a value.)", symbols[0]); case UNASSIGNED_VARIABLE_OP_ASSIGN: CHECK_SYMBOLS(1); return vformat(R"(Using assignment with operation but the variable "%s" was not previously assigned a value.)", symbols[0]); diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_typecheck_inner_class.gd b/modules/gdscript/tests/scripts/analyzer/features/enum_typecheck_inner_class.gd index 1c4b19d8e0..0444051831 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/enum_typecheck_inner_class.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_typecheck_inner_class.gd @@ -11,6 +11,7 @@ class InnerClass: var e2: InnerClass.MyEnum var e3: EnumTypecheckOuterClass.InnerClass.MyEnum + @warning_ignore("unassigned_variable") print("Self ", e1, e2, e3) e1 = MyEnum.V1 e2 = MyEnum.V1 @@ -48,6 +49,7 @@ func test_outer_from_outer(): var e1: MyEnum var e2: EnumTypecheckOuterClass.MyEnum + @warning_ignore("unassigned_variable") print("Self ", e1, e2) e1 = MyEnum.V1 e2 = MyEnum.V1 @@ -66,6 +68,7 @@ func test_inner_from_outer(): var e1: InnerClass.MyEnum var e2: EnumTypecheckOuterClass.InnerClass.MyEnum + @warning_ignore("unassigned_variable") print("Inner ", e1, e2) e1 = InnerClass.MyEnum.V1 e2 = InnerClass.MyEnum.V1 diff --git a/modules/gdscript/tests/scripts/analyzer/features/infer_type_on_string_format.gd b/modules/gdscript/tests/scripts/analyzer/features/infer_type_on_string_format.gd new file mode 100644 index 0000000000..c83a3a8a14 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/infer_type_on_string_format.gd @@ -0,0 +1,6 @@ +# GH-88082 + +func test(): + var x = 1 + var message := "value: %s" % x + print(message) diff --git a/modules/gdscript/tests/scripts/analyzer/features/infer_type_on_string_format.out b/modules/gdscript/tests/scripts/analyzer/features/infer_type_on_string_format.out new file mode 100644 index 0000000000..cf6464a4c3 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/infer_type_on_string_format.out @@ -0,0 +1,2 @@ +GDTEST_OK +value: 1 diff --git a/modules/gdscript/tests/scripts/analyzer/features/unassigned_builtin_typed.gd b/modules/gdscript/tests/scripts/analyzer/features/unassigned_builtin_typed.gd new file mode 100644 index 0000000000..8099b366f3 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/unassigned_builtin_typed.gd @@ -0,0 +1,7 @@ +# GH-88117, GH-85796 + +func test(): + var array: Array + # Should not emit unassigned warning because the Array type has a default value. + array.assign([1, 2, 3]) + print(array) diff --git a/modules/gdscript/tests/scripts/analyzer/features/unassigned_builtin_typed.out b/modules/gdscript/tests/scripts/analyzer/features/unassigned_builtin_typed.out new file mode 100644 index 0000000000..6d85a6cc07 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/unassigned_builtin_typed.out @@ -0,0 +1,2 @@ +GDTEST_OK +[1, 2, 3] diff --git a/modules/gdscript/tests/scripts/analyzer/features/warning_ignore_warnings.gd b/modules/gdscript/tests/scripts/analyzer/features/warning_ignore_warnings.gd index 8a1ab6f406..333950d64e 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/warning_ignore_warnings.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/warning_ignore_warnings.gd @@ -31,8 +31,8 @@ func int_func() -> int: func test_warnings(unused_private_class_variable): var t = 1 - @warning_ignore("unassigned_variable") var unassigned_variable + @warning_ignore("unassigned_variable") print(unassigned_variable) var _unassigned_variable_op_assign diff --git a/modules/gdscript/tests/scripts/parser/warnings/unassigned_variable.gd b/modules/gdscript/tests/scripts/parser/warnings/unassigned_variable.gd index afb5059eea..b38cffb754 100644 --- a/modules/gdscript/tests/scripts/parser/warnings/unassigned_variable.gd +++ b/modules/gdscript/tests/scripts/parser/warnings/unassigned_variable.gd @@ -1,2 +1,11 @@ func test(): - var __ + var unassigned + print(unassigned) + unassigned = "something" # Assigned only after use. + + var a + print(a) # Unassigned, warn. + if a: # Still unassigned, warn. + a = 1 + print(a) # Assigned (dead code), don't warn. + print(a) # "Maybe" assigned, don't warn. diff --git a/modules/gdscript/tests/scripts/parser/warnings/unassigned_variable.out b/modules/gdscript/tests/scripts/parser/warnings/unassigned_variable.out index 10f89be132..36db304ef4 100644 --- a/modules/gdscript/tests/scripts/parser/warnings/unassigned_variable.out +++ b/modules/gdscript/tests/scripts/parser/warnings/unassigned_variable.out @@ -1,5 +1,16 @@ GDTEST_OK >> WARNING ->> Line: 2 +>> Line: 3 >> UNASSIGNED_VARIABLE ->> The variable "__" was used but never assigned a value. +>> The variable "unassigned" was used before being assigned a value. +>> WARNING +>> Line: 7 +>> UNASSIGNED_VARIABLE +>> The variable "a" was used before being assigned a value. +>> WARNING +>> Line: 8 +>> UNASSIGNED_VARIABLE +>> The variable "a" was used before being assigned a value. +<null> +<null> +<null> diff --git a/modules/gdscript/tests/scripts/runtime/features/reset_unassigned_variables_in_loops.gd b/modules/gdscript/tests/scripts/runtime/features/reset_unassigned_variables_in_loops.gd index c45f8dce48..2bd5362f2a 100644 --- a/modules/gdscript/tests/scripts/runtime/features/reset_unassigned_variables_in_loops.gd +++ b/modules/gdscript/tests/scripts/runtime/features/reset_unassigned_variables_in_loops.gd @@ -7,6 +7,7 @@ func test(): var b if true: var c + @warning_ignore("unassigned_variable") prints("Begin:", i, a, b, c) a = 1 b = 1 @@ -20,6 +21,7 @@ func test(): var b if true: var c + @warning_ignore("unassigned_variable") prints("Begin:", j, a, b, c) a = 1 b = 1 diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp index 5438f5020c..1fe402341b 100644 --- a/modules/openxr/openxr_api.cpp +++ b/modules/openxr/openxr_api.cpp @@ -1204,10 +1204,12 @@ bool OpenXRAPI::create_main_swapchains(Size2i p_size) { depth_views[i].subImage.imageRect.offset.y = 0; depth_views[i].subImage.imageRect.extent.width = main_swapchain_size.width; depth_views[i].subImage.imageRect.extent.height = main_swapchain_size.height; + // OpenXR spec says that: minDepth < maxDepth. depth_views[i].minDepth = 0.0; depth_views[i].maxDepth = 1.0; - depth_views[i].nearZ = 0.01; // Near and far Z will be set to the correct values in fill_projection_matrix - depth_views[i].farZ = 100.0; + // But we can reverse near and far for reverse-Z. + depth_views[i].nearZ = 100.0; // Near and far Z will be set to the correct values in fill_projection_matrix + depth_views[i].farZ = 0.01; } }; @@ -1802,8 +1804,9 @@ bool OpenXRAPI::get_view_projection(uint32_t p_view, double p_z_near, double p_z // if we're using depth views, make sure we update our near and far there... if (depth_views != nullptr) { for (uint32_t i = 0; i < view_count; i++) { - depth_views[i].nearZ = p_z_near; - depth_views[i].farZ = p_z_far; + // As we are using reverse-Z these need to be flipped. + depth_views[i].nearZ = p_z_far; + depth_views[i].farZ = p_z_near; } } diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp index aa51374637..b19760026a 100644 --- a/platform/android/export/export_plugin.cpp +++ b/platform/android/export/export_plugin.cpp @@ -2658,7 +2658,7 @@ String EditorExportPlatformAndroid::get_apk_expansion_fullpath(const Ref<EditorE Error EditorExportPlatformAndroid::save_apk_expansion_file(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path) { String fullpath = get_apk_expansion_fullpath(p_preset, p_path); - Error err = save_pack(false, p_preset, p_debug, fullpath); + Error err = save_pack(p_preset, p_debug, fullpath); return err; } @@ -3102,9 +3102,9 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP user_data.libs_directory = gradle_build_directory.path_join("libs"); user_data.debug = p_debug; if (p_flags & DEBUG_FLAG_DUMB_CLIENT) { - err = export_project_files(true, p_preset, p_debug, ignore_apk_file, &user_data, copy_gradle_so); + err = export_project_files(p_preset, p_debug, ignore_apk_file, &user_data, copy_gradle_so); } else { - err = export_project_files(true, p_preset, p_debug, rename_and_store_file_in_gradle_project, &user_data, copy_gradle_so); + err = export_project_files(p_preset, p_debug, rename_and_store_file_in_gradle_project, &user_data, copy_gradle_so); } if (err != OK) { add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("Could not export project files to gradle project.")); @@ -3489,7 +3489,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP APKExportData ed; ed.ep = &ep; ed.apk = unaligned_apk; - err = export_project_files(true, p_preset, p_debug, ignore_apk_file, &ed, save_apk_so); + err = export_project_files(p_preset, p_debug, ignore_apk_file, &ed, save_apk_so); } else { if (apk_expansion) { err = save_apk_expansion_file(p_preset, p_debug, p_path); @@ -3501,7 +3501,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP APKExportData ed; ed.ep = &ep; ed.apk = unaligned_apk; - err = export_project_files(true, p_preset, p_debug, save_apk_file, &ed, save_apk_so); + err = export_project_files(p_preset, p_debug, save_apk_file, &ed, save_apk_so); } } diff --git a/platform/ios/export/export_plugin.cpp b/platform/ios/export/export_plugin.cpp index dba1820ca7..33389129b7 100644 --- a/platform/ios/export/export_plugin.cpp +++ b/platform/ios/export/export_plugin.cpp @@ -1662,7 +1662,7 @@ Error EditorExportPlatformIOS::_export_project_helper(const Ref<EditorExportPres } String pack_path = binary_dir + ".pck"; Vector<SharedObject> libraries; - Error err = save_pack(true, p_preset, p_debug, pack_path, &libraries); + Error err = save_pack(p_preset, p_debug, pack_path, &libraries); if (err) { // Message is supplied by the subroutine method. return err; diff --git a/platform/macos/export/export_plugin.cpp b/platform/macos/export/export_plugin.cpp index e19a1e416c..d75def9b50 100644 --- a/platform/macos/export/export_plugin.cpp +++ b/platform/macos/export/export_plugin.cpp @@ -1774,7 +1774,7 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p String pack_path = tmp_app_path_name + "/Contents/Resources/" + pkg_name + ".pck"; Vector<SharedObject> shared_objects; - err = save_pack(true, p_preset, p_debug, pack_path, &shared_objects); + err = save_pack(p_preset, p_debug, pack_path, &shared_objects); bool lib_validation = p_preset->get("codesign/entitlements/disable_library_validation"); if (!shared_objects.is_empty() && sign_enabled && ad_hoc && !lib_validation) { diff --git a/platform/web/export/export_plugin.cpp b/platform/web/export/export_plugin.cpp index ec7f599507..41c969b5f4 100644 --- a/platform/web/export/export_plugin.cpp +++ b/platform/web/export/export_plugin.cpp @@ -479,7 +479,7 @@ Error EditorExportPlatformWeb::export_project(const Ref<EditorExportPreset> &p_p // Export pck and shared objects Vector<SharedObject> shared_objects; String pck_path = base_path + ".pck"; - Error error = save_pack(true, p_preset, p_debug, pck_path, &shared_objects); + Error error = save_pack(p_preset, p_debug, pck_path, &shared_objects); if (error != OK) { add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not write file: \"%s\"."), pck_path)); return error; diff --git a/scene/3d/mesh_instance_3d.cpp b/scene/3d/mesh_instance_3d.cpp index 8635240655..616fb18d53 100644 --- a/scene/3d/mesh_instance_3d.cpp +++ b/scene/3d/mesh_instance_3d.cpp @@ -204,6 +204,10 @@ Ref<Skin> MeshInstance3D::get_skin() const { return skin; } +Ref<SkinReference> MeshInstance3D::get_skin_reference() const { + return skin_ref; +} + void MeshInstance3D::set_skeleton_path(const NodePath &p_skeleton) { skeleton_path = p_skeleton; if (!is_inside_tree()) { @@ -518,6 +522,7 @@ void MeshInstance3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_skeleton_path"), &MeshInstance3D::get_skeleton_path); ClassDB::bind_method(D_METHOD("set_skin", "skin"), &MeshInstance3D::set_skin); ClassDB::bind_method(D_METHOD("get_skin"), &MeshInstance3D::get_skin); + ClassDB::bind_method(D_METHOD("get_skin_reference"), &MeshInstance3D::get_skin_reference); ClassDB::bind_method(D_METHOD("get_surface_override_material_count"), &MeshInstance3D::get_surface_override_material_count); ClassDB::bind_method(D_METHOD("set_surface_override_material", "surface", "material"), &MeshInstance3D::set_surface_override_material); diff --git a/scene/3d/mesh_instance_3d.h b/scene/3d/mesh_instance_3d.h index add6bfe15e..d6ae1291d3 100644 --- a/scene/3d/mesh_instance_3d.h +++ b/scene/3d/mesh_instance_3d.h @@ -75,6 +75,8 @@ public: void set_skeleton_path(const NodePath &p_skeleton); NodePath get_skeleton_path(); + Ref<SkinReference> get_skin_reference() const; + int get_blend_shape_count() const; int find_blend_shape_by_name(const StringName &p_name); float get_blend_shape_value(int p_blend_shape) const; diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index 1baf71dd07..f8bbedde09 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -835,10 +835,13 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o // Draw dropcap. int dc_lines = l.text_buf->get_dropcap_lines(); float h_off = l.text_buf->get_dropcap_size().x; - if (l.dc_ol_size > 0) { - l.text_buf->draw_dropcap_outline(ci, p_ofs + ((rtl) ? Vector2() : Vector2(l.offset.x, 0)), l.dc_ol_size, l.dc_ol_color); + bool skip_dc = (trim_chars && l.char_offset > visible_characters) || (trim_glyphs_ltr && (r_processed_glyphs >= visible_glyphs)) || (trim_glyphs_rtl && (r_processed_glyphs < total_glyphs - visible_glyphs)); + if (!skip_dc) { + if (l.dc_ol_size > 0) { + l.text_buf->draw_dropcap_outline(ci, p_ofs + ((rtl) ? Vector2() : Vector2(l.offset.x, 0)), l.dc_ol_size, l.dc_ol_color); + } + l.text_buf->draw_dropcap(ci, p_ofs + ((rtl) ? Vector2() : Vector2(l.offset.x, 0)), l.dc_color); } - l.text_buf->draw_dropcap(ci, p_ofs + ((rtl) ? Vector2() : Vector2(l.offset.x, 0)), l.dc_color); int line_count = 0; Size2 ctrl_size = get_size(); @@ -894,7 +897,8 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o } break; } - if (!prefix.is_empty() && line == 0) { + bool skip_prefix = (visible_chars_behavior == TextServer::VC_CHARS_BEFORE_SHAPING && l.char_offset == visible_characters) || (trim_chars && l.char_offset > visible_characters) || (trim_glyphs_ltr && (r_processed_glyphs >= visible_glyphs)) || (trim_glyphs_rtl && (r_processed_glyphs < total_glyphs - visible_glyphs)); + if (!prefix.is_empty() && line == 0 && !skip_prefix) { Ref<Font> font = theme_cache.normal_font; int font_size = theme_cache.normal_font_size; diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index 6e31671475..6bb745ac57 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -2582,8 +2582,6 @@ void TextEdit::_move_caret_to_line_end(bool p_select) { set_caret_column(row_end_col, i == 0, i); } - carets.write[i].last_fit_x = INT_MAX; - if (p_select) { _post_shift_selection(i); } diff --git a/scene/main/node.cpp b/scene/main/node.cpp index beb2583b61..5c5049759f 100644 --- a/scene/main/node.cpp +++ b/scene/main/node.cpp @@ -29,7 +29,6 @@ /**************************************************************************/ #include "node.h" -#include "node.compat.inc" #include "core/config/project_settings.h" #include "core/core_string_names.h" @@ -3006,7 +3005,7 @@ static void find_owned_by(Node *p_by, Node *p_node, List<Node *> *p_owned) { } } -void Node::replace_by(Node *p_node, bool p_keep_groups, bool p_keep_children) { +void Node::replace_by(Node *p_node, bool p_keep_groups) { ERR_THREAD_GUARD ERR_FAIL_NULL(p_node); ERR_FAIL_COND(p_node->data.parent); @@ -3027,13 +3026,13 @@ void Node::replace_by(Node *p_node, bool p_keep_groups, bool p_keep_children) { _replace_connections_target(p_node); if (data.owner) { - if (p_keep_children) { - for (int i = 0; i < get_child_count(); i++) { - find_owned_by(data.owner, get_child(i), &owned_by_owner); - } + for (int i = 0; i < get_child_count(); i++) { + find_owned_by(data.owner, get_child(i), &owned_by_owner); } + _clean_up_owner(); } + Node *parent = data.parent; int index_in_parent = get_index(false); @@ -3045,33 +3044,31 @@ void Node::replace_by(Node *p_node, bool p_keep_groups, bool p_keep_children) { emit_signal(SNAME("replacing_by"), p_node); - if (p_keep_children) { - while (get_child_count()) { - Node *child = get_child(0); - remove_child(child); - if (!child->is_owned_by_parent()) { - // add the custom children to the p_node - Node *child_owner = child->get_owner() == this ? p_node : child->get_owner(); - child->set_owner(nullptr); - p_node->add_child(child); - child->set_owner(child_owner); - } + while (get_child_count()) { + Node *child = get_child(0); + remove_child(child); + if (!child->is_owned_by_parent()) { + // add the custom children to the p_node + Node *child_owner = child->get_owner() == this ? p_node : child->get_owner(); + child->set_owner(nullptr); + p_node->add_child(child); + child->set_owner(child_owner); } + } - for (Node *E : owned) { - if (E->data.owner != p_node) { - E->set_owner(p_node); - } + p_node->set_owner(owner); + for (Node *E : owned) { + if (E->data.owner != p_node) { + E->set_owner(p_node); } + } - for (Node *E : owned_by_owner) { - if (E->data.owner != owner) { - E->set_owner(owner); - } + for (Node *E : owned_by_owner) { + if (E->data.owner != owner) { + E->set_owner(owner); } } - p_node->set_owner(owner); p_node->set_scene_file_path(get_scene_file_path()); } @@ -3598,7 +3595,7 @@ void Node::_bind_methods() { ClassDB::bind_method(D_METHOD("create_tween"), &Node::create_tween); ClassDB::bind_method(D_METHOD("duplicate", "flags"), &Node::duplicate, DEFVAL(DUPLICATE_USE_INSTANTIATION | DUPLICATE_SIGNALS | DUPLICATE_GROUPS | DUPLICATE_SCRIPTS)); - ClassDB::bind_method(D_METHOD("replace_by", "node", "keep_groups", "keep_children"), &Node::replace_by, DEFVAL(false), DEFVAL(true)); + ClassDB::bind_method(D_METHOD("replace_by", "node", "keep_groups"), &Node::replace_by, DEFVAL(false)); ClassDB::bind_method(D_METHOD("set_scene_instance_load_placeholder", "load_placeholder"), &Node::set_scene_instance_load_placeholder); ClassDB::bind_method(D_METHOD("get_scene_instance_load_placeholder"), &Node::get_scene_instance_load_placeholder); diff --git a/scene/main/node.h b/scene/main/node.h index 99def10338..f49eeec9cd 100644 --- a/scene/main/node.h +++ b/scene/main/node.h @@ -310,11 +310,6 @@ private: Variant _call_thread_safe_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error); protected: -#ifndef DISABLE_DEPRECATED - void _replace_by_bind_compat_89992(Node *p_node, bool p_keep_data = false); - static void _bind_compatibility_methods(); -#endif // DISABLE_DEPRECATED - void _block() { data.blocked++; } void _unblock() { data.blocked--; } @@ -634,7 +629,7 @@ public: return binds; } - void replace_by(Node *p_node, bool p_keep_groups = false, bool p_keep_children = true); + void replace_by(Node *p_node, bool p_keep_data = false); void set_process_mode(ProcessMode p_mode); ProcessMode get_process_mode() const; diff --git a/scene/resources/resource_format_text.cpp b/scene/resources/resource_format_text.cpp index ea8408a594..07868e7e49 100644 --- a/scene/resources/resource_format_text.cpp +++ b/scene/resources/resource_format_text.cpp @@ -39,7 +39,8 @@ // Version 2: changed names for Basis, AABB, Vectors, etc. // Version 3: new string ID for ext/subresources, breaks forward compat. -#define FORMAT_VERSION 3 +// Version 4: PackedByteArray is now stored as base64 encoded. +#define FORMAT_VERSION 4 #define BINARY_FORMAT_VERSION 4 @@ -273,8 +274,8 @@ Ref<PackedScene> ResourceLoaderText::_parse_node_tag(VariantParser::ResourcePars if (next_tag.fields.has("groups")) { Array groups = next_tag.fields["groups"]; - for (int i = 0; i < groups.size(); i++) { - packed_scene->get_state()->add_node_group(node_id, packed_scene->get_state()->add_name(groups[i])); + for (const Variant &group : groups) { + packed_scene->get_state()->add_node_group(node_id, packed_scene->get_state()->add_name(group)); } } @@ -352,8 +353,8 @@ Ref<PackedScene> ResourceLoaderText::_parse_node_tag(VariantParser::ResourcePars } Vector<int> bind_ints; - for (int i = 0; i < binds.size(); i++) { - bind_ints.push_back(packed_scene->get_state()->add_value(binds[i])); + for (const Variant &bind : binds) { + bind_ints.push_back(packed_scene->get_state()->add_value(bind)); } packed_scene->get_state()->add_connection( @@ -1952,10 +1953,8 @@ void ResourceFormatSaverTextInstance::_find_resources(const Variant &p_variant, case Variant::ARRAY: { Array varray = p_variant; _find_resources(varray.get_typed_script()); - int len = varray.size(); - for (int i = 0; i < len; i++) { - const Variant &v = varray.get(i); - _find_resources(v); + for (const Variant &var : varray) { + _find_resources(var); } } break; diff --git a/servers/rendering/dummy/storage/light_storage.h b/servers/rendering/dummy/storage/light_storage.h index a76305cdaa..0a9602b603 100644 --- a/servers/rendering/dummy/storage/light_storage.h +++ b/servers/rendering/dummy/storage/light_storage.h @@ -91,6 +91,7 @@ public: void light_instance_set_aabb(RID p_light_instance, const AABB &p_aabb) override {} void light_instance_set_shadow_transform(RID p_light_instance, const Projection &p_projection, const Transform3D &p_transform, float p_far, float p_split, int p_pass, float p_shadow_texel_size, float p_bias_scale = 1.0, float p_range_begin = 0, const Vector2 &p_uv_scale = Vector2()) override {} void light_instance_mark_visible(RID p_light_instance) override {} + virtual bool light_instance_is_shadow_visible_at_position(RID p_light_instance, const Vector3 &p_position) const override { return false; } /* PROBE API */ virtual RID reflection_probe_allocate() override { return RID(); } diff --git a/servers/rendering/renderer_rd/storage_rd/light_storage.h b/servers/rendering/renderer_rd/storage_rd/light_storage.h index b3d6bf5254..f152cc5dae 100644 --- a/servers/rendering/renderer_rd/storage_rd/light_storage.h +++ b/servers/rendering/renderer_rd/storage_rd/light_storage.h @@ -590,6 +590,29 @@ public: virtual void light_instance_set_shadow_transform(RID p_light_instance, const Projection &p_projection, const Transform3D &p_transform, float p_far, float p_split, int p_pass, float p_shadow_texel_size, float p_bias_scale = 1.0, float p_range_begin = 0, const Vector2 &p_uv_scale = Vector2()) override; virtual void light_instance_mark_visible(RID p_light_instance) override; + virtual bool light_instance_is_shadow_visible_at_position(RID p_light_instance, const Vector3 &p_position) const override { + const LightInstance *light_instance = light_instance_owner.get_or_null(p_light_instance); + ERR_FAIL_NULL_V(light_instance, false); + const Light *light = light_owner.get_or_null(light_instance->light); + ERR_FAIL_NULL_V(light, false); + + if (!light->shadow) { + return false; + } + + if (!light->distance_fade) { + return true; + } + + real_t distance = p_position.distance_to(light_instance->transform.origin); + + if (distance > light->distance_fade_shadow + light->distance_fade_length) { + return false; + } + + return true; + } + _FORCE_INLINE_ RID light_instance_get_base_light(RID p_light_instance) { LightInstance *li = light_instance_owner.get_or_null(p_light_instance); return li->light; diff --git a/servers/rendering/renderer_rd/storage_rd/render_scene_buffers_rd.cpp b/servers/rendering/renderer_rd/storage_rd/render_scene_buffers_rd.cpp index c5d74d395f..b7934cb3de 100644 --- a/servers/rendering/renderer_rd/storage_rd/render_scene_buffers_rd.cpp +++ b/servers/rendering/renderer_rd/storage_rd/render_scene_buffers_rd.cpp @@ -130,9 +130,10 @@ void RenderSceneBuffersRD::cleanup() { named_textures.clear(); // Clear weight_buffer / blur textures. - for (const WeightBuffers &weight_buffer : weight_buffers) { + for (WeightBuffers &weight_buffer : weight_buffers) { if (weight_buffer.weight.is_valid()) { RD::get_singleton()->free(weight_buffer.weight); + weight_buffer.weight = RID(); } } } diff --git a/servers/rendering/renderer_scene_cull.cpp b/servers/rendering/renderer_scene_cull.cpp index b33de9d6f4..96c0479ac3 100644 --- a/servers/rendering/renderer_scene_cull.cpp +++ b/servers/rendering/renderer_scene_cull.cpp @@ -1029,7 +1029,6 @@ inline bool is_geometry_instance(RenderingServer::InstanceType p_type) { void RendererSceneCull::instance_set_custom_aabb(RID p_instance, AABB p_aabb) { Instance *instance = instance_owner.get_or_null(p_instance); ERR_FAIL_NULL(instance); - ERR_FAIL_COND(!is_geometry_instance(instance->base_type)); if (p_aabb != AABB()) { // Set custom AABB @@ -3029,6 +3028,7 @@ void RendererSceneCull::_render_scene(const RendererSceneRender::CameraData *p_c light_culler->prepare_camera(p_camera_data->main_transform, p_camera_data->main_projection); Scenario *scenario = scenario_owner.get_or_null(p_scenario); + Vector3 camera_position = p_camera_data->main_transform.origin; ERR_FAIL_COND(p_render_buffers.is_null()); @@ -3038,7 +3038,7 @@ void RendererSceneCull::_render_scene(const RendererSceneRender::CameraData *p_c if (p_reflection_probe.is_null()) { //no rendering code here, this is only to set up what needs to be done, request regions, etc. - scene_render->sdfgi_update(p_render_buffers, p_environment, p_camera_data->main_transform.origin); //update conditions for SDFGI (whether its used or not) + scene_render->sdfgi_update(p_render_buffers, p_environment, camera_position); //update conditions for SDFGI (whether its used or not) } RENDER_TIMESTAMP("Update Visibility Dependencies"); @@ -3051,7 +3051,7 @@ void RendererSceneCull::_render_scene(const RendererSceneRender::CameraData *p_c VisibilityCullData visibility_cull_data; visibility_cull_data.scenario = scenario; visibility_cull_data.viewport_mask = scenario->viewport_visibility_masks[p_viewport]; - visibility_cull_data.camera_position = p_camera_data->main_transform.origin; + visibility_cull_data.camera_position = camera_position; for (int i = scenario->instance_visibility.get_bin_count() - 1; i > 0; i--) { // We skip bin 0 visibility_cull_data.cull_offset = scenario->instance_visibility.get_bin_start(i); @@ -3220,16 +3220,20 @@ void RendererSceneCull::_render_scene(const RendererSceneRender::CameraData *p_c } } - // Positional Shadowss + // Positional Shadows for (uint32_t i = 0; i < (uint32_t)scene_cull_result.lights.size(); i++) { Instance *ins = scene_cull_result.lights[i]; - if (!p_shadow_atlas.is_valid() || !RSG::light_storage->light_has_shadow(ins->base)) { + if (!p_shadow_atlas.is_valid()) { continue; } InstanceLightData *light = static_cast<InstanceLightData *>(ins->base_data); + if (!RSG::light_storage->light_instance_is_shadow_visible_at_position(light->instance, camera_position)) { + continue; + } + float coverage = 0.f; { //compute coverage diff --git a/servers/rendering/shader_language.cpp b/servers/rendering/shader_language.cpp index fef1a205d6..8fbad346a4 100644 --- a/servers/rendering/shader_language.cpp +++ b/servers/rendering/shader_language.cpp @@ -8361,7 +8361,7 @@ Error ShaderLanguage::_parse_shader(const HashMap<StringName, FunctionInfo> &p_f } } #endif // DEBUG_ENABLED - if (String(shader_type_identifier) != "spatial") { + if (shader_type_identifier != StringName() && String(shader_type_identifier) != "spatial") { _set_error(vformat(RTR("Uniform instances are not yet implemented for '%s' shaders."), shader_type_identifier)); return ERR_PARSE_ERROR; } @@ -8848,7 +8848,7 @@ Error ShaderLanguage::_parse_shader(const HashMap<StringName, FunctionInfo> &p_f _set_error(RTR("'hint_normal_roughness_texture' is only available when using the Forward+ backend.")); return ERR_PARSE_ERROR; } - if (String(shader_type_identifier) != "spatial") { + if (shader_type_identifier != StringName() && String(shader_type_identifier) != "spatial") { _set_error(vformat(RTR("'hint_normal_roughness_texture' is not supported in '%s' shaders."), shader_type_identifier)); return ERR_PARSE_ERROR; } @@ -8857,7 +8857,7 @@ Error ShaderLanguage::_parse_shader(const HashMap<StringName, FunctionInfo> &p_f new_hint = ShaderNode::Uniform::HINT_DEPTH_TEXTURE; --texture_uniforms; --texture_binding; - if (String(shader_type_identifier) != "spatial") { + if (shader_type_identifier != StringName() && String(shader_type_identifier) != "spatial") { _set_error(vformat(RTR("'hint_depth_texture' is not supported in '%s' shaders."), shader_type_identifier)); return ERR_PARSE_ERROR; } diff --git a/servers/rendering/storage/light_storage.h b/servers/rendering/storage/light_storage.h index d439598f3d..6a0adfa596 100644 --- a/servers/rendering/storage/light_storage.h +++ b/servers/rendering/storage/light_storage.h @@ -98,6 +98,7 @@ public: virtual bool light_instances_can_render_shadow_cube() const { return true; } + virtual bool light_instance_is_shadow_visible_at_position(RID p_light, const Vector3 &p_position) const = 0; /* PROBE API */ diff --git a/scene/main/node.compat.inc b/tests/core/io/test_ip.h index 69ece1a40d..7b5583faa0 100644 --- a/scene/main/node.compat.inc +++ b/tests/core/io/test_ip.h @@ -1,5 +1,5 @@ /**************************************************************************/ -/* node.compat.inc */ +/* test_ip.h */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,14 +28,24 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ -#ifndef DISABLE_DEPRECATED +#ifndef TEST_IP_H +#define TEST_IP_H -void Node::_replace_by_bind_compat_89992(Node *p_node, bool p_keep_data) { - replace_by(p_node, p_keep_data, true); -} +#include "core/io/ip.h" + +#include "tests/test_macros.h" -void Node::_bind_compatibility_methods() { - ClassDB::bind_compatibility_method(D_METHOD("replace_by", "node", "keep_groups"), &Node::_replace_by_bind_compat_89992, DEFVAL(false)); +namespace TestIP { + +TEST_CASE("[IP] resolve_hostname") { + for (int x = 0; x < 1000; x++) { + IPAddress IPV4 = IP::get_singleton()->resolve_hostname("localhost", IP::TYPE_IPV4); + CHECK("127.0.0.1" == String(IPV4)); + IPAddress IPV6 = IP::get_singleton()->resolve_hostname("localhost", IP::TYPE_IPV6); + CHECK("0:0:0:0:0:0:0:1" == String(IPV6)); + } } -#endif +} // namespace TestIP + +#endif // TEST_IP_H diff --git a/tests/core/templates/test_oa_hash_map.h b/tests/core/templates/test_oa_hash_map.h new file mode 100644 index 0000000000..6e80b52054 --- /dev/null +++ b/tests/core/templates/test_oa_hash_map.h @@ -0,0 +1,227 @@ +/**************************************************************************/ +/* test_oa_hash_map.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef TEST_OA_HASH_MAP_H +#define TEST_OA_HASH_MAP_H + +#include "core/templates/oa_hash_map.h" +#include "scene/resources/texture.h" + +#include "tests/test_macros.h" + +namespace TestOAHashMap { + +TEST_CASE("[OAHashMap] Insert element") { + OAHashMap<int, int> map; + map.insert(42, 84); + int data = 0; + bool lookup_res = map.lookup(42, data); + int value = *map.lookup_ptr(42); + CHECK(lookup_res); + CHECK(value == 84); + CHECK(data == 84); +} + +TEST_CASE("[OAHashMap] Set element") { + OAHashMap<int, int> map; + map.set(42, 84); + int data = 0; + bool lookup_res = map.lookup(42, data); + int value = *map.lookup_ptr(42); + CHECK(lookup_res); + CHECK(value == 84); + CHECK(data == 84); +} + +TEST_CASE("[OAHashMap] Overwrite element") { + OAHashMap<int, int> map; + map.set(42, 84); + map.set(42, 1234); + int result = *map.lookup_ptr(42); + CHECK(result == 1234); +} + +TEST_CASE("[OAHashMap] Remove element") { + OAHashMap<int, int> map; + map.insert(42, 84); + map.remove(42); + CHECK(!map.has(42)); +} + +TEST_CASE("[OAHashMap] Get Num_Elements") { + OAHashMap<int, int> map; + map.set(42, 84); + map.set(123, 84); + map.set(123, 84); + map.set(0, 84); + map.set(123485, 84); + + CHECK(map.get_num_elements() == 4); +} + +TEST_CASE("[OAHashMap] Iteration") { + OAHashMap<int, int> map; + map.insert(42, 84); + map.insert(123, 12385); + map.insert(0, 12934); + map.insert(123485, 1238888); + map.set(123, 111111); + + Vector<Pair<int, int>> expected; + expected.push_back(Pair<int, int>(42, 84)); + expected.push_back(Pair<int, int>(123, 111111)); + expected.push_back(Pair<int, int>(0, 12934)); + expected.push_back(Pair<int, int>(123485, 1238888)); + + int idx = 0; + for (OAHashMap<int, int>::Iterator it = map.iter(); it.valid; it = map.next_iter(it)) { + int64_t result = expected.find(Pair<int, int>(*it.key, *it.value)); + CHECK(result >= 0); + idx++; + } +} + +TEST_CASE("[OAHashMap] Insert, iterate, remove many strings") { + uint64_t pre_mem = Memory::get_mem_usage(); + { + const int elem_max = 40; + OAHashMap<String, int> map; + for (int i = 0; i < elem_max; i++) { + map.insert(itos(i), i); + } + + Vector<String> elems_still_valid; + + for (int i = 0; i < elem_max; i++) { + if ((i % 5) == 0) { + map.remove(itos(i)); + } else { + elems_still_valid.push_back(itos(i)); + } + } + + CHECK(elems_still_valid.size() == map.get_num_elements()); + + for (int i = 0; i < elems_still_valid.size(); i++) { + CHECK(map.has(elems_still_valid[i])); + } + } + + CHECK(Memory::get_mem_usage() == pre_mem); +} + +TEST_CASE("[OAHashMap] Clear") { + OAHashMap<int, int> map; + map.insert(42, 84); + map.insert(0, 1234); + map.clear(); + CHECK(!map.has(42)); + CHECK(!map.has(0)); + CHECK(map.is_empty()); +} + +TEST_CASE("[OAHashMap] Copy constructor") { + uint64_t pre_mem = Memory::get_mem_usage(); + { + OAHashMap<int, int> map0; + const uint32_t count = 5; + for (uint32_t i = 0; i < count; i++) { + map0.insert(i, i); + } + OAHashMap<int, int> map1(map0); + CHECK(map0.get_num_elements() == map1.get_num_elements()); + CHECK(map0.get_capacity() == map1.get_capacity()); + CHECK(*map0.lookup_ptr(0) == *map1.lookup_ptr(0)); + } + CHECK(Memory::get_mem_usage() == pre_mem); +} + +TEST_CASE("[OAHashMap] Operator =") { + uint64_t pre_mem = Memory::get_mem_usage(); + { + OAHashMap<int, int> map0; + OAHashMap<int, int> map1; + const uint32_t count = 5; + map1.insert(1234, 1234); + for (uint32_t i = 0; i < count; i++) { + map0.insert(i, i); + } + map1 = map0; + CHECK(map0.get_num_elements() == map1.get_num_elements()); + CHECK(map0.get_capacity() == map1.get_capacity()); + CHECK(*map0.lookup_ptr(0) == *map1.lookup_ptr(0)); + } + CHECK(Memory::get_mem_usage() == pre_mem); +} + +TEST_CASE("[OAHashMap] Non-trivial types") { + uint64_t pre_mem = Memory::get_mem_usage(); + { + OAHashMap<String, Ref<Texture2D>> map1; + const uint32_t count = 10; + for (uint32_t i = 0; i < count; i++) { + String string = "qwerty"; + string += itos(i); + Ref<Texture2D> ref_texture_2d; + + map1.set(string, ref_texture_2d); + Ref<Texture2D> map_vec = *map1.lookup_ptr(string); + CHECK(map_vec == ref_texture_2d); + } + OAHashMap<String, Ref<Texture2D>> map1copy(map1); + CHECK(map1copy.has(String("qwerty0"))); + map1copy = map1; + CHECK(map1copy.has(String("qwerty2"))); + + OAHashMap<int64_t, Vector4 *> map2; + + for (uint32_t i = 0; i < count; i++) { + Vector4 *vec = memnew(Vector4); + vec->x = 10; + vec->y = 12; + vec->z = 151; + vec->w = -13; + map2.set(i, vec); + Vector4 *p = nullptr; + map2.lookup(i, p); + CHECK(*p == *vec); + } + + OAHashMap<int64_t, Vector4 *> map3(map2); + for (OAHashMap<int64_t, Vector4 *>::Iterator it = map2.iter(); it.valid; it = map2.next_iter(it)) { + memdelete(*(it.value)); + } + } + CHECK(Memory::get_mem_usage() == pre_mem); +} + +} // namespace TestOAHashMap + +#endif // TEST_OA_HASH_MAP_H diff --git a/tests/core/variant/test_array.h b/tests/core/variant/test_array.h index ea61ae2a02..287345e831 100644 --- a/tests/core/variant/test_array.h +++ b/tests/core/variant/test_array.h @@ -545,6 +545,54 @@ TEST_CASE("[Array] Recursive self comparison") { a2.clear(); } +TEST_CASE("[Array] Iteration") { + Array a1 = build_array(1, 2, 3); + Array a2 = build_array(1, 2, 3); + + int idx = 0; + for (Variant &E : a1) { + CHECK_EQ(int(a2[idx]), int(E)); + idx++; + } + + idx = 0; + + for (const Variant &E : (const Array &)a1) { + CHECK_EQ(int(a2[idx]), int(E)); + idx++; + } + + a1.clear(); +} + +TEST_CASE("[Array] Iteration and modification") { + Array a1 = build_array(1, 2, 3); + Array a2 = build_array(2, 3, 4); + Array a3 = build_array(1, 2, 3); + Array a4 = build_array(1, 2, 3); + a3.make_read_only(); + + int idx = 0; + for (Variant &E : a1) { + E = a2[idx]; + idx++; + } + + CHECK_EQ(a1, a2); + + // Ensure read-only is respected. + idx = 0; + for (Variant &E : a3) { + E = a2[idx]; + } + + CHECK_EQ(a3, a4); + + a1.clear(); + a2.clear(); + a4.clear(); +} + } // namespace TestArray #endif // TEST_ARRAY_H diff --git a/tests/scene/test_camera_2d.h b/tests/scene/test_camera_2d.h new file mode 100644 index 0000000000..f03a4aed53 --- /dev/null +++ b/tests/scene/test_camera_2d.h @@ -0,0 +1,318 @@ +/**************************************************************************/ +/* test_camera_2d.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef TEST_CAMERA_2D_H +#define TEST_CAMERA_2D_H + +#include "scene/2d/camera_2d.h" +#include "scene/main/viewport.h" +#include "scene/main/window.h" +#include "tests/test_macros.h" + +namespace TestCamera2D { + +TEST_CASE("[SceneTree][Camera2D] Getters and setters") { + Camera2D *test_camera = memnew(Camera2D); + + SUBCASE("AnchorMode") { + test_camera->set_anchor_mode(Camera2D::AnchorMode::ANCHOR_MODE_FIXED_TOP_LEFT); + CHECK(test_camera->get_anchor_mode() == Camera2D::AnchorMode::ANCHOR_MODE_FIXED_TOP_LEFT); + test_camera->set_anchor_mode(Camera2D::AnchorMode::ANCHOR_MODE_DRAG_CENTER); + CHECK(test_camera->get_anchor_mode() == Camera2D::AnchorMode::ANCHOR_MODE_DRAG_CENTER); + } + + SUBCASE("ProcessCallback") { + test_camera->set_process_callback(Camera2D::Camera2DProcessCallback::CAMERA2D_PROCESS_PHYSICS); + CHECK(test_camera->get_process_callback() == Camera2D::Camera2DProcessCallback::CAMERA2D_PROCESS_PHYSICS); + test_camera->set_process_callback(Camera2D::Camera2DProcessCallback::CAMERA2D_PROCESS_IDLE); + CHECK(test_camera->get_process_callback() == Camera2D::Camera2DProcessCallback::CAMERA2D_PROCESS_IDLE); + } + + SUBCASE("Drag") { + constexpr float drag_left_margin = 0.8f; + constexpr float drag_top_margin = 0.8f; + constexpr float drag_right_margin = 0.8f; + constexpr float drag_bottom_margin = 0.8f; + constexpr float drag_horizontal_offset1 = 0.5f; + constexpr float drag_horizontal_offset2 = -0.5f; + constexpr float drag_vertical_offset1 = 0.5f; + constexpr float drag_vertical_offset2 = -0.5f; + test_camera->set_drag_margin(SIDE_LEFT, drag_left_margin); + CHECK(test_camera->get_drag_margin(SIDE_LEFT) == drag_left_margin); + test_camera->set_drag_margin(SIDE_TOP, drag_top_margin); + CHECK(test_camera->get_drag_margin(SIDE_TOP) == drag_top_margin); + test_camera->set_drag_margin(SIDE_RIGHT, drag_right_margin); + CHECK(test_camera->get_drag_margin(SIDE_RIGHT) == drag_right_margin); + test_camera->set_drag_margin(SIDE_BOTTOM, drag_bottom_margin); + CHECK(test_camera->get_drag_margin(SIDE_BOTTOM) == drag_bottom_margin); + test_camera->set_drag_horizontal_enabled(true); + CHECK(test_camera->is_drag_horizontal_enabled()); + test_camera->set_drag_horizontal_enabled(false); + CHECK_FALSE(test_camera->is_drag_horizontal_enabled()); + test_camera->set_drag_horizontal_offset(drag_horizontal_offset1); + CHECK(test_camera->get_drag_horizontal_offset() == drag_horizontal_offset1); + test_camera->set_drag_horizontal_offset(drag_horizontal_offset2); + CHECK(test_camera->get_drag_horizontal_offset() == drag_horizontal_offset2); + test_camera->set_drag_vertical_enabled(true); + CHECK(test_camera->is_drag_vertical_enabled()); + test_camera->set_drag_vertical_enabled(false); + CHECK_FALSE(test_camera->is_drag_vertical_enabled()); + test_camera->set_drag_vertical_offset(drag_vertical_offset1); + CHECK(test_camera->get_drag_vertical_offset() == drag_vertical_offset1); + test_camera->set_drag_vertical_offset(drag_vertical_offset2); + CHECK(test_camera->get_drag_vertical_offset() == drag_vertical_offset2); + } + + SUBCASE("Drawing") { + test_camera->set_margin_drawing_enabled(true); + CHECK(test_camera->is_margin_drawing_enabled()); + test_camera->set_margin_drawing_enabled(false); + CHECK_FALSE(test_camera->is_margin_drawing_enabled()); + test_camera->set_limit_drawing_enabled(true); + CHECK(test_camera->is_limit_drawing_enabled()); + test_camera->set_limit_drawing_enabled(false); + CHECK_FALSE(test_camera->is_limit_drawing_enabled()); + test_camera->set_screen_drawing_enabled(true); + CHECK(test_camera->is_screen_drawing_enabled()); + test_camera->set_screen_drawing_enabled(false); + CHECK_FALSE(test_camera->is_screen_drawing_enabled()); + } + + SUBCASE("Enabled") { + test_camera->set_enabled(true); + CHECK(test_camera->is_enabled()); + test_camera->set_enabled(false); + CHECK_FALSE(test_camera->is_enabled()); + } + + SUBCASE("Rotation") { + constexpr float rotation_smoothing_speed = 20.0f; + test_camera->set_ignore_rotation(true); + CHECK(test_camera->is_ignoring_rotation()); + test_camera->set_ignore_rotation(false); + CHECK_FALSE(test_camera->is_ignoring_rotation()); + test_camera->set_rotation_smoothing_enabled(true); + CHECK(test_camera->is_rotation_smoothing_enabled()); + test_camera->set_rotation_smoothing_speed(rotation_smoothing_speed); + CHECK(test_camera->get_rotation_smoothing_speed() == rotation_smoothing_speed); + } + + SUBCASE("Zoom") { + const Vector2 zoom = Vector2(4, 4); + test_camera->set_zoom(zoom); + CHECK(test_camera->get_zoom() == zoom); + } + + SUBCASE("Offset") { + const Vector2 offset = Vector2(100, 100); + test_camera->set_offset(offset); + CHECK(test_camera->get_offset() == offset); + } + + SUBCASE("Limit") { + constexpr int limit_left = 100; + constexpr int limit_top = 100; + constexpr int limit_right = 100; + constexpr int limit_bottom = 100; + test_camera->set_limit_smoothing_enabled(true); + CHECK(test_camera->is_limit_smoothing_enabled()); + test_camera->set_limit_smoothing_enabled(false); + CHECK_FALSE(test_camera->is_limit_smoothing_enabled()); + test_camera->set_limit(SIDE_LEFT, limit_left); + CHECK(test_camera->get_limit(SIDE_LEFT) == limit_left); + test_camera->set_limit(SIDE_TOP, limit_top); + CHECK(test_camera->get_limit(SIDE_TOP) == limit_top); + test_camera->set_limit(SIDE_RIGHT, limit_right); + CHECK(test_camera->get_limit(SIDE_RIGHT) == limit_right); + test_camera->set_limit(SIDE_BOTTOM, limit_bottom); + CHECK(test_camera->get_limit(SIDE_BOTTOM) == limit_bottom); + } + + SUBCASE("Position") { + constexpr float smoothing_speed = 20.0f; + test_camera->set_position_smoothing_enabled(true); + CHECK(test_camera->is_position_smoothing_enabled()); + test_camera->set_position_smoothing_speed(smoothing_speed); + CHECK(test_camera->get_position_smoothing_speed() == smoothing_speed); + } + + memdelete(test_camera); +} + +TEST_CASE("[SceneTree][Camera2D] Camera positioning") { + SubViewport *mock_viewport = memnew(SubViewport); + Camera2D *test_camera = memnew(Camera2D); + + mock_viewport->set_size(Vector2(400, 200)); + SceneTree::get_singleton()->get_root()->add_child(mock_viewport); + mock_viewport->add_child(test_camera); + + SUBCASE("Anchor mode") { + test_camera->set_anchor_mode(Camera2D::ANCHOR_MODE_DRAG_CENTER); + CHECK(test_camera->get_camera_screen_center().is_equal_approx(Vector2(0, 0))); + CHECK(test_camera->get_camera_position().is_equal_approx(Vector2(0, 0))); + + test_camera->set_anchor_mode(Camera2D::ANCHOR_MODE_FIXED_TOP_LEFT); + CHECK(test_camera->get_camera_screen_center().is_equal_approx(Vector2(200, 100))); + CHECK(test_camera->get_camera_position().is_equal_approx(Vector2(0, 0))); + } + + SUBCASE("Offset") { + test_camera->set_offset(Vector2(100, 100)); + CHECK(test_camera->get_camera_screen_center().is_equal_approx(Vector2(100, 100))); + CHECK(test_camera->get_camera_position().is_equal_approx(Vector2(0, 0))); + + test_camera->set_offset(Vector2(-100, 300)); + CHECK(test_camera->get_camera_screen_center().is_equal_approx(Vector2(-100, 300))); + CHECK(test_camera->get_camera_position().is_equal_approx(Vector2(0, 0))); + + test_camera->set_offset(Vector2(0, 0)); + CHECK(test_camera->get_camera_screen_center().is_equal_approx(Vector2(0, 0))); + CHECK(test_camera->get_camera_position().is_equal_approx(Vector2(0, 0))); + } + + SUBCASE("Limits") { + test_camera->set_limit(SIDE_LEFT, 100); + test_camera->set_limit(SIDE_TOP, 50); + + CHECK(test_camera->get_camera_screen_center().is_equal_approx(Vector2(300, 150))); + CHECK(test_camera->get_camera_position().is_equal_approx(Vector2(0, 0))); + + test_camera->set_limit(SIDE_LEFT, 0); + test_camera->set_limit(SIDE_TOP, 0); + + CHECK(test_camera->get_camera_screen_center().is_equal_approx(Vector2(200, 100))); + CHECK(test_camera->get_camera_position().is_equal_approx(Vector2(0, 0))); + } + + SUBCASE("Drag") { + CHECK(test_camera->get_camera_screen_center().is_equal_approx(Vector2(0, 0))); + + // horizontal + test_camera->set_drag_horizontal_enabled(true); + test_camera->set_drag_margin(SIDE_RIGHT, 0.5); + + test_camera->set_position(Vector2(100, 100)); + test_camera->force_update_scroll(); + CHECK(test_camera->get_camera_position().is_equal_approx(Vector2(0, 100))); + CHECK(test_camera->get_camera_screen_center().is_equal_approx(Vector2(0, 100))); + test_camera->set_position(Vector2(101, 101)); + test_camera->force_update_scroll(); + CHECK(test_camera->get_camera_position().is_equal_approx(Vector2(1, 101))); + CHECK(test_camera->get_camera_screen_center().is_equal_approx(Vector2(1, 101))); + + // test align + test_camera->set_position(Vector2(0, 0)); + test_camera->align(); + CHECK(test_camera->get_camera_position().is_equal_approx(Vector2(0, 0))); + CHECK(test_camera->get_camera_screen_center().is_equal_approx(Vector2(0, 0))); + + // vertical + test_camera->set_drag_vertical_enabled(true); + test_camera->set_drag_horizontal_enabled(false); + test_camera->set_drag_margin(SIDE_TOP, 0.3); + + test_camera->set_position(Vector2(200, -20)); + test_camera->force_update_scroll(); + CHECK(test_camera->get_camera_position().is_equal_approx(Vector2(200, 0))); + CHECK(test_camera->get_camera_screen_center().is_equal_approx(Vector2(200, 0))); + test_camera->set_position(Vector2(250, -55)); + test_camera->force_update_scroll(); + CHECK(test_camera->get_camera_position().is_equal_approx(Vector2(250, -25))); + CHECK(test_camera->get_camera_screen_center().is_equal_approx(Vector2(250, -25))); + } + + memdelete(test_camera); + memdelete(mock_viewport); +} + +TEST_CASE("[SceneTree][Camera2D] Transforms") { + SubViewport *mock_viewport = memnew(SubViewport); + Camera2D *test_camera = memnew(Camera2D); + + mock_viewport->set_size(Vector2(400, 200)); + SceneTree::get_singleton()->get_root()->add_child(mock_viewport); + mock_viewport->add_child(test_camera); + + SUBCASE("Default camera") { + Transform2D xform = mock_viewport->get_canvas_transform(); + // x,y are basis vectors, origin = screen center + Transform2D test_xform = Transform2D(Vector2(1, 0), Vector2(0, 1), Vector2(200, 100)); + CHECK(xform.is_equal_approx(test_xform)); + } + + SUBCASE("Zoom") { + test_camera->set_zoom(Vector2(0.5, 2)); + Transform2D xform = mock_viewport->get_canvas_transform(); + Transform2D test_xform = Transform2D(Vector2(0.5, 0), Vector2(0, 2), Vector2(200, 100)); + CHECK(xform.is_equal_approx(test_xform)); + + test_camera->set_zoom(Vector2(10, 10)); + xform = mock_viewport->get_canvas_transform(); + test_xform = Transform2D(Vector2(10, 0), Vector2(0, 10), Vector2(200, 100)); + CHECK(xform.is_equal_approx(test_xform)); + + test_camera->set_zoom(Vector2(1, 1)); + xform = mock_viewport->get_canvas_transform(); + test_xform = Transform2D(Vector2(1, 0), Vector2(0, 1), Vector2(200, 100)); + CHECK(xform.is_equal_approx(test_xform)); + } + + SUBCASE("Rotation") { + test_camera->set_rotation(Math_PI / 2); + Transform2D xform = mock_viewport->get_canvas_transform(); + Transform2D test_xform = Transform2D(Vector2(1, 0), Vector2(0, 1), Vector2(200, 100)); + CHECK(xform.is_equal_approx(test_xform)); + + test_camera->set_ignore_rotation(false); + xform = mock_viewport->get_canvas_transform(); + test_xform = Transform2D(Vector2(0, -1), Vector2(1, 0), Vector2(200, 100)); + CHECK(xform.is_equal_approx(test_xform)); + + test_camera->set_rotation(-1 * Math_PI); + test_camera->force_update_scroll(); + xform = mock_viewport->get_canvas_transform(); + test_xform = Transform2D(Vector2(-1, 0), Vector2(0, -1), Vector2(200, 100)); + CHECK(xform.is_equal_approx(test_xform)); + + test_camera->set_rotation(0); + test_camera->force_update_scroll(); + xform = mock_viewport->get_canvas_transform(); + test_xform = Transform2D(Vector2(1, 0), Vector2(0, 1), Vector2(200, 100)); + CHECK(xform.is_equal_approx(test_xform)); + } + + memdelete(test_camera); + memdelete(mock_viewport); +} + +} // namespace TestCamera2D + +#endif // TEST_CAMERA_2D_H diff --git a/tests/test_main.cpp b/tests/test_main.cpp index 24eb84127b..bb6837c965 100644 --- a/tests/test_main.cpp +++ b/tests/test_main.cpp @@ -44,6 +44,7 @@ #include "tests/core/io/test_file_access.h" #include "tests/core/io/test_http_client.h" #include "tests/core/io/test_image.h" +#include "tests/core/io/test_ip.h" #include "tests/core/io/test_json.h" #include "tests/core/io/test_marshalls.h" #include "tests/core/io/test_pck_packer.h" @@ -85,6 +86,7 @@ #include "tests/core/templates/test_list.h" #include "tests/core/templates/test_local_vector.h" #include "tests/core/templates/test_lru.h" +#include "tests/core/templates/test_oa_hash_map.h" #include "tests/core/templates/test_paged_array.h" #include "tests/core/templates/test_rid.h" #include "tests/core/templates/test_vector.h" @@ -101,6 +103,7 @@ #include "tests/scene/test_arraymesh.h" #include "tests/scene/test_audio_stream_wav.h" #include "tests/scene/test_bit_map.h" +#include "tests/scene/test_camera_2d.h" #include "tests/scene/test_code_edit.h" #include "tests/scene/test_color_picker.h" #include "tests/scene/test_control.h" |