diff options
Diffstat (limited to 'core')
31 files changed, 914 insertions, 548 deletions
diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp index 6e897e4d2d..d9cf65b63c 100644 --- a/core/config/project_settings.cpp +++ b/core/config/project_settings.cpp @@ -348,7 +348,6 @@ bool ProjectSettings::_get(const StringName &p_name, Variant &r_ret) const { _THREAD_SAFE_METHOD_ if (!props.has(p_name)) { - WARN_PRINT("Property not found: " + String(p_name)); return false; } r_ret = props[p_name].variant; @@ -1185,22 +1184,16 @@ bool ProjectSettings::is_project_loaded() const { } bool ProjectSettings::_property_can_revert(const StringName &p_name) const { - if (!props.has(p_name)) { - return false; - } - - return props[p_name].initial != props[p_name].variant; + return props.has(p_name); } bool ProjectSettings::_property_get_revert(const StringName &p_name, Variant &r_property) const { - if (!props.has(p_name)) { - return false; + const RBMap<StringName, ProjectSettings::VariantContainer>::Element *value = props.find(p_name); + if (value) { + r_property = value->value().initial.duplicate(); + return true; } - - // Duplicate so that if value is array or dictionary, changing the setting will not change the stored initial value. - r_property = props[p_name].initial.duplicate(); - - return true; + return false; } void ProjectSettings::set_setting(const String &p_setting, const Variant &p_value) { @@ -1415,7 +1408,7 @@ void ProjectSettings::_add_builtin_input_map() { } Dictionary action; - action["deadzone"] = Variant(0.5f); + action["deadzone"] = Variant(0.2f); action["events"] = events; String action_name = "input/" + E.key; diff --git a/core/extension/gdextension_interface.cpp b/core/extension/gdextension_interface.cpp index ddf90f6130..66b0161160 100644 --- a/core/extension/gdextension_interface.cpp +++ b/core/extension/gdextension_interface.cpp @@ -507,6 +507,14 @@ static GDExtensionBool gdextension_variant_has_key(GDExtensionConstVariantPtr p_ return ret; } +static GDObjectInstanceID gdextension_variant_get_object_instance_id(GDExtensionConstVariantPtr p_self) { + const Variant *self = (const Variant *)p_self; + if (likely(self->get_type() == Variant::OBJECT)) { + return self->operator ObjectID(); + } + return 0; +} + static void gdextension_variant_get_type_name(GDExtensionVariantType p_type, GDExtensionUninitializedVariantPtr r_ret) { String name = Variant::get_type_name((Variant::Type)p_type); memnew_placement(r_ret, String(name)); @@ -1610,6 +1618,7 @@ void gdextension_setup_interface() { REGISTER_INTERFACE_FUNC(variant_has_method); REGISTER_INTERFACE_FUNC(variant_has_member); REGISTER_INTERFACE_FUNC(variant_has_key); + REGISTER_INTERFACE_FUNC(variant_get_object_instance_id); REGISTER_INTERFACE_FUNC(variant_get_type_name); REGISTER_INTERFACE_FUNC(variant_can_convert); REGISTER_INTERFACE_FUNC(variant_can_convert_strict); diff --git a/core/extension/gdextension_interface.h b/core/extension/gdextension_interface.h index 9e3ce25698..374dbfd071 100644 --- a/core/extension/gdextension_interface.h +++ b/core/extension/gdextension_interface.h @@ -1308,6 +1308,21 @@ typedef GDExtensionBool (*GDExtensionInterfaceVariantHasMember)(GDExtensionVaria typedef GDExtensionBool (*GDExtensionInterfaceVariantHasKey)(GDExtensionConstVariantPtr p_self, GDExtensionConstVariantPtr p_key, GDExtensionBool *r_valid); /** + * @name variant_get_object_instance_id + * @since 4.4 + * + * Gets the object instance ID from a variant of type GDEXTENSION_VARIANT_TYPE_OBJECT. + * + * If the variant isn't of type GDEXTENSION_VARIANT_TYPE_OBJECT, then zero will be returned. + * The instance ID will be returned even if the object is no longer valid - use `object_get_instance_by_id()` to check if the object is still valid. + * + * @param p_self A pointer to the Variant. + * + * @return The instance ID for the contained object. + */ +typedef GDObjectInstanceID (*GDExtensionInterfaceVariantGetObjectInstanceId)(GDExtensionConstVariantPtr p_self); + +/** * @name variant_get_type_name * @since 4.1 * diff --git a/core/input/input_map.compat.inc b/core/input/input_map.compat.inc new file mode 100644 index 0000000000..da4bd962b6 --- /dev/null +++ b/core/input/input_map.compat.inc @@ -0,0 +1,41 @@ +/**************************************************************************/ +/* input_map.compat.inc */ +/**************************************************************************/ +/* 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 DISABLE_DEPRECATED + +void InputMap::_add_action_bind_compat_97281(const StringName &p_action, float p_deadzone) { + add_action(p_action, p_deadzone); +} + +void InputMap::_bind_compatibility_methods() { + ClassDB::bind_compatibility_method(D_METHOD("add_action", "action", "deadzone"), &InputMap::_add_action_bind_compat_97281, DEFVAL(0.5f)); +} + +#endif // DISABLE_DEPRECATED diff --git a/core/input/input_map.cpp b/core/input/input_map.cpp index 726653c92e..5b9377fe59 100644 --- a/core/input/input_map.cpp +++ b/core/input/input_map.cpp @@ -29,6 +29,7 @@ /**************************************************************************/ #include "input_map.h" +#include "input_map.compat.inc" #include "core/config/project_settings.h" #include "core/input/input.h" @@ -41,7 +42,7 @@ InputMap *InputMap::singleton = nullptr; void InputMap::_bind_methods() { ClassDB::bind_method(D_METHOD("has_action", "action"), &InputMap::has_action); ClassDB::bind_method(D_METHOD("get_actions"), &InputMap::_get_actions); - ClassDB::bind_method(D_METHOD("add_action", "action", "deadzone"), &InputMap::add_action, DEFVAL(0.5f)); + ClassDB::bind_method(D_METHOD("add_action", "action", "deadzone"), &InputMap::add_action, DEFVAL(0.2f)); ClassDB::bind_method(D_METHOD("erase_action", "action"), &InputMap::erase_action); ClassDB::bind_method(D_METHOD("action_set_deadzone", "action", "deadzone"), &InputMap::action_set_deadzone); @@ -304,7 +305,7 @@ void InputMap::load_from_project_settings() { String name = pi.name.substr(pi.name.find("/") + 1, pi.name.length()); Dictionary action = GLOBAL_GET(pi.name); - float deadzone = action.has("deadzone") ? (float)action["deadzone"] : 0.5f; + float deadzone = action.has("deadzone") ? (float)action["deadzone"] : 0.2f; Array events = action["events"]; add_action(name, deadzone); diff --git a/core/input/input_map.h b/core/input/input_map.h index ad4a8ae1a5..45798490f7 100644 --- a/core/input/input_map.h +++ b/core/input/input_map.h @@ -64,12 +64,17 @@ private: protected: static void _bind_methods(); +#ifndef DISABLE_DEPRECATED + void _add_action_bind_compat_97281(const StringName &p_action, float p_deadzone = 0.5); + static void _bind_compatibility_methods(); +#endif // DISABLE_DEPRECATED + public: static _FORCE_INLINE_ InputMap *get_singleton() { return singleton; } bool has_action(const StringName &p_action) const; List<StringName> get_actions() const; - void add_action(const StringName &p_action, float p_deadzone = 0.5); + void add_action(const StringName &p_action, float p_deadzone = 0.2); void erase_action(const StringName &p_action); float action_get_deadzone(const StringName &p_action); diff --git a/core/io/image.cpp b/core/io/image.cpp index aa391b77dd..d782af931f 100644 --- a/core/io/image.cpp +++ b/core/io/image.cpp @@ -2751,7 +2751,7 @@ Error Image::compress_from_channels(CompressMode p_mode, UsedChannels p_channels case COMPRESS_S3TC: { // BC3 is unsupported currently. - if ((p_channels == USED_CHANNELS_RGB || p_channels == USED_CHANNELS_L) && _image_compress_bc_rd_func) { + if ((p_channels == USED_CHANNELS_R || p_channels == USED_CHANNELS_RGB || p_channels == USED_CHANNELS_L) && _image_compress_bc_rd_func) { Error result = _image_compress_bc_rd_func(this, p_channels); // If the image was compressed successfully, we return here. If not, we fall back to the default compression scheme. diff --git a/core/io/json.cpp b/core/io/json.cpp index 664ff7857b..22219fca29 100644 --- a/core/io/json.cpp +++ b/core/io/json.cpp @@ -121,7 +121,7 @@ String JSON::_stringify(const Variant &p_var, const String &p_indent, int p_cur_ d.get_key_list(&keys); if (p_sort_keys) { - keys.sort(); + keys.sort_custom<StringLikeVariantOrder>(); } bool first_key = true; diff --git a/core/io/resource.cpp b/core/io/resource.cpp index 5f8a4b85a4..0ff4fbe490 100644 --- a/core/io/resource.cpp +++ b/core/io/resource.cpp @@ -99,31 +99,42 @@ void Resource::set_path_cache(const String &p_path) { GDVIRTUAL_CALL(_set_path_cache, p_path); } +static thread_local RandomPCG unique_id_gen(0, RandomPCG::DEFAULT_INC); + +void Resource::seed_scene_unique_id(uint32_t p_seed) { + unique_id_gen.seed(p_seed); +} + String Resource::generate_scene_unique_id() { // Generate a unique enough hash, but still user-readable. // If it's not unique it does not matter because the saver will try again. - OS::DateTime dt = OS::get_singleton()->get_datetime(); - uint32_t hash = hash_murmur3_one_32(OS::get_singleton()->get_ticks_usec()); - hash = hash_murmur3_one_32(dt.year, hash); - hash = hash_murmur3_one_32(dt.month, hash); - hash = hash_murmur3_one_32(dt.day, hash); - hash = hash_murmur3_one_32(dt.hour, hash); - hash = hash_murmur3_one_32(dt.minute, hash); - hash = hash_murmur3_one_32(dt.second, hash); - hash = hash_murmur3_one_32(Math::rand(), hash); + if (unique_id_gen.get_seed() == 0) { + OS::DateTime dt = OS::get_singleton()->get_datetime(); + uint32_t hash = hash_murmur3_one_32(OS::get_singleton()->get_ticks_usec()); + hash = hash_murmur3_one_32(dt.year, hash); + hash = hash_murmur3_one_32(dt.month, hash); + hash = hash_murmur3_one_32(dt.day, hash); + hash = hash_murmur3_one_32(dt.hour, hash); + hash = hash_murmur3_one_32(dt.minute, hash); + hash = hash_murmur3_one_32(dt.second, hash); + hash = hash_murmur3_one_32(Math::rand(), hash); + unique_id_gen.seed(hash); + } + + uint32_t random_num = unique_id_gen.rand(); static constexpr uint32_t characters = 5; static constexpr uint32_t char_count = ('z' - 'a'); static constexpr uint32_t base = char_count + ('9' - '0'); String id; for (uint32_t i = 0; i < characters; i++) { - uint32_t c = hash % base; + uint32_t c = random_num % base; if (c < char_count) { id += String::chr('a' + c); } else { id += String::chr('0' + (c - char_count)); } - hash /= base; + random_num /= base; } return id; diff --git a/core/io/resource.h b/core/io/resource.h index 8966c0233c..015f7ad197 100644 --- a/core/io/resource.h +++ b/core/io/resource.h @@ -114,6 +114,7 @@ public: virtual void set_path_cache(const String &p_path); // Set raw path without involving resource cache. _FORCE_INLINE_ bool is_built_in() const { return path_cache.is_empty() || path_cache.contains("::") || path_cache.begins_with("local://"); } + static void seed_scene_unique_id(uint32_t p_seed); static String generate_scene_unique_id(); void set_scene_unique_id(const String &p_id); String get_scene_unique_id() const; diff --git a/core/io/resource_format_binary.cpp b/core/io/resource_format_binary.cpp index b4826c356e..109999d612 100644 --- a/core/io/resource_format_binary.cpp +++ b/core/io/resource_format_binary.cpp @@ -845,29 +845,27 @@ Error ResourceLoaderBinary::load() { } } - if (ClassDB::has_property(res->get_class_name(), name)) { - if (value.get_type() == Variant::ARRAY) { - Array set_array = value; - bool is_get_valid = false; - Variant get_value = res->get(name, &is_get_valid); - if (is_get_valid && get_value.get_type() == Variant::ARRAY) { - Array get_array = get_value; - if (!set_array.is_same_typed(get_array)) { - value = Array(set_array, get_array.get_typed_builtin(), get_array.get_typed_class_name(), get_array.get_typed_script()); - } + if (value.get_type() == Variant::ARRAY) { + Array set_array = value; + bool is_get_valid = false; + Variant get_value = res->get(name, &is_get_valid); + if (is_get_valid && get_value.get_type() == Variant::ARRAY) { + Array get_array = get_value; + if (!set_array.is_same_typed(get_array)) { + value = Array(set_array, get_array.get_typed_builtin(), get_array.get_typed_class_name(), get_array.get_typed_script()); } } + } - if (value.get_type() == Variant::DICTIONARY) { - Dictionary set_dict = value; - bool is_get_valid = false; - Variant get_value = res->get(name, &is_get_valid); - if (is_get_valid && get_value.get_type() == Variant::DICTIONARY) { - Dictionary get_dict = get_value; - if (!set_dict.is_same_typed(get_dict)) { - value = Dictionary(set_dict, get_dict.get_typed_key_builtin(), get_dict.get_typed_key_class_name(), get_dict.get_typed_key_script(), - get_dict.get_typed_value_builtin(), get_dict.get_typed_value_class_name(), get_dict.get_typed_value_script()); - } + if (value.get_type() == Variant::DICTIONARY) { + Dictionary set_dict = value; + bool is_get_valid = false; + Variant get_value = res->get(name, &is_get_valid); + if (is_get_valid && get_value.get_type() == Variant::DICTIONARY) { + Dictionary get_dict = get_value; + if (!set_dict.is_same_typed(get_dict)) { + value = Dictionary(set_dict, get_dict.get_typed_key_builtin(), get_dict.get_typed_key_class_name(), get_dict.get_typed_key_script(), + get_dict.get_typed_value_builtin(), get_dict.get_typed_value_class_name(), get_dict.get_typed_value_script()); } } } @@ -2136,6 +2134,8 @@ static String _resource_get_class(Ref<Resource> p_resource) { } Error ResourceFormatSaverBinaryInstance::save(const String &p_path, const Ref<Resource> &p_resource, uint32_t p_flags) { + Resource::seed_scene_unique_id(p_path.hash()); + Error err; Ref<FileAccess> f; if (p_flags & ResourceSaver::FLAG_COMPRESS) { diff --git a/core/math/geometry_2d.cpp b/core/math/geometry_2d.cpp index d60619b27f..a49826958a 100644 --- a/core/math/geometry_2d.cpp +++ b/core/math/geometry_2d.cpp @@ -35,7 +35,8 @@ #define STB_RECT_PACK_IMPLEMENTATION #include "thirdparty/misc/stb_rect_pack.h" -#define PRECISION 5 // Based on CMP_EPSILON. +const int clipper_precision = 5; // Based on CMP_EPSILON. +const double clipper_scale = Math::pow(10.0, clipper_precision); Vector<Vector<Vector2>> Geometry2D::decompose_polygon_in_convex(const Vector<Point2> &polygon) { Vector<Vector<Vector2>> decomp; @@ -224,7 +225,7 @@ Vector<Vector<Point2>> Geometry2D::_polypaths_do_operation(PolyBooleanOperation path_b[i] = PointD(p_polypath_b[i].x, p_polypath_b[i].y); } - ClipperD clp(PRECISION); // Scale points up internally to attain the desired precision. + ClipperD clp(clipper_precision); // Scale points up internally to attain the desired precision. clp.PreserveCollinear(false); // Remove redundant vertices. if (is_a_open) { clp.AddOpenSubject({ path_a }); @@ -298,9 +299,10 @@ Vector<Vector<Point2>> Geometry2D::_polypath_offset(const Vector<Point2> &p_poly } // Inflate/deflate. - PathsD paths = InflatePaths({ polypath }, p_delta, jt, et, 2.0, PRECISION, 0.0); - // Here the miter_limit = 2.0 and arc_tolerance = 0.0 are Clipper2 defaults, - // and the PRECISION is used to scale points up internally, to attain the desired precision. + PathsD paths = InflatePaths({ polypath }, p_delta, jt, et, 2.0, clipper_precision, 0.25 * clipper_scale); + // Here the points are scaled up internally and + // the arc_tolerance is scaled accordingly + // to attain the desired precision. Vector<Vector<Point2>> polypaths; for (PathsD::size_type i = 0; i < paths.size(); ++i) { diff --git a/core/object/ref_counted.h b/core/object/ref_counted.h index f0706b4d08..22eb5a7a3f 100644 --- a/core/object/ref_counted.h +++ b/core/object/ref_counted.h @@ -57,24 +57,30 @@ template <typename T> class Ref { T *reference = nullptr; - void ref(const Ref &p_from) { - if (p_from.reference == reference) { + _FORCE_INLINE_ void ref(const Ref &p_from) { + ref_pointer<false>(p_from.reference); + } + + template <bool Init> + _FORCE_INLINE_ void ref_pointer(T *p_refcounted) { + if (p_refcounted == reference) { return; } - unref(); - - reference = p_from.reference; + // This will go out of scope and get unref'd. + Ref cleanup_ref; + cleanup_ref.reference = reference; + reference = p_refcounted; if (reference) { - reference->reference(); - } - } - - void ref_pointer(T *p_ref) { - ERR_FAIL_NULL(p_ref); - - if (p_ref->init_ref()) { - reference = p_ref; + if constexpr (Init) { + if (!reference->init_ref()) { + reference = nullptr; + } + } else { + if (!reference->reference()) { + reference = nullptr; + } + } } } @@ -124,15 +130,11 @@ public: template <typename T_Other> void operator=(const Ref<T_Other> &p_from) { - RefCounted *refb = const_cast<RefCounted *>(static_cast<const RefCounted *>(p_from.ptr())); - if (!refb) { - unref(); - return; - } - Ref r; - r.reference = Object::cast_to<T>(refb); - ref(r); - r.reference = nullptr; + ref_pointer<false>(Object::cast_to<T>(p_from.ptr())); + } + + void operator=(T *p_from) { + ref_pointer<true>(p_from); } void operator=(const Variant &p_variant) { @@ -142,16 +144,7 @@ public: return; } - unref(); - - if (!object) { - return; - } - - T *r = Object::cast_to<T>(object); - if (r && r->reference()) { - reference = r; - } + ref_pointer<false>(Object::cast_to<T>(object)); } template <typename T_Other> @@ -159,48 +152,25 @@ public: if (reference == p_ptr) { return; } - unref(); - T *r = Object::cast_to<T>(p_ptr); - if (r) { - ref_pointer(r); - } + ref_pointer<true>(Object::cast_to<T>(p_ptr)); } Ref(const Ref &p_from) { - ref(p_from); + this->operator=(p_from); } template <typename T_Other> Ref(const Ref<T_Other> &p_from) { - RefCounted *refb = const_cast<RefCounted *>(static_cast<const RefCounted *>(p_from.ptr())); - if (!refb) { - unref(); - return; - } - Ref r; - r.reference = Object::cast_to<T>(refb); - ref(r); - r.reference = nullptr; + this->operator=(p_from); } - Ref(T *p_reference) { - if (p_reference) { - ref_pointer(p_reference); - } + Ref(T *p_from) { + this->operator=(p_from); } - Ref(const Variant &p_variant) { - Object *object = p_variant.get_validated_object(); - - if (!object) { - return; - } - - T *r = Object::cast_to<T>(object); - if (r && r->reference()) { - reference = r; - } + Ref(const Variant &p_from) { + this->operator=(p_from); } inline bool is_valid() const { return reference != nullptr; } @@ -222,7 +192,7 @@ public: ref(memnew(T(p_params...))); } - Ref() {} + Ref() = default; ~Ref() { unref(); @@ -299,13 +269,13 @@ struct GetTypeInfo<const Ref<T> &> { template <typename T> struct VariantInternalAccessor<Ref<T>> { static _FORCE_INLINE_ Ref<T> get(const Variant *v) { return Ref<T>(*VariantInternal::get_object(v)); } - static _FORCE_INLINE_ void set(Variant *v, const Ref<T> &p_ref) { VariantInternal::refcounted_object_assign(v, p_ref.ptr()); } + static _FORCE_INLINE_ void set(Variant *v, const Ref<T> &p_ref) { VariantInternal::object_assign(v, p_ref); } }; template <typename T> struct VariantInternalAccessor<const Ref<T> &> { static _FORCE_INLINE_ Ref<T> get(const Variant *v) { return Ref<T>(*VariantInternal::get_object(v)); } - static _FORCE_INLINE_ void set(Variant *v, const Ref<T> &p_ref) { VariantInternal::refcounted_object_assign(v, p_ref.ptr()); } + static _FORCE_INLINE_ void set(Variant *v, const Ref<T> &p_ref) { VariantInternal::object_assign(v, p_ref); } }; #endif // REF_COUNTED_H diff --git a/core/string/translation_domain.cpp b/core/string/translation_domain.cpp index b44eb40366..53b9ce8379 100644 --- a/core/string/translation_domain.cpp +++ b/core/string/translation_domain.cpp @@ -33,6 +33,170 @@ #include "core/string/translation.h" #include "core/string/translation_server.h" +struct _character_accent_pair { + const char32_t character; + const char32_t *accented_character; +}; + +static _character_accent_pair _character_to_accented[] = { + { 'A', U"Å" }, + { 'B', U"ß" }, + { 'C', U"Ç" }, + { 'D', U"Ð" }, + { 'E', U"É" }, + { 'F', U"F́" }, + { 'G', U"Ĝ" }, + { 'H', U"Ĥ" }, + { 'I', U"Ĩ" }, + { 'J', U"Ĵ" }, + { 'K', U"ĸ" }, + { 'L', U"Ł" }, + { 'M', U"Ḿ" }, + { 'N', U"й" }, + { 'O', U"Ö" }, + { 'P', U"Ṕ" }, + { 'Q', U"Q́" }, + { 'R', U"Ř" }, + { 'S', U"Ŝ" }, + { 'T', U"Ŧ" }, + { 'U', U"Ũ" }, + { 'V', U"Ṽ" }, + { 'W', U"Ŵ" }, + { 'X', U"X́" }, + { 'Y', U"Ÿ" }, + { 'Z', U"Ž" }, + { 'a', U"á" }, + { 'b', U"ḅ" }, + { 'c', U"ć" }, + { 'd', U"d́" }, + { 'e', U"é" }, + { 'f', U"f́" }, + { 'g', U"ǵ" }, + { 'h', U"h̀" }, + { 'i', U"í" }, + { 'j', U"ǰ" }, + { 'k', U"ḱ" }, + { 'l', U"ł" }, + { 'm', U"m̀" }, + { 'n', U"ή" }, + { 'o', U"ô" }, + { 'p', U"ṕ" }, + { 'q', U"q́" }, + { 'r', U"ŕ" }, + { 's', U"š" }, + { 't', U"ŧ" }, + { 'u', U"ü" }, + { 'v', U"ṽ" }, + { 'w', U"ŵ" }, + { 'x', U"x́" }, + { 'y', U"ý" }, + { 'z', U"ź" }, +}; + +String TranslationDomain::_get_override_string(const String &p_message) const { + String res; + for (int i = 0; i < p_message.length(); i++) { + if (pseudolocalization.skip_placeholders_enabled && _is_placeholder(p_message, i)) { + res += p_message[i]; + res += p_message[i + 1]; + i++; + continue; + } + res += '*'; + } + return res; +} + +String TranslationDomain::_double_vowels(const String &p_message) const { + String res; + for (int i = 0; i < p_message.length(); i++) { + if (pseudolocalization.skip_placeholders_enabled && _is_placeholder(p_message, i)) { + res += p_message[i]; + res += p_message[i + 1]; + i++; + continue; + } + res += p_message[i]; + if (p_message[i] == 'a' || p_message[i] == 'e' || p_message[i] == 'i' || p_message[i] == 'o' || p_message[i] == 'u' || + p_message[i] == 'A' || p_message[i] == 'E' || p_message[i] == 'I' || p_message[i] == 'O' || p_message[i] == 'U') { + res += p_message[i]; + } + } + return res; +}; + +String TranslationDomain::_replace_with_accented_string(const String &p_message) const { + String res; + for (int i = 0; i < p_message.length(); i++) { + if (pseudolocalization.skip_placeholders_enabled && _is_placeholder(p_message, i)) { + res += p_message[i]; + res += p_message[i + 1]; + i++; + continue; + } + const char32_t *accented = _get_accented_version(p_message[i]); + if (accented) { + res += accented; + } else { + res += p_message[i]; + } + } + return res; +} + +String TranslationDomain::_wrap_with_fakebidi_characters(const String &p_message) const { + String res; + char32_t fakebidiprefix = U'\u202e'; + char32_t fakebidisuffix = U'\u202c'; + res += fakebidiprefix; + // The fake bidi unicode gets popped at every newline so pushing it back at every newline. + for (int i = 0; i < p_message.length(); i++) { + if (p_message[i] == '\n') { + res += fakebidisuffix; + res += p_message[i]; + res += fakebidiprefix; + } else if (pseudolocalization.skip_placeholders_enabled && _is_placeholder(p_message, i)) { + res += fakebidisuffix; + res += p_message[i]; + res += p_message[i + 1]; + res += fakebidiprefix; + i++; + } else { + res += p_message[i]; + } + } + res += fakebidisuffix; + return res; +} + +String TranslationDomain::_add_padding(const String &p_message, int p_length) const { + String underscores = String("_").repeat(p_length * pseudolocalization.expansion_ratio / 2); + String prefix = pseudolocalization.prefix + underscores; + String suffix = underscores + pseudolocalization.suffix; + + return prefix + p_message + suffix; +} + +const char32_t *TranslationDomain::_get_accented_version(char32_t p_character) const { + if (!is_ascii_alphabet_char(p_character)) { + return nullptr; + } + + for (unsigned int i = 0; i < sizeof(_character_to_accented) / sizeof(_character_to_accented[0]); i++) { + if (_character_to_accented[i].character == p_character) { + return _character_to_accented[i].accented_character; + } + } + + return nullptr; +} + +bool TranslationDomain::_is_placeholder(const String &p_message, int p_index) const { + return p_index < p_message.length() - 1 && p_message[p_index] == '%' && + (p_message[p_index + 1] == 's' || p_message[p_index + 1] == 'c' || p_message[p_index + 1] == 'd' || + p_message[p_index + 1] == 'o' || p_message[p_index + 1] == 'x' || p_message[p_index + 1] == 'X' || p_message[p_index + 1] == 'f'); +} + StringName TranslationDomain::get_message_from_translations(const String &p_locale, const StringName &p_message, const StringName &p_context) const { StringName res; int best_score = 0; @@ -129,9 +293,9 @@ StringName TranslationDomain::translate(const StringName &p_message, const Strin } if (!res) { - return p_message; + return pseudolocalization.enabled ? pseudolocalize(p_message) : p_message; } - return res; + return pseudolocalization.enabled ? pseudolocalize(res) : res; } StringName TranslationDomain::translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const { @@ -152,6 +316,104 @@ StringName TranslationDomain::translate_plural(const StringName &p_message, cons return res; } +bool TranslationDomain::is_pseudolocalization_enabled() const { + return pseudolocalization.enabled; +} + +void TranslationDomain::set_pseudolocalization_enabled(bool p_enabled) { + pseudolocalization.enabled = p_enabled; +} + +bool TranslationDomain::is_pseudolocalization_accents_enabled() const { + return pseudolocalization.accents_enabled; +} + +void TranslationDomain::set_pseudolocalization_accents_enabled(bool p_enabled) { + pseudolocalization.accents_enabled = p_enabled; +} + +bool TranslationDomain::is_pseudolocalization_double_vowels_enabled() const { + return pseudolocalization.double_vowels_enabled; +} + +void TranslationDomain::set_pseudolocalization_double_vowels_enabled(bool p_enabled) { + pseudolocalization.double_vowels_enabled = p_enabled; +} + +bool TranslationDomain::is_pseudolocalization_fake_bidi_enabled() const { + return pseudolocalization.fake_bidi_enabled; +} + +void TranslationDomain::set_pseudolocalization_fake_bidi_enabled(bool p_enabled) { + pseudolocalization.fake_bidi_enabled = p_enabled; +} + +bool TranslationDomain::is_pseudolocalization_override_enabled() const { + return pseudolocalization.override_enabled; +} + +void TranslationDomain::set_pseudolocalization_override_enabled(bool p_enabled) { + pseudolocalization.override_enabled = p_enabled; +} + +bool TranslationDomain::is_pseudolocalization_skip_placeholders_enabled() const { + return pseudolocalization.skip_placeholders_enabled; +} + +void TranslationDomain::set_pseudolocalization_skip_placeholders_enabled(bool p_enabled) { + pseudolocalization.skip_placeholders_enabled = p_enabled; +} + +float TranslationDomain::get_pseudolocalization_expansion_ratio() const { + return pseudolocalization.expansion_ratio; +} + +void TranslationDomain::set_pseudolocalization_expansion_ratio(float p_ratio) { + pseudolocalization.expansion_ratio = p_ratio; +} + +String TranslationDomain::get_pseudolocalization_prefix() const { + return pseudolocalization.prefix; +} + +void TranslationDomain::set_pseudolocalization_prefix(const String &p_prefix) { + pseudolocalization.prefix = p_prefix; +} + +String TranslationDomain::get_pseudolocalization_suffix() const { + return pseudolocalization.suffix; +} + +void TranslationDomain::set_pseudolocalization_suffix(const String &p_suffix) { + pseudolocalization.suffix = p_suffix; +} + +StringName TranslationDomain::pseudolocalize(const StringName &p_message) const { + if (p_message.is_empty()) { + return p_message; + } + + String message = p_message; + int length = message.length(); + if (pseudolocalization.override_enabled) { + message = _get_override_string(message); + } + + if (pseudolocalization.double_vowels_enabled) { + message = _double_vowels(message); + } + + if (pseudolocalization.accents_enabled) { + message = _replace_with_accented_string(message); + } + + if (pseudolocalization.fake_bidi_enabled) { + message = _wrap_with_fakebidi_characters(message); + } + + return _add_padding(message, length); +} + void TranslationDomain::_bind_methods() { ClassDB::bind_method(D_METHOD("get_translation_object", "locale"), &TranslationDomain::get_translation_object); ClassDB::bind_method(D_METHOD("add_translation", "translation"), &TranslationDomain::add_translation); @@ -159,6 +421,36 @@ void TranslationDomain::_bind_methods() { ClassDB::bind_method(D_METHOD("clear"), &TranslationDomain::clear); ClassDB::bind_method(D_METHOD("translate", "message", "context"), &TranslationDomain::translate, DEFVAL(StringName())); ClassDB::bind_method(D_METHOD("translate_plural", "message", "message_plural", "n", "context"), &TranslationDomain::translate_plural, DEFVAL(StringName())); + + ClassDB::bind_method(D_METHOD("is_pseudolocalization_enabled"), &TranslationDomain::is_pseudolocalization_enabled); + ClassDB::bind_method(D_METHOD("set_pseudolocalization_enabled", "enabled"), &TranslationDomain::set_pseudolocalization_enabled); + ClassDB::bind_method(D_METHOD("is_pseudolocalization_accents_enabled"), &TranslationDomain::is_pseudolocalization_accents_enabled); + ClassDB::bind_method(D_METHOD("set_pseudolocalization_accents_enabled", "enabled"), &TranslationDomain::set_pseudolocalization_accents_enabled); + ClassDB::bind_method(D_METHOD("is_pseudolocalization_double_vowels_enabled"), &TranslationDomain::is_pseudolocalization_double_vowels_enabled); + ClassDB::bind_method(D_METHOD("set_pseudolocalization_double_vowels_enabled", "enabled"), &TranslationDomain::set_pseudolocalization_double_vowels_enabled); + ClassDB::bind_method(D_METHOD("is_pseudolocalization_fake_bidi_enabled"), &TranslationDomain::is_pseudolocalization_fake_bidi_enabled); + ClassDB::bind_method(D_METHOD("set_pseudolocalization_fake_bidi_enabled", "enabled"), &TranslationDomain::set_pseudolocalization_fake_bidi_enabled); + ClassDB::bind_method(D_METHOD("is_pseudolocalization_override_enabled"), &TranslationDomain::is_pseudolocalization_override_enabled); + ClassDB::bind_method(D_METHOD("set_pseudolocalization_override_enabled", "enabled"), &TranslationDomain::set_pseudolocalization_override_enabled); + ClassDB::bind_method(D_METHOD("is_pseudolocalization_skip_placeholders_enabled"), &TranslationDomain::is_pseudolocalization_skip_placeholders_enabled); + ClassDB::bind_method(D_METHOD("set_pseudolocalization_skip_placeholders_enabled", "enabled"), &TranslationDomain::set_pseudolocalization_skip_placeholders_enabled); + ClassDB::bind_method(D_METHOD("get_pseudolocalization_expansion_ratio"), &TranslationDomain::get_pseudolocalization_expansion_ratio); + ClassDB::bind_method(D_METHOD("set_pseudolocalization_expansion_ratio", "ratio"), &TranslationDomain::set_pseudolocalization_expansion_ratio); + ClassDB::bind_method(D_METHOD("get_pseudolocalization_prefix"), &TranslationDomain::get_pseudolocalization_prefix); + ClassDB::bind_method(D_METHOD("set_pseudolocalization_prefix", "prefix"), &TranslationDomain::set_pseudolocalization_prefix); + ClassDB::bind_method(D_METHOD("get_pseudolocalization_suffix"), &TranslationDomain::get_pseudolocalization_suffix); + ClassDB::bind_method(D_METHOD("set_pseudolocalization_suffix", "suffix"), &TranslationDomain::set_pseudolocalization_suffix); + ClassDB::bind_method(D_METHOD("pseudolocalize", "message"), &TranslationDomain::pseudolocalize); + + ADD_PROPERTY(PropertyInfo(Variant::Type::BOOL, "pseudolocalization_enabled"), "set_pseudolocalization_enabled", "is_pseudolocalization_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::Type::BOOL, "pseudolocalization_accents_enabled"), "set_pseudolocalization_accents_enabled", "is_pseudolocalization_accents_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::Type::BOOL, "pseudolocalization_double_vowels_enabled"), "set_pseudolocalization_double_vowels_enabled", "is_pseudolocalization_double_vowels_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::Type::BOOL, "pseudolocalization_fake_bidi_enabled"), "set_pseudolocalization_fake_bidi_enabled", "is_pseudolocalization_fake_bidi_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::Type::BOOL, "pseudolocalization_override_enabled"), "set_pseudolocalization_override_enabled", "is_pseudolocalization_override_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::Type::BOOL, "pseudolocalization_skip_placeholders_enabled"), "set_pseudolocalization_skip_placeholders_enabled", "is_pseudolocalization_skip_placeholders_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::Type::FLOAT, "pseudolocalization_expansion_ratio"), "set_pseudolocalization_expansion_ratio", "get_pseudolocalization_expansion_ratio"); + ADD_PROPERTY(PropertyInfo(Variant::Type::STRING, "pseudolocalization_prefix"), "set_pseudolocalization_prefix", "get_pseudolocalization_prefix"); + ADD_PROPERTY(PropertyInfo(Variant::Type::STRING, "pseudolocalization_suffix"), "set_pseudolocalization_suffix", "get_pseudolocalization_suffix"); } TranslationDomain::TranslationDomain() { diff --git a/core/string/translation_domain.h b/core/string/translation_domain.h index 6139967217..55592d3b35 100644 --- a/core/string/translation_domain.h +++ b/core/string/translation_domain.h @@ -38,7 +38,28 @@ class Translation; class TranslationDomain : public RefCounted { GDCLASS(TranslationDomain, RefCounted); + struct PseudolocalizationConfig { + bool enabled = false; + bool accents_enabled = true; + bool double_vowels_enabled = false; + bool fake_bidi_enabled = false; + bool override_enabled = false; + bool skip_placeholders_enabled = true; + float expansion_ratio = 0.0; + String prefix = "["; + String suffix = "]"; + }; + HashSet<Ref<Translation>> translations; + PseudolocalizationConfig pseudolocalization; + + String _get_override_string(const String &p_message) const; + String _double_vowels(const String &p_message) const; + String _replace_with_accented_string(const String &p_message) const; + String _wrap_with_fakebidi_characters(const String &p_message) const; + String _add_padding(const String &p_message, int p_length) const; + const char32_t *_get_accented_version(char32_t p_character) const; + bool _is_placeholder(const String &p_message, int p_index) const; protected: static void _bind_methods(); @@ -59,6 +80,27 @@ public: StringName translate(const StringName &p_message, const StringName &p_context) const; StringName translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const; + bool is_pseudolocalization_enabled() const; + void set_pseudolocalization_enabled(bool p_enabled); + bool is_pseudolocalization_accents_enabled() const; + void set_pseudolocalization_accents_enabled(bool p_enabled); + bool is_pseudolocalization_double_vowels_enabled() const; + void set_pseudolocalization_double_vowels_enabled(bool p_enabled); + bool is_pseudolocalization_fake_bidi_enabled() const; + void set_pseudolocalization_fake_bidi_enabled(bool p_enabled); + bool is_pseudolocalization_override_enabled() const; + void set_pseudolocalization_override_enabled(bool p_enabled); + bool is_pseudolocalization_skip_placeholders_enabled() const; + void set_pseudolocalization_skip_placeholders_enabled(bool p_enabled); + float get_pseudolocalization_expansion_ratio() const; + void set_pseudolocalization_expansion_ratio(float p_ratio); + String get_pseudolocalization_prefix() const; + void set_pseudolocalization_prefix(const String &p_prefix); + String get_pseudolocalization_suffix() const; + void set_pseudolocalization_suffix(const String &p_suffix); + + StringName pseudolocalize(const StringName &p_message) const; + TranslationDomain(); }; diff --git a/core/string/translation_server.cpp b/core/string/translation_server.cpp index c6b818a49b..89b37d0b8a 100644 --- a/core/string/translation_server.cpp +++ b/core/string/translation_server.cpp @@ -39,66 +39,6 @@ #include "main/main.h" #endif -struct _character_accent_pair { - const char32_t character; - const char32_t *accented_character; -}; - -static _character_accent_pair _character_to_accented[] = { - { 'A', U"Å" }, - { 'B', U"ß" }, - { 'C', U"Ç" }, - { 'D', U"Ð" }, - { 'E', U"É" }, - { 'F', U"F́" }, - { 'G', U"Ĝ" }, - { 'H', U"Ĥ" }, - { 'I', U"Ĩ" }, - { 'J', U"Ĵ" }, - { 'K', U"ĸ" }, - { 'L', U"Ł" }, - { 'M', U"Ḿ" }, - { 'N', U"й" }, - { 'O', U"Ö" }, - { 'P', U"Ṕ" }, - { 'Q', U"Q́" }, - { 'R', U"Ř" }, - { 'S', U"Ŝ" }, - { 'T', U"Ŧ" }, - { 'U', U"Ũ" }, - { 'V', U"Ṽ" }, - { 'W', U"Ŵ" }, - { 'X', U"X́" }, - { 'Y', U"Ÿ" }, - { 'Z', U"Ž" }, - { 'a', U"á" }, - { 'b', U"ḅ" }, - { 'c', U"ć" }, - { 'd', U"d́" }, - { 'e', U"é" }, - { 'f', U"f́" }, - { 'g', U"ǵ" }, - { 'h', U"h̀" }, - { 'i', U"í" }, - { 'j', U"ǰ" }, - { 'k', U"ḱ" }, - { 'l', U"ł" }, - { 'm', U"m̀" }, - { 'n', U"ή" }, - { 'o', U"ô" }, - { 'p', U"ṕ" }, - { 'q', U"q́" }, - { 'r', U"ŕ" }, - { 's', U"š" }, - { 't', U"ŧ" }, - { 'u', U"ü" }, - { 'v', U"ṽ" }, - { 'w', U"ŵ" }, - { 'x', U"x́" }, - { 'y', U"ý" }, - { 'z', U"ź" }, -}; - Vector<TranslationServer::LocaleScriptInfo> TranslationServer::locale_script_info; HashMap<String, String> TranslationServer::language_map; @@ -433,8 +373,7 @@ StringName TranslationServer::translate(const StringName &p_message, const Strin return p_message; } - const StringName res = main_domain->translate(p_message, p_context); - return pseudolocalization_enabled ? pseudolocalize(res) : res; + return main_domain->translate(p_message, p_context); } StringName TranslationServer::translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const { @@ -510,15 +449,15 @@ void TranslationServer::setup() { } fallback = GLOBAL_DEF("internationalization/locale/fallback", "en"); - pseudolocalization_enabled = GLOBAL_DEF("internationalization/pseudolocalization/use_pseudolocalization", false); - pseudolocalization_accents_enabled = GLOBAL_DEF("internationalization/pseudolocalization/replace_with_accents", true); - pseudolocalization_double_vowels_enabled = GLOBAL_DEF("internationalization/pseudolocalization/double_vowels", false); - pseudolocalization_fake_bidi_enabled = GLOBAL_DEF("internationalization/pseudolocalization/fake_bidi", false); - pseudolocalization_override_enabled = GLOBAL_DEF("internationalization/pseudolocalization/override", false); - expansion_ratio = GLOBAL_DEF("internationalization/pseudolocalization/expansion_ratio", 0.0); - pseudolocalization_prefix = GLOBAL_DEF("internationalization/pseudolocalization/prefix", "["); - pseudolocalization_suffix = GLOBAL_DEF("internationalization/pseudolocalization/suffix", "]"); - pseudolocalization_skip_placeholders_enabled = GLOBAL_DEF("internationalization/pseudolocalization/skip_placeholders", true); + main_domain->set_pseudolocalization_enabled(GLOBAL_DEF("internationalization/pseudolocalization/use_pseudolocalization", false)); + main_domain->set_pseudolocalization_accents_enabled(GLOBAL_DEF("internationalization/pseudolocalization/replace_with_accents", true)); + main_domain->set_pseudolocalization_double_vowels_enabled(GLOBAL_DEF("internationalization/pseudolocalization/double_vowels", false)); + main_domain->set_pseudolocalization_fake_bidi_enabled(GLOBAL_DEF("internationalization/pseudolocalization/fake_bidi", false)); + main_domain->set_pseudolocalization_override_enabled(GLOBAL_DEF("internationalization/pseudolocalization/override", false)); + main_domain->set_pseudolocalization_expansion_ratio(GLOBAL_DEF("internationalization/pseudolocalization/expansion_ratio", 0.0)); + main_domain->set_pseudolocalization_prefix(GLOBAL_DEF("internationalization/pseudolocalization/prefix", "[")); + main_domain->set_pseudolocalization_suffix(GLOBAL_DEF("internationalization/pseudolocalization/suffix", "]")); + main_domain->set_pseudolocalization_skip_placeholders_enabled(GLOBAL_DEF("internationalization/pseudolocalization/skip_placeholders", true)); #ifdef TOOLS_ENABLED ProjectSettings::get_singleton()->set_custom_property_info(PropertyInfo(Variant::STRING, "internationalization/locale/fallback", PROPERTY_HINT_LOCALE_ID, "")); @@ -567,11 +506,11 @@ StringName TranslationServer::doc_translate_plural(const StringName &p_message, } bool TranslationServer::is_pseudolocalization_enabled() const { - return pseudolocalization_enabled; + return main_domain->is_pseudolocalization_enabled(); } void TranslationServer::set_pseudolocalization_enabled(bool p_enabled) { - pseudolocalization_enabled = p_enabled; + main_domain->set_pseudolocalization_enabled(p_enabled); ResourceLoader::reload_translation_remaps(); @@ -581,14 +520,14 @@ void TranslationServer::set_pseudolocalization_enabled(bool p_enabled) { } void TranslationServer::reload_pseudolocalization() { - pseudolocalization_accents_enabled = GLOBAL_GET("internationalization/pseudolocalization/replace_with_accents"); - pseudolocalization_double_vowels_enabled = GLOBAL_GET("internationalization/pseudolocalization/double_vowels"); - pseudolocalization_fake_bidi_enabled = GLOBAL_GET("internationalization/pseudolocalization/fake_bidi"); - pseudolocalization_override_enabled = GLOBAL_GET("internationalization/pseudolocalization/override"); - expansion_ratio = GLOBAL_GET("internationalization/pseudolocalization/expansion_ratio"); - pseudolocalization_prefix = GLOBAL_GET("internationalization/pseudolocalization/prefix"); - pseudolocalization_suffix = GLOBAL_GET("internationalization/pseudolocalization/suffix"); - pseudolocalization_skip_placeholders_enabled = GLOBAL_GET("internationalization/pseudolocalization/skip_placeholders"); + main_domain->set_pseudolocalization_accents_enabled(GLOBAL_GET("internationalization/pseudolocalization/replace_with_accents")); + main_domain->set_pseudolocalization_double_vowels_enabled(GLOBAL_GET("internationalization/pseudolocalization/double_vowels")); + main_domain->set_pseudolocalization_fake_bidi_enabled(GLOBAL_GET("internationalization/pseudolocalization/fake_bidi")); + main_domain->set_pseudolocalization_override_enabled(GLOBAL_GET("internationalization/pseudolocalization/override")); + main_domain->set_pseudolocalization_expansion_ratio(GLOBAL_GET("internationalization/pseudolocalization/expansion_ratio")); + main_domain->set_pseudolocalization_prefix(GLOBAL_GET("internationalization/pseudolocalization/prefix")); + main_domain->set_pseudolocalization_suffix(GLOBAL_GET("internationalization/pseudolocalization/suffix")); + main_domain->set_pseudolocalization_skip_placeholders_enabled(GLOBAL_GET("internationalization/pseudolocalization/skip_placeholders")); ResourceLoader::reload_translation_remaps(); @@ -598,138 +537,7 @@ void TranslationServer::reload_pseudolocalization() { } StringName TranslationServer::pseudolocalize(const StringName &p_message) const { - String message = p_message; - int length = message.length(); - if (pseudolocalization_override_enabled) { - message = get_override_string(message); - } - - if (pseudolocalization_double_vowels_enabled) { - message = double_vowels(message); - } - - if (pseudolocalization_accents_enabled) { - message = replace_with_accented_string(message); - } - - if (pseudolocalization_fake_bidi_enabled) { - message = wrap_with_fakebidi_characters(message); - } - - StringName res = add_padding(message, length); - return res; -} - -StringName TranslationServer::tool_pseudolocalize(const StringName &p_message) const { - String message = p_message; - message = double_vowels(message); - message = replace_with_accented_string(message); - StringName res = "[!!! " + message + " !!!]"; - return res; -} - -String TranslationServer::get_override_string(String &p_message) const { - String res; - for (int i = 0; i < p_message.length(); i++) { - if (pseudolocalization_skip_placeholders_enabled && is_placeholder(p_message, i)) { - res += p_message[i]; - res += p_message[i + 1]; - i++; - continue; - } - res += '*'; - } - return res; -} - -String TranslationServer::double_vowels(String &p_message) const { - String res; - for (int i = 0; i < p_message.length(); i++) { - if (pseudolocalization_skip_placeholders_enabled && is_placeholder(p_message, i)) { - res += p_message[i]; - res += p_message[i + 1]; - i++; - continue; - } - res += p_message[i]; - if (p_message[i] == 'a' || p_message[i] == 'e' || p_message[i] == 'i' || p_message[i] == 'o' || p_message[i] == 'u' || - p_message[i] == 'A' || p_message[i] == 'E' || p_message[i] == 'I' || p_message[i] == 'O' || p_message[i] == 'U') { - res += p_message[i]; - } - } - return res; -}; - -String TranslationServer::replace_with_accented_string(String &p_message) const { - String res; - for (int i = 0; i < p_message.length(); i++) { - if (pseudolocalization_skip_placeholders_enabled && is_placeholder(p_message, i)) { - res += p_message[i]; - res += p_message[i + 1]; - i++; - continue; - } - const char32_t *accented = get_accented_version(p_message[i]); - if (accented) { - res += accented; - } else { - res += p_message[i]; - } - } - return res; -} - -String TranslationServer::wrap_with_fakebidi_characters(String &p_message) const { - String res; - char32_t fakebidiprefix = U'\u202e'; - char32_t fakebidisuffix = U'\u202c'; - res += fakebidiprefix; - // The fake bidi unicode gets popped at every newline so pushing it back at every newline. - for (int i = 0; i < p_message.length(); i++) { - if (p_message[i] == '\n') { - res += fakebidisuffix; - res += p_message[i]; - res += fakebidiprefix; - } else if (pseudolocalization_skip_placeholders_enabled && is_placeholder(p_message, i)) { - res += fakebidisuffix; - res += p_message[i]; - res += p_message[i + 1]; - res += fakebidiprefix; - i++; - } else { - res += p_message[i]; - } - } - res += fakebidisuffix; - return res; -} - -String TranslationServer::add_padding(const String &p_message, int p_length) const { - String underscores = String("_").repeat(p_length * expansion_ratio / 2); - String prefix = pseudolocalization_prefix + underscores; - String suffix = underscores + pseudolocalization_suffix; - - return prefix + p_message + suffix; -} - -const char32_t *TranslationServer::get_accented_version(char32_t p_character) const { - if (!is_ascii_alphabet_char(p_character)) { - return nullptr; - } - - for (unsigned int i = 0; i < sizeof(_character_to_accented) / sizeof(_character_to_accented[0]); i++) { - if (_character_to_accented[i].character == p_character) { - return _character_to_accented[i].accented_character; - } - } - - return nullptr; -} - -bool TranslationServer::is_placeholder(String &p_message, int p_index) const { - return p_index < p_message.length() - 1 && p_message[p_index] == '%' && - (p_message[p_index + 1] == 's' || p_message[p_index + 1] == 'c' || p_message[p_index + 1] == 'd' || - p_message[p_index + 1] == 'o' || p_message[p_index + 1] == 'x' || p_message[p_index + 1] == 'X' || p_message[p_index + 1] == 'f'); + return main_domain->pseudolocalize(p_message); } #ifdef TOOLS_ENABLED diff --git a/core/string/translation_server.h b/core/string/translation_server.h index 272fa1f11c..a09230c019 100644 --- a/core/string/translation_server.h +++ b/core/string/translation_server.h @@ -48,25 +48,6 @@ class TranslationServer : public Object { bool enabled = true; - bool pseudolocalization_enabled = false; - bool pseudolocalization_accents_enabled = false; - bool pseudolocalization_double_vowels_enabled = false; - bool pseudolocalization_fake_bidi_enabled = false; - bool pseudolocalization_override_enabled = false; - bool pseudolocalization_skip_placeholders_enabled = false; - float expansion_ratio = 0.0; - String pseudolocalization_prefix; - String pseudolocalization_suffix; - - StringName tool_pseudolocalize(const StringName &p_message) const; - String get_override_string(String &p_message) const; - String double_vowels(String &p_message) const; - String replace_with_accented_string(String &p_message) const; - String wrap_with_fakebidi_characters(String &p_message) const; - String add_padding(const String &p_message, int p_length) const; - const char32_t *get_accented_version(char32_t p_character) const; - bool is_placeholder(String &p_message, int p_index) const; - static TranslationServer *singleton; bool _load_translations(const String &p_from); String _standardize_locale(const String &p_locale, bool p_add_defaults) const; @@ -93,6 +74,8 @@ class TranslationServer : public Object { public: _FORCE_INLINE_ static TranslationServer *get_singleton() { return singleton; } + Ref<TranslationDomain> get_editor_domain() const { return editor_domain; } + void set_enabled(bool p_enabled) { enabled = p_enabled; } _FORCE_INLINE_ bool is_enabled() const { return enabled; } diff --git a/core/templates/hash_map.cpp b/core/templates/hash_map.cpp new file mode 100644 index 0000000000..93664dd2e1 --- /dev/null +++ b/core/templates/hash_map.cpp @@ -0,0 +1,43 @@ +/**************************************************************************/ +/* hash_map.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "hash_map.h" + +#include "core/variant/variant.h" + +bool _hashmap_variant_less_than(const Variant &p_left, const Variant &p_right) { + bool valid = false; + Variant res; + Variant::evaluate(Variant::OP_LESS, p_left, p_right, res, valid); + if (!valid) { + res = false; + } + return res; +} diff --git a/core/templates/hash_map.h b/core/templates/hash_map.h index a3e8c2c788..329952e8d4 100644 --- a/core/templates/hash_map.h +++ b/core/templates/hash_map.h @@ -61,6 +61,8 @@ struct HashMapElement { data(p_key, p_value) {} }; +bool _hashmap_variant_less_than(const Variant &p_left, const Variant &p_right); + template <typename TKey, typename TValue, typename Hasher = HashMapHasherDefault, typename Comparator = HashMapComparatorDefault<TKey>, @@ -271,6 +273,47 @@ public: num_elements = 0; } + void sort() { + if (elements == nullptr || num_elements < 2) { + return; // An empty or single element HashMap is already sorted. + } + // Use insertion sort because we want this operation to be fast for the + // common case where the input is already sorted or nearly sorted. + HashMapElement<TKey, TValue> *inserting = head_element->next; + while (inserting != nullptr) { + HashMapElement<TKey, TValue> *after = nullptr; + for (HashMapElement<TKey, TValue> *current = inserting->prev; current != nullptr; current = current->prev) { + if (_hashmap_variant_less_than(inserting->data.key, current->data.key)) { + after = current; + } else { + break; + } + } + HashMapElement<TKey, TValue> *next = inserting->next; + if (after != nullptr) { + // Modify the elements around `inserting` to remove it from its current position. + inserting->prev->next = next; + if (next == nullptr) { + tail_element = inserting->prev; + } else { + next->prev = inserting->prev; + } + // Modify `before` and `after` to insert `inserting` between them. + HashMapElement<TKey, TValue> *before = after->prev; + if (before == nullptr) { + head_element = inserting; + } else { + before->next = inserting; + } + after->prev = inserting; + // Point `inserting` to its new surroundings. + inserting->prev = before; + inserting->next = after; + } + inserting = next; + } + } + TValue &get(const TKey &p_key) { uint32_t pos = 0; bool exists = _lookup_pos(p_key, pos); diff --git a/core/templates/hashfuncs.h b/core/templates/hashfuncs.h index fc7a78bcf5..21eef10297 100644 --- a/core/templates/hashfuncs.h +++ b/core/templates/hashfuncs.h @@ -32,10 +32,17 @@ #define HASHFUNCS_H #include "core/math/aabb.h" +#include "core/math/basis.h" +#include "core/math/color.h" #include "core/math/math_defs.h" #include "core/math/math_funcs.h" +#include "core/math/plane.h" +#include "core/math/projection.h" +#include "core/math/quaternion.h" #include "core/math/rect2.h" #include "core/math/rect2i.h" +#include "core/math/transform_2d.h" +#include "core/math/transform_3d.h" #include "core/math/vector2.h" #include "core/math/vector2i.h" #include "core/math/vector3.h" @@ -414,6 +421,13 @@ struct HashMapComparatorDefault<double> { }; template <> +struct HashMapComparatorDefault<Color> { + static bool compare(const Color &p_lhs, const Color &p_rhs) { + return ((p_lhs.r == p_rhs.r) || (Math::is_nan(p_lhs.r) && Math::is_nan(p_rhs.r))) && ((p_lhs.g == p_rhs.g) || (Math::is_nan(p_lhs.g) && Math::is_nan(p_rhs.g))) && ((p_lhs.b == p_rhs.b) || (Math::is_nan(p_lhs.b) && Math::is_nan(p_rhs.b))) && ((p_lhs.a == p_rhs.a) || (Math::is_nan(p_lhs.a) && Math::is_nan(p_rhs.a))); + } +}; + +template <> struct HashMapComparatorDefault<Vector2> { static bool compare(const Vector2 &p_lhs, const Vector2 &p_rhs) { return ((p_lhs.x == p_rhs.x) || (Math::is_nan(p_lhs.x) && Math::is_nan(p_rhs.x))) && ((p_lhs.y == p_rhs.y) || (Math::is_nan(p_lhs.y) && Math::is_nan(p_rhs.y))); @@ -427,6 +441,87 @@ struct HashMapComparatorDefault<Vector3> { } }; +template <> +struct HashMapComparatorDefault<Vector4> { + static bool compare(const Vector4 &p_lhs, const Vector4 &p_rhs) { + return ((p_lhs.x == p_rhs.x) || (Math::is_nan(p_lhs.x) && Math::is_nan(p_rhs.x))) && ((p_lhs.y == p_rhs.y) || (Math::is_nan(p_lhs.y) && Math::is_nan(p_rhs.y))) && ((p_lhs.z == p_rhs.z) || (Math::is_nan(p_lhs.z) && Math::is_nan(p_rhs.z))) && ((p_lhs.w == p_rhs.w) || (Math::is_nan(p_lhs.w) && Math::is_nan(p_rhs.w))); + } +}; + +template <> +struct HashMapComparatorDefault<Rect2> { + static bool compare(const Rect2 &p_lhs, const Rect2 &p_rhs) { + return HashMapComparatorDefault<Vector2>().compare(p_lhs.position, p_rhs.position) && HashMapComparatorDefault<Vector2>().compare(p_lhs.size, p_rhs.size); + } +}; + +template <> +struct HashMapComparatorDefault<AABB> { + static bool compare(const AABB &p_lhs, const AABB &p_rhs) { + return HashMapComparatorDefault<Vector3>().compare(p_lhs.position, p_rhs.position) && HashMapComparatorDefault<Vector3>().compare(p_lhs.size, p_rhs.size); + } +}; + +template <> +struct HashMapComparatorDefault<Plane> { + static bool compare(const Plane &p_lhs, const Plane &p_rhs) { + return HashMapComparatorDefault<Vector3>().compare(p_lhs.normal, p_rhs.normal) && ((p_lhs.d == p_rhs.d) || (Math::is_nan(p_lhs.d) && Math::is_nan(p_rhs.d))); + } +}; + +template <> +struct HashMapComparatorDefault<Transform2D> { + static bool compare(const Transform2D &p_lhs, const Transform2D &p_rhs) { + for (int i = 0; i < 3; ++i) { + if (!HashMapComparatorDefault<Vector2>().compare(p_lhs.columns[i], p_rhs.columns[i])) { + return false; + } + } + + return true; + } +}; + +template <> +struct HashMapComparatorDefault<Basis> { + static bool compare(const Basis &p_lhs, const Basis &p_rhs) { + for (int i = 0; i < 3; ++i) { + if (!HashMapComparatorDefault<Vector3>().compare(p_lhs.rows[i], p_rhs.rows[i])) { + return false; + } + } + + return true; + } +}; + +template <> +struct HashMapComparatorDefault<Transform3D> { + static bool compare(const Transform3D &p_lhs, const Transform3D &p_rhs) { + return HashMapComparatorDefault<Basis>().compare(p_lhs.basis, p_rhs.basis) && HashMapComparatorDefault<Vector3>().compare(p_lhs.origin, p_rhs.origin); + } +}; + +template <> +struct HashMapComparatorDefault<Projection> { + static bool compare(const Projection &p_lhs, const Projection &p_rhs) { + for (int i = 0; i < 4; ++i) { + if (!HashMapComparatorDefault<Vector4>().compare(p_lhs.columns[i], p_rhs.columns[i])) { + return false; + } + } + + return true; + } +}; + +template <> +struct HashMapComparatorDefault<Quaternion> { + static bool compare(const Quaternion &p_lhs, const Quaternion &p_rhs) { + return ((p_lhs.x == p_rhs.x) || (Math::is_nan(p_lhs.x) && Math::is_nan(p_rhs.x))) && ((p_lhs.y == p_rhs.y) || (Math::is_nan(p_lhs.y) && Math::is_nan(p_rhs.y))) && ((p_lhs.z == p_rhs.z) || (Math::is_nan(p_lhs.z) && Math::is_nan(p_rhs.z))) && ((p_lhs.w == p_rhs.w) || (Math::is_nan(p_lhs.w) && Math::is_nan(p_rhs.w))); + } +}; + constexpr uint32_t HASH_TABLE_SIZE_MAX = 29; inline constexpr uint32_t hash_table_size_primes[HASH_TABLE_SIZE_MAX] = { diff --git a/core/templates/rid_owner.h b/core/templates/rid_owner.h index 537413e2ba..4200159054 100644 --- a/core/templates/rid_owner.h +++ b/core/templates/rid_owner.h @@ -32,7 +32,7 @@ #define RID_OWNER_H #include "core/os/memory.h" -#include "core/os/spin_lock.h" +#include "core/os/mutex.h" #include "core/string/print_string.h" #include "core/templates/hash_set.h" #include "core/templates/list.h" @@ -69,42 +69,54 @@ public: template <typename T, bool THREAD_SAFE = false> class RID_Alloc : public RID_AllocBase { - T **chunks = nullptr; + struct Chunk { + T data; + uint32_t validator; + }; + Chunk **chunks = nullptr; uint32_t **free_list_chunks = nullptr; - uint32_t **validator_chunks = nullptr; uint32_t elements_in_chunk; uint32_t max_alloc = 0; uint32_t alloc_count = 0; + uint32_t chunk_limit = 0; const char *description = nullptr; - mutable SpinLock spin_lock; + mutable Mutex mutex; _FORCE_INLINE_ RID _allocate_rid() { if constexpr (THREAD_SAFE) { - spin_lock.lock(); + mutex.lock(); } if (alloc_count == max_alloc) { //allocate a new chunk uint32_t chunk_count = alloc_count == 0 ? 0 : (max_alloc / elements_in_chunk); + if (THREAD_SAFE && chunk_count == chunk_limit) { + mutex.unlock(); + if (description != nullptr) { + ERR_FAIL_V_MSG(RID(), vformat("Element limit for RID of type '%s' reached.", String(description))); + } else { + ERR_FAIL_V_MSG(RID(), "Element limit reached."); + } + } //grow chunks - chunks = (T **)memrealloc(chunks, sizeof(T *) * (chunk_count + 1)); - chunks[chunk_count] = (T *)memalloc(sizeof(T) * elements_in_chunk); //but don't initialize - - //grow validators - validator_chunks = (uint32_t **)memrealloc(validator_chunks, sizeof(uint32_t *) * (chunk_count + 1)); - validator_chunks[chunk_count] = (uint32_t *)memalloc(sizeof(uint32_t) * elements_in_chunk); + if constexpr (!THREAD_SAFE) { + chunks = (Chunk **)memrealloc(chunks, sizeof(Chunk *) * (chunk_count + 1)); + } + chunks[chunk_count] = (Chunk *)memalloc(sizeof(Chunk) * elements_in_chunk); //but don't initialize //grow free lists - free_list_chunks = (uint32_t **)memrealloc(free_list_chunks, sizeof(uint32_t *) * (chunk_count + 1)); + if constexpr (!THREAD_SAFE) { + free_list_chunks = (uint32_t **)memrealloc(free_list_chunks, sizeof(uint32_t *) * (chunk_count + 1)); + } free_list_chunks[chunk_count] = (uint32_t *)memalloc(sizeof(uint32_t) * elements_in_chunk); //initialize for (uint32_t i = 0; i < elements_in_chunk; i++) { // Don't initialize chunk. - validator_chunks[chunk_count][i] = 0xFFFFFFFF; + chunks[chunk_count][i].validator = 0xFFFFFFFF; free_list_chunks[chunk_count][i] = alloc_count + i; } @@ -122,14 +134,13 @@ class RID_Alloc : public RID_AllocBase { id <<= 32; id |= free_index; - validator_chunks[free_chunk][free_element] = validator; - - validator_chunks[free_chunk][free_element] |= 0x80000000; //mark uninitialized bit + chunks[free_chunk][free_element].validator = validator; + chunks[free_chunk][free_element].validator |= 0x80000000; //mark uninitialized bit alloc_count++; if constexpr (THREAD_SAFE) { - spin_lock.unlock(); + mutex.unlock(); } return _make_from_id(id); @@ -156,16 +167,10 @@ public: if (p_rid == RID()) { return nullptr; } - if constexpr (THREAD_SAFE) { - spin_lock.lock(); - } uint64_t id = p_rid.get_id(); uint32_t idx = uint32_t(id & 0xFFFFFFFF); if (unlikely(idx >= max_alloc)) { - if constexpr (THREAD_SAFE) { - spin_lock.unlock(); - } return nullptr; } @@ -174,38 +179,26 @@ public: uint32_t validator = uint32_t(id >> 32); + Chunk &c = chunks[idx_chunk][idx_element]; if (unlikely(p_initialize)) { - if (unlikely(!(validator_chunks[idx_chunk][idx_element] & 0x80000000))) { - if constexpr (THREAD_SAFE) { - spin_lock.unlock(); - } + if (unlikely(!(c.validator & 0x80000000))) { ERR_FAIL_V_MSG(nullptr, "Initializing already initialized RID"); } - if (unlikely((validator_chunks[idx_chunk][idx_element] & 0x7FFFFFFF) != validator)) { - if constexpr (THREAD_SAFE) { - spin_lock.unlock(); - } + if (unlikely((c.validator & 0x7FFFFFFF) != validator)) { ERR_FAIL_V_MSG(nullptr, "Attempting to initialize the wrong RID"); } - validator_chunks[idx_chunk][idx_element] &= 0x7FFFFFFF; //initialized + c.validator &= 0x7FFFFFFF; //initialized - } else if (unlikely(validator_chunks[idx_chunk][idx_element] != validator)) { - if constexpr (THREAD_SAFE) { - spin_lock.unlock(); - } - if ((validator_chunks[idx_chunk][idx_element] & 0x80000000) && validator_chunks[idx_chunk][idx_element] != 0xFFFFFFFF) { + } else if (unlikely(c.validator != validator)) { + if ((c.validator & 0x80000000) && c.validator != 0xFFFFFFFF) { ERR_FAIL_V_MSG(nullptr, "Attempting to use an uninitialized RID"); } return nullptr; } - T *ptr = &chunks[idx_chunk][idx_element]; - - if constexpr (THREAD_SAFE) { - spin_lock.unlock(); - } + T *ptr = &c.data; return ptr; } @@ -222,14 +215,14 @@ public: _FORCE_INLINE_ bool owns(const RID &p_rid) const { if constexpr (THREAD_SAFE) { - spin_lock.lock(); + mutex.lock(); } uint64_t id = p_rid.get_id(); uint32_t idx = uint32_t(id & 0xFFFFFFFF); if (unlikely(idx >= max_alloc)) { if constexpr (THREAD_SAFE) { - spin_lock.unlock(); + mutex.unlock(); } return false; } @@ -239,10 +232,10 @@ public: uint32_t validator = uint32_t(id >> 32); - bool owned = (validator != 0x7FFFFFFF) && (validator_chunks[idx_chunk][idx_element] & 0x7FFFFFFF) == validator; + bool owned = (validator != 0x7FFFFFFF) && (chunks[idx_chunk][idx_element].validator & 0x7FFFFFFF) == validator; if constexpr (THREAD_SAFE) { - spin_lock.unlock(); + mutex.unlock(); } return owned; @@ -250,14 +243,14 @@ public: _FORCE_INLINE_ void free(const RID &p_rid) { if constexpr (THREAD_SAFE) { - spin_lock.lock(); + mutex.lock(); } uint64_t id = p_rid.get_id(); uint32_t idx = uint32_t(id & 0xFFFFFFFF); if (unlikely(idx >= max_alloc)) { if constexpr (THREAD_SAFE) { - spin_lock.unlock(); + mutex.unlock(); } ERR_FAIL(); } @@ -266,26 +259,26 @@ public: uint32_t idx_element = idx % elements_in_chunk; uint32_t validator = uint32_t(id >> 32); - if (unlikely(validator_chunks[idx_chunk][idx_element] & 0x80000000)) { + if (unlikely(chunks[idx_chunk][idx_element].validator & 0x80000000)) { if constexpr (THREAD_SAFE) { - spin_lock.unlock(); + mutex.unlock(); } - ERR_FAIL_MSG("Attempted to free an uninitialized or invalid RID."); - } else if (unlikely(validator_chunks[idx_chunk][idx_element] != validator)) { + ERR_FAIL_MSG("Attempted to free an uninitialized or invalid RID"); + } else if (unlikely(chunks[idx_chunk][idx_element].validator != validator)) { if constexpr (THREAD_SAFE) { - spin_lock.unlock(); + mutex.unlock(); } ERR_FAIL(); } - chunks[idx_chunk][idx_element].~T(); - validator_chunks[idx_chunk][idx_element] = 0xFFFFFFFF; // go invalid + chunks[idx_chunk][idx_element].data.~T(); + chunks[idx_chunk][idx_element].validator = 0xFFFFFFFF; // go invalid alloc_count--; free_list_chunks[alloc_count / elements_in_chunk][alloc_count % elements_in_chunk] = idx; if constexpr (THREAD_SAFE) { - spin_lock.unlock(); + mutex.unlock(); } } @@ -294,34 +287,35 @@ public: } void get_owned_list(List<RID> *p_owned) const { if constexpr (THREAD_SAFE) { - spin_lock.lock(); + mutex.lock(); } for (size_t i = 0; i < max_alloc; i++) { - uint64_t validator = validator_chunks[i / elements_in_chunk][i % elements_in_chunk]; + uint64_t validator = chunks[i / elements_in_chunk][i % elements_in_chunk].validator; if (validator != 0xFFFFFFFF) { p_owned->push_back(_make_from_id((validator << 32) | i)); } } if constexpr (THREAD_SAFE) { - spin_lock.unlock(); + mutex.unlock(); } } //used for fast iteration in the elements or RIDs void fill_owned_buffer(RID *p_rid_buffer) const { if constexpr (THREAD_SAFE) { - spin_lock.lock(); + mutex.lock(); } uint32_t idx = 0; for (size_t i = 0; i < max_alloc; i++) { - uint64_t validator = validator_chunks[i / elements_in_chunk][i % elements_in_chunk]; + uint64_t validator = chunks[i / elements_in_chunk][i % elements_in_chunk].validator; if (validator != 0xFFFFFFFF) { p_rid_buffer[idx] = _make_from_id((validator << 32) | i); idx++; } } + if constexpr (THREAD_SAFE) { - spin_lock.unlock(); + mutex.unlock(); } } @@ -329,8 +323,13 @@ public: description = p_descrption; } - RID_Alloc(uint32_t p_target_chunk_byte_size = 65536) { + RID_Alloc(uint32_t p_target_chunk_byte_size = 65536, uint32_t p_maximum_number_of_elements = 262144) { elements_in_chunk = sizeof(T) > p_target_chunk_byte_size ? 1 : (p_target_chunk_byte_size / sizeof(T)); + if constexpr (THREAD_SAFE) { + chunk_limit = (p_maximum_number_of_elements / elements_in_chunk) + 1; + chunks = (Chunk **)memalloc(sizeof(Chunk *) * chunk_limit); + free_list_chunks = (uint32_t **)memalloc(sizeof(uint32_t *) * chunk_limit); + } } ~RID_Alloc() { @@ -339,12 +338,12 @@ public: alloc_count, description ? description : typeid(T).name())); for (size_t i = 0; i < max_alloc; i++) { - uint64_t validator = validator_chunks[i / elements_in_chunk][i % elements_in_chunk]; + uint64_t validator = chunks[i / elements_in_chunk][i % elements_in_chunk].validator; if (validator & 0x80000000) { continue; //uninitialized } if (validator != 0xFFFFFFFF) { - chunks[i / elements_in_chunk][i % elements_in_chunk].~T(); + chunks[i / elements_in_chunk][i % elements_in_chunk].data.~T(); } } } @@ -352,14 +351,12 @@ public: uint32_t chunk_count = max_alloc / elements_in_chunk; for (uint32_t i = 0; i < chunk_count; i++) { memfree(chunks[i]); - memfree(validator_chunks[i]); memfree(free_list_chunks[i]); } if (chunks) { memfree(chunks); memfree(free_list_chunks); - memfree(validator_chunks); } } }; @@ -419,8 +416,8 @@ public: alloc.set_description(p_descrption); } - RID_PtrOwner(uint32_t p_target_chunk_byte_size = 65536) : - alloc(p_target_chunk_byte_size) {} + RID_PtrOwner(uint32_t p_target_chunk_byte_size = 65536, uint32_t p_maximum_number_of_elements = 262144) : + alloc(p_target_chunk_byte_size, p_maximum_number_of_elements) {} }; template <typename T, bool THREAD_SAFE = false> @@ -473,8 +470,8 @@ public: void set_description(const char *p_descrption) { alloc.set_description(p_descrption); } - RID_Owner(uint32_t p_target_chunk_byte_size = 65536) : - alloc(p_target_chunk_byte_size) {} + RID_Owner(uint32_t p_target_chunk_byte_size = 65536, uint32_t p_maximum_number_of_elements = 262144) : + alloc(p_target_chunk_byte_size, p_maximum_number_of_elements) {} }; #endif // RID_OWNER_H diff --git a/core/variant/callable.cpp b/core/variant/callable.cpp index bb2d0313f6..5ce90cd8ff 100644 --- a/core/variant/callable.cpp +++ b/core/variant/callable.cpp @@ -315,31 +315,32 @@ bool Callable::operator<(const Callable &p_callable) const { } void Callable::operator=(const Callable &p_callable) { + CallableCustom *cleanup_ref = nullptr; if (is_custom()) { if (p_callable.is_custom()) { if (custom == p_callable.custom) { return; } } - - if (custom->ref_count.unref()) { - memdelete(custom); - custom = nullptr; - } + cleanup_ref = custom; + custom = nullptr; } if (p_callable.is_custom()) { method = StringName(); - if (!p_callable.custom->ref_count.ref()) { - object = 0; - } else { - object = 0; + object = 0; + if (p_callable.custom->ref_count.ref()) { custom = p_callable.custom; } } else { method = p_callable.method; object = p_callable.object; } + + if (cleanup_ref != nullptr && cleanup_ref->ref_count.unref()) { + memdelete(cleanup_ref); + } + cleanup_ref = nullptr; } Callable::operator String() const { diff --git a/core/variant/dictionary.cpp b/core/variant/dictionary.cpp index 2db754438f..501ca69205 100644 --- a/core/variant/dictionary.cpp +++ b/core/variant/dictionary.cpp @@ -294,6 +294,11 @@ void Dictionary::clear() { _p->variant_map.clear(); } +void Dictionary::sort() { + ERR_FAIL_COND_MSG(_p->read_only, "Dictionary is in read-only state."); + _p->variant_map.sort(); +} + void Dictionary::merge(const Dictionary &p_dictionary, bool p_overwrite) { ERR_FAIL_COND_MSG(_p->read_only, "Dictionary is in read-only state."); for (const KeyValue<Variant, Variant> &E : p_dictionary._p->variant_map) { diff --git a/core/variant/dictionary.h b/core/variant/dictionary.h index 5f3ce40219..bbfb5b3083 100644 --- a/core/variant/dictionary.h +++ b/core/variant/dictionary.h @@ -64,6 +64,7 @@ public: int size() const; bool is_empty() const; void clear(); + void sort(); void merge(const Dictionary &p_dictionary, bool p_overwrite = false); Dictionary merged(const Dictionary &p_dictionary, bool p_overwrite = false) const; diff --git a/core/variant/variant.cpp b/core/variant/variant.cpp index 186643b024..e2865a06be 100644 --- a/core/variant/variant.cpp +++ b/core/variant/variant.cpp @@ -1072,17 +1072,69 @@ bool Variant::is_null() const { } } +void Variant::ObjData::ref(const ObjData &p_from) { + // Mirrors Ref::ref in refcounted.h + if (p_from.id == id) { + return; + } + + ObjData cleanup_ref = *this; + + *this = p_from; + if (id.is_ref_counted()) { + RefCounted *reference = static_cast<RefCounted *>(obj); + // Assuming reference is not null because id.is_ref_counted() was true. + if (!reference->reference()) { + *this = ObjData(); + } + } + + cleanup_ref.unref(); +} + +void Variant::ObjData::ref_pointer(Object *p_object) { + // Mirrors Ref::ref_pointer in refcounted.h + if (p_object == obj) { + return; + } + + ObjData cleanup_ref = *this; + + if (p_object) { + *this = ObjData{ p_object->get_instance_id(), p_object }; + if (p_object->is_ref_counted()) { + RefCounted *reference = static_cast<RefCounted *>(p_object); + if (!reference->init_ref()) { + *this = ObjData(); + } + } + } else { + *this = ObjData(); + } + + cleanup_ref.unref(); +} + +void Variant::ObjData::unref() { + // Mirrors Ref::unref in refcounted.h + if (id.is_ref_counted()) { + RefCounted *reference = static_cast<RefCounted *>(obj); + // Assuming reference is not null because id.is_ref_counted() was true. + if (reference->unreference()) { + memdelete(reference); + } + } + *this = ObjData(); +} + void Variant::reference(const Variant &p_variant) { - switch (type) { - case NIL: - case BOOL: - case INT: - case FLOAT: - break; - default: - clear(); + if (type == OBJECT && p_variant.type == OBJECT) { + _get_obj().ref(p_variant._get_obj()); + return; } + clear(); + type = p_variant.type; switch (p_variant.type) { @@ -1165,18 +1217,7 @@ void Variant::reference(const Variant &p_variant) { } break; case OBJECT: { memnew_placement(_data._mem, ObjData); - - if (p_variant._get_obj().obj && p_variant._get_obj().id.is_ref_counted()) { - RefCounted *ref_counted = static_cast<RefCounted *>(p_variant._get_obj().obj); - if (!ref_counted->reference()) { - _get_obj().obj = nullptr; - _get_obj().id = ObjectID(); - break; - } - } - - _get_obj().obj = const_cast<Object *>(p_variant._get_obj().obj); - _get_obj().id = p_variant._get_obj().id; + _get_obj().ref(p_variant._get_obj()); } break; case CALLABLE: { memnew_placement(_data._mem, Callable(*reinterpret_cast<const Callable *>(p_variant._data._mem))); @@ -1375,15 +1416,7 @@ void Variant::_clear_internal() { reinterpret_cast<NodePath *>(_data._mem)->~NodePath(); } break; case OBJECT: { - if (_get_obj().id.is_ref_counted()) { - // We are safe that there is a reference here. - RefCounted *ref_counted = static_cast<RefCounted *>(_get_obj().obj); - if (ref_counted->unreference()) { - memdelete(ref_counted); - } - } - _get_obj().obj = nullptr; - _get_obj().id = ObjectID(); + _get_obj().unref(); } break; case RID: { // Not much need probably. @@ -2589,24 +2622,8 @@ Variant::Variant(const ::RID &p_rid) : Variant::Variant(const Object *p_object) : type(OBJECT) { - memnew_placement(_data._mem, ObjData); - - if (p_object) { - if (p_object->is_ref_counted()) { - RefCounted *ref_counted = const_cast<RefCounted *>(static_cast<const RefCounted *>(p_object)); - if (!ref_counted->init_ref()) { - _get_obj().obj = nullptr; - _get_obj().id = ObjectID(); - return; - } - } - - _get_obj().obj = const_cast<Object *>(p_object); - _get_obj().id = p_object->get_instance_id(); - } else { - _get_obj().obj = nullptr; - _get_obj().id = ObjectID(); - } + _get_obj() = ObjData(); + _get_obj().ref_pointer(const_cast<Object *>(p_object)); } Variant::Variant(const Callable &p_callable) : @@ -2828,26 +2845,7 @@ void Variant::operator=(const Variant &p_variant) { *reinterpret_cast<::RID *>(_data._mem) = *reinterpret_cast<const ::RID *>(p_variant._data._mem); } break; case OBJECT: { - if (_get_obj().id.is_ref_counted()) { - //we are safe that there is a reference here - RefCounted *ref_counted = static_cast<RefCounted *>(_get_obj().obj); - if (ref_counted->unreference()) { - memdelete(ref_counted); - } - } - - if (p_variant._get_obj().obj && p_variant._get_obj().id.is_ref_counted()) { - RefCounted *ref_counted = static_cast<RefCounted *>(p_variant._get_obj().obj); - if (!ref_counted->reference()) { - _get_obj().obj = nullptr; - _get_obj().id = ObjectID(); - break; - } - } - - _get_obj().obj = const_cast<Object *>(p_variant._get_obj().obj); - _get_obj().id = p_variant._get_obj().id; - + _get_obj().ref(p_variant._get_obj()); } break; case CALLABLE: { *reinterpret_cast<Callable *>(_data._mem) = *reinterpret_cast<const Callable *>(p_variant._data._mem); diff --git a/core/variant/variant.h b/core/variant/variant.h index d4e4b330cd..3b1924e8ea 100644 --- a/core/variant/variant.h +++ b/core/variant/variant.h @@ -62,6 +62,10 @@ #include "core/variant/dictionary.h" class Object; +class RefCounted; + +template <typename T> +class Ref; struct PropertyInfo; struct MethodInfo; @@ -175,6 +179,20 @@ private: struct ObjData { ObjectID id; Object *obj = nullptr; + + void ref(const ObjData &p_from); + void ref_pointer(Object *p_object); + void ref_pointer(RefCounted *p_object); + void unref(); + + template <typename T> + _ALWAYS_INLINE_ void ref(const Ref<T> &p_from) { + if (p_from.is_valid()) { + ref(ObjData{ p_from->get_instance_id(), p_from.ptr() }); + } else { + unref(); + } + } }; /* array helpers */ @@ -836,6 +854,19 @@ struct StringLikeVariantComparator { static bool compare(const Variant &p_lhs, const Variant &p_rhs); }; +struct StringLikeVariantOrder { + static _ALWAYS_INLINE_ bool compare(const Variant &p_lhs, const Variant &p_rhs) { + if (p_lhs.is_string() && p_rhs.is_string()) { + return p_lhs.operator String() < p_rhs.operator String(); + } + return p_lhs < p_rhs; + } + + _ALWAYS_INLINE_ bool operator()(const Variant &p_lhs, const Variant &p_rhs) const { + return compare(p_lhs, p_rhs); + } +}; + Variant::ObjData &Variant::_get_obj() { return *reinterpret_cast<ObjData *>(&_data._mem[0]); } diff --git a/core/variant/variant_call.cpp b/core/variant/variant_call.cpp index ab33c9db55..29e11462c9 100644 --- a/core/variant/variant_call.cpp +++ b/core/variant/variant_call.cpp @@ -2272,6 +2272,7 @@ static void _register_variant_builtin_methods_misc() { bind_method(Dictionary, is_empty, sarray(), varray()); bind_method(Dictionary, clear, sarray(), varray()); bind_method(Dictionary, assign, sarray("dictionary"), varray()); + bind_method(Dictionary, sort, sarray(), varray()); bind_method(Dictionary, merge, sarray("dictionary", "overwrite"), varray(false)); bind_method(Dictionary, merged, sarray("dictionary", "overwrite"), varray(false)); bind_method(Dictionary, has, sarray("key"), varray()); diff --git a/core/variant/variant_construct.cpp b/core/variant/variant_construct.cpp index fb75a874e7..6c37d5e4b7 100644 --- a/core/variant/variant_construct.cpp +++ b/core/variant/variant_construct.cpp @@ -323,36 +323,6 @@ String Variant::get_constructor_argument_name(Variant::Type p_type, int p_constr return construct_data[p_type][p_constructor].arg_names[p_argument]; } -void VariantInternal::refcounted_object_assign(Variant *v, const RefCounted *rc) { - if (!rc || !const_cast<RefCounted *>(rc)->init_ref()) { - v->_get_obj().obj = nullptr; - v->_get_obj().id = ObjectID(); - return; - } - - v->_get_obj().obj = const_cast<RefCounted *>(rc); - v->_get_obj().id = rc->get_instance_id(); -} - -void VariantInternal::object_assign(Variant *v, const Object *o) { - if (o) { - if (o->is_ref_counted()) { - RefCounted *ref_counted = const_cast<RefCounted *>(static_cast<const RefCounted *>(o)); - if (!ref_counted->init_ref()) { - v->_get_obj().obj = nullptr; - v->_get_obj().id = ObjectID(); - return; - } - } - - v->_get_obj().obj = const_cast<Object *>(o); - v->_get_obj().id = o->get_instance_id(); - } else { - v->_get_obj().obj = nullptr; - v->_get_obj().id = ObjectID(); - } -} - void Variant::get_constructor_list(Type p_type, List<MethodInfo> *r_list) { ERR_FAIL_INDEX(p_type, Variant::VARIANT_MAX); diff --git a/core/variant/variant_construct.h b/core/variant/variant_construct.h index 68210a9451..f625419da7 100644 --- a/core/variant/variant_construct.h +++ b/core/variant/variant_construct.h @@ -156,14 +156,14 @@ public: if (p_args[0]->get_type() == Variant::NIL) { VariantInternal::clear(&r_ret); VariantTypeChanger<Object *>::change(&r_ret); - VariantInternal::object_assign_null(&r_ret); + VariantInternal::object_reset_data(&r_ret); r_error.error = Callable::CallError::CALL_OK; } else if (p_args[0]->get_type() == Variant::OBJECT) { - VariantInternal::clear(&r_ret); VariantTypeChanger<Object *>::change(&r_ret); VariantInternal::object_assign(&r_ret, p_args[0]); r_error.error = Callable::CallError::CALL_OK; } else { + VariantInternal::clear(&r_ret); r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = 0; r_error.expected = Variant::OBJECT; @@ -171,7 +171,6 @@ public: } static inline void validated_construct(Variant *r_ret, const Variant **p_args) { - VariantInternal::clear(r_ret); VariantTypeChanger<Object *>::change(r_ret); VariantInternal::object_assign(r_ret, p_args[0]); } @@ -203,13 +202,13 @@ public: VariantInternal::clear(&r_ret); VariantTypeChanger<Object *>::change(&r_ret); - VariantInternal::object_assign_null(&r_ret); + VariantInternal::object_reset_data(&r_ret); } static inline void validated_construct(Variant *r_ret, const Variant **p_args) { VariantInternal::clear(r_ret); VariantTypeChanger<Object *>::change(r_ret); - VariantInternal::object_assign_null(r_ret); + VariantInternal::object_reset_data(r_ret); } static void ptr_construct(void *base, const void **p_args) { PtrConstruct<Object *>::construct(nullptr, base); diff --git a/core/variant/variant_internal.h b/core/variant/variant_internal.h index 58a45c0a1f..58652d26e0 100644 --- a/core/variant/variant_internal.h +++ b/core/variant/variant_internal.h @@ -220,7 +220,7 @@ public: // Should be in the same order as Variant::Type for consistency. // Those primitive and vector types don't need an `init_` method: // Nil, bool, float, Vector2/i, Rect2/i, Vector3/i, Plane, Quat, RID. - // Object is a special case, handled via `object_assign_null`. + // Object is a special case, handled via `object_reset_data`. _FORCE_INLINE_ static void init_string(Variant *v) { memnew_placement(v->_data._mem, String); v->type = Variant::STRING; @@ -319,7 +319,7 @@ public: v->type = Variant::PACKED_VECTOR4_ARRAY; } _FORCE_INLINE_ static void init_object(Variant *v) { - object_assign_null(v); + object_reset_data(v); v->type = Variant::OBJECT; } @@ -327,19 +327,28 @@ public: v->clear(); } - static void object_assign(Variant *v, const Object *o); // Needs RefCounted, so it's implemented elsewhere. - static void refcounted_object_assign(Variant *v, const RefCounted *rc); + _FORCE_INLINE_ static void object_assign(Variant *v, const Variant *vo) { + v->_get_obj().ref(vo->_get_obj()); + } + + _FORCE_INLINE_ static void object_assign(Variant *v, Object *o) { + v->_get_obj().ref_pointer(o); + } - _FORCE_INLINE_ static void object_assign(Variant *v, const Variant *o) { - object_assign(v, o->_get_obj().obj); + _FORCE_INLINE_ static void object_assign(Variant *v, const Object *o) { + v->_get_obj().ref_pointer(const_cast<Object *>(o)); + } + + template <typename T> + _FORCE_INLINE_ static void object_assign(Variant *v, const Ref<T> &r) { + v->_get_obj().ref(r); } - _FORCE_INLINE_ static void object_assign_null(Variant *v) { - v->_get_obj().obj = nullptr; - v->_get_obj().id = ObjectID(); + _FORCE_INLINE_ static void object_reset_data(Variant *v) { + v->_get_obj() = Variant::ObjData(); } - static void update_object_id(Variant *v) { + _FORCE_INLINE_ static void update_object_id(Variant *v) { const Object *o = v->_get_obj().obj; if (o) { v->_get_obj().id = o->get_instance_id(); diff --git a/core/variant/variant_parser.cpp b/core/variant/variant_parser.cpp index f5f96456d3..f05b9cd83a 100644 --- a/core/variant/variant_parser.cpp +++ b/core/variant/variant_parser.cpp @@ -2245,7 +2245,7 @@ Error VariantWriter::write(const Variant &p_variant, StoreStringFunc p_store_str } else { List<Variant> keys; dict.get_key_list(&keys); - keys.sort(); + keys.sort_custom<StringLikeVariantOrder>(); if (keys.is_empty()) { // Avoid unnecessary line break. |