diff options
Diffstat (limited to 'core')
41 files changed, 840 insertions, 388 deletions
diff --git a/core/config/engine.cpp b/core/config/engine.cpp index 9cdc21fe8e..d77c913314 100644 --- a/core/config/engine.cpp +++ b/core/config/engine.cpp @@ -267,6 +267,14 @@ bool Engine::is_extra_gpu_memory_tracking_enabled() const { return extra_gpu_memory_tracking; } +void Engine::set_print_to_stdout(bool p_enabled) { + CoreGlobals::print_line_enabled = p_enabled; +} + +bool Engine::is_printing_to_stdout() const { + return CoreGlobals::print_line_enabled; +} + void Engine::set_print_error_messages(bool p_enabled) { CoreGlobals::print_error_enabled = p_enabled; } diff --git a/core/config/engine.h b/core/config/engine.h index f858eba328..a0b1ffa981 100644 --- a/core/config/engine.h +++ b/core/config/engine.h @@ -128,6 +128,9 @@ public: void set_time_scale(double p_scale); double get_time_scale() const; + void set_print_to_stdout(bool p_enabled); + bool is_printing_to_stdout() const; + void set_print_error_messages(bool p_enabled); bool is_printing_error_messages() const; void print_header(const String &p_string) const; diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp index 32f36e01f9..7951ee9edd 100644 --- a/core/config/project_settings.cpp +++ b/core/config/project_settings.cpp @@ -1016,7 +1016,7 @@ Error ProjectSettings::save_custom(const String &p_path, const CustomMap &p_cust } } // Check for the existence of a csproj file. - if (_csproj_exists(p_path.get_base_dir())) { + if (_csproj_exists(get_resource_path())) { // If there is a csproj file, add the C# feature if it doesn't already exist. if (!project_features.has("C#")) { project_features.append("C#"); @@ -1574,6 +1574,7 @@ ProjectSettings::ProjectSettings() { ProjectSettings::ProjectSettings(const String &p_path) { if (load_custom(p_path) == OK) { + resource_path = p_path.get_base_dir(); project_loaded = true; } } diff --git a/core/core_bind.cpp b/core/core_bind.cpp index b27981d56b..891e3a28c9 100644 --- a/core/core_bind.cpp +++ b/core/core_bind.cpp @@ -184,6 +184,10 @@ void ResourceSaver::remove_resource_format_saver(Ref<ResourceFormatSaver> p_form ::ResourceSaver::remove_resource_format_saver(p_format_saver); } +ResourceUID::ID ResourceSaver::get_resource_id_for_path(const String &p_path, bool p_generate) { + return ::ResourceSaver::get_resource_id_for_path(p_path, p_generate); +} + ResourceSaver *ResourceSaver::singleton = nullptr; void ResourceSaver::_bind_methods() { @@ -191,6 +195,7 @@ void ResourceSaver::_bind_methods() { ClassDB::bind_method(D_METHOD("get_recognized_extensions", "type"), &ResourceSaver::get_recognized_extensions); ClassDB::bind_method(D_METHOD("add_resource_format_saver", "format_saver", "at_front"), &ResourceSaver::add_resource_format_saver, DEFVAL(false)); ClassDB::bind_method(D_METHOD("remove_resource_format_saver", "format_saver"), &ResourceSaver::remove_resource_format_saver); + ClassDB::bind_method(D_METHOD("get_resource_id_for_path", "path", "generate"), &ResourceSaver::get_resource_id_for_path, DEFVAL(false)); BIND_BITFIELD_FLAG(FLAG_NONE); BIND_BITFIELD_FLAG(FLAG_RELATIVE_PATHS); @@ -1419,6 +1424,11 @@ Variant ClassDB::instantiate(const StringName &p_class) const { } } +ClassDB::APIType ClassDB::class_get_api_type(const StringName &p_class) const { + ::ClassDB::APIType api_type = ::ClassDB::get_api_type(p_class); + return (APIType)api_type; +} + bool ClassDB::class_has_signal(const StringName &p_class, const StringName &p_signal) const { return ::ClassDB::has_signal(p_class, p_signal); } @@ -1615,7 +1625,7 @@ void ClassDB::get_argument_options(const StringName &p_function, int p_idx, List pf == "class_has_method" || pf == "class_get_method_list" || pf == "class_get_integer_constant_list" || pf == "class_has_integer_constant" || pf == "class_get_integer_constant" || pf == "class_has_enum" || pf == "class_get_enum_list" || pf == "class_get_enum_constants" || pf == "class_get_integer_constant_enum" || - pf == "is_class_enabled" || pf == "is_class_enum_bitfield"); + pf == "is_class_enabled" || pf == "is_class_enum_bitfield" || pf == "class_get_api_type"); } if (first_argument_is_class || pf == "is_parent_class") { for (const String &E : get_class_list()) { @@ -1636,6 +1646,8 @@ void ClassDB::_bind_methods() { ::ClassDB::bind_method(D_METHOD("can_instantiate", "class"), &ClassDB::can_instantiate); ::ClassDB::bind_method(D_METHOD("instantiate", "class"), &ClassDB::instantiate); + ::ClassDB::bind_method(D_METHOD("class_get_api_type", "class"), &ClassDB::class_get_api_type); + ::ClassDB::bind_method(D_METHOD("class_has_signal", "class", "signal"), &ClassDB::class_has_signal); ::ClassDB::bind_method(D_METHOD("class_get_signal", "class", "signal"), &ClassDB::class_get_signal); ::ClassDB::bind_method(D_METHOD("class_get_signal_list", "class", "no_inheritance"), &ClassDB::class_get_signal_list, DEFVAL(false)); @@ -1669,6 +1681,12 @@ void ClassDB::_bind_methods() { ::ClassDB::bind_method(D_METHOD("is_class_enum_bitfield", "class", "enum", "no_inheritance"), &ClassDB::is_class_enum_bitfield, DEFVAL(false)); ::ClassDB::bind_method(D_METHOD("is_class_enabled", "class"), &ClassDB::is_class_enabled); + + BIND_ENUM_CONSTANT(API_CORE); + BIND_ENUM_CONSTANT(API_EDITOR); + BIND_ENUM_CONSTANT(API_EXTENSION); + BIND_ENUM_CONSTANT(API_EDITOR_EXTENSION); + BIND_ENUM_CONSTANT(API_NONE); } } // namespace special @@ -1835,6 +1853,14 @@ String Engine::get_write_movie_path() const { return ::Engine::get_singleton()->get_write_movie_path(); } +void Engine::set_print_to_stdout(bool p_enabled) { + ::Engine::get_singleton()->set_print_to_stdout(p_enabled); +} + +bool Engine::is_printing_to_stdout() const { + return ::Engine::get_singleton()->is_printing_to_stdout(); +} + void Engine::set_print_error_messages(bool p_enabled) { ::Engine::get_singleton()->set_print_error_messages(p_enabled); } @@ -1903,10 +1929,14 @@ void Engine::_bind_methods() { ClassDB::bind_method(D_METHOD("get_write_movie_path"), &Engine::get_write_movie_path); + ClassDB::bind_method(D_METHOD("set_print_to_stdout", "enabled"), &Engine::set_print_to_stdout); + ClassDB::bind_method(D_METHOD("is_printing_to_stdout"), &Engine::is_printing_to_stdout); + ClassDB::bind_method(D_METHOD("set_print_error_messages", "enabled"), &Engine::set_print_error_messages); ClassDB::bind_method(D_METHOD("is_printing_error_messages"), &Engine::is_printing_error_messages); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "print_error_messages"), "set_print_error_messages", "is_printing_error_messages"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "print_to_stdout"), "set_print_to_stdout", "is_printing_to_stdout"); ADD_PROPERTY(PropertyInfo(Variant::INT, "physics_ticks_per_second"), "set_physics_ticks_per_second", "get_physics_ticks_per_second"); ADD_PROPERTY(PropertyInfo(Variant::INT, "max_physics_steps_per_frame"), "set_max_physics_steps_per_frame", "get_max_physics_steps_per_frame"); ADD_PROPERTY(PropertyInfo(Variant::INT, "max_fps"), "set_max_fps", "get_max_fps"); diff --git a/core/core_bind.h b/core/core_bind.h index 7e2686c848..ce0bde3c05 100644 --- a/core/core_bind.h +++ b/core/core_bind.h @@ -116,6 +116,8 @@ public: void add_resource_format_saver(Ref<ResourceFormatSaver> p_format_saver, bool p_at_front); void remove_resource_format_saver(Ref<ResourceFormatSaver> p_format_saver); + ResourceUID::ID get_resource_id_for_path(const String &p_path, bool p_generate = false); + ResourceSaver() { singleton = this; } }; @@ -447,6 +449,14 @@ protected: static void _bind_methods(); public: + enum APIType { + API_CORE, + API_EDITOR, + API_EXTENSION, + API_EDITOR_EXTENSION, + API_NONE, + }; + PackedStringArray get_class_list() const; PackedStringArray get_inheriters_from_class(const StringName &p_class) const; StringName get_parent_class(const StringName &p_class) const; @@ -455,6 +465,7 @@ public: bool can_instantiate(const StringName &p_class) const; Variant instantiate(const StringName &p_class) const; + APIType class_get_api_type(const StringName &p_class) const; bool class_has_signal(const StringName &p_class, const StringName &p_signal) const; Dictionary class_get_signal(const StringName &p_class, const StringName &p_signal) const; TypedArray<Dictionary> class_get_signal_list(const StringName &p_class, bool p_no_inheritance = false) const; @@ -558,6 +569,9 @@ public: // `set_write_movie_path()` is not exposed to the scripting API as changing it at run-time has no effect. String get_write_movie_path() const; + void set_print_to_stdout(bool p_enabled); + bool is_printing_to_stdout() const; + void set_print_error_messages(bool p_enabled); bool is_printing_error_messages() const; @@ -634,4 +648,6 @@ VARIANT_ENUM_CAST(core_bind::Geometry2D::PolyEndType); VARIANT_ENUM_CAST(core_bind::Thread::Priority); +VARIANT_ENUM_CAST(core_bind::special::ClassDB::APIType); + #endif // CORE_BIND_H diff --git a/core/input/input_event.cpp b/core/input/input_event.cpp index de3efa7a3a..905526bbbd 100644 --- a/core/input/input_event.cpp +++ b/core/input/input_event.cpp @@ -1088,7 +1088,7 @@ void InputEventMouseMotion::_bind_methods() { /////////////////////////////////// void InputEventJoypadMotion::set_axis(JoyAxis p_axis) { - ERR_FAIL_COND(p_axis < JoyAxis::LEFT_X || p_axis > JoyAxis::MAX); + ERR_FAIL_COND(p_axis < JoyAxis::INVALID || p_axis > JoyAxis::MAX); axis = p_axis; emit_changed(); diff --git a/core/io/image.cpp b/core/io/image.cpp index fcbe483e38..aa391b77dd 100644 --- a/core/io/image.cpp +++ b/core/io/image.cpp @@ -866,12 +866,10 @@ static void _scale_cubic(const uint8_t *__restrict p_src, uint8_t *__restrict p_ template <int CC, typename T> static void _scale_bilinear(const uint8_t *__restrict p_src, uint8_t *__restrict p_dst, uint32_t p_src_width, uint32_t p_src_height, uint32_t p_dst_width, uint32_t p_dst_height) { - enum { - FRAC_BITS = 8, - FRAC_LEN = (1 << FRAC_BITS), - FRAC_HALF = (FRAC_LEN >> 1), - FRAC_MASK = FRAC_LEN - 1 - }; + constexpr uint32_t FRAC_BITS = 8; + constexpr uint32_t FRAC_LEN = (1 << FRAC_BITS); + constexpr uint32_t FRAC_HALF = (FRAC_LEN >> 1); + constexpr uint32_t FRAC_MASK = FRAC_LEN - 1; for (uint32_t i = 0; i < p_dst_height; i++) { // Add 0.5 in order to interpolate based on pixel center @@ -2751,6 +2749,19 @@ Error Image::compress_from_channels(CompressMode p_mode, UsedChannels p_channels } break; + case COMPRESS_S3TC: { + // BC3 is unsupported currently. + if ((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. + if (result == OK) { + return OK; + } + } + + } break; + default: { } } @@ -3138,6 +3149,7 @@ void (*Image::_image_compress_etc1_func)(Image *) = nullptr; void (*Image::_image_compress_etc2_func)(Image *, Image::UsedChannels) = nullptr; void (*Image::_image_compress_astc_func)(Image *, Image::ASTCFormat) = nullptr; Error (*Image::_image_compress_bptc_rd_func)(Image *, Image::UsedChannels) = nullptr; +Error (*Image::_image_compress_bc_rd_func)(Image *, Image::UsedChannels) = nullptr; void (*Image::_image_decompress_bc)(Image *) = nullptr; void (*Image::_image_decompress_bptc)(Image *) = nullptr; void (*Image::_image_decompress_etc1)(Image *) = nullptr; diff --git a/core/io/image.h b/core/io/image.h index 4461ae71a6..78757246e0 100644 --- a/core/io/image.h +++ b/core/io/image.h @@ -160,6 +160,7 @@ public: static void (*_image_compress_astc_func)(Image *, ASTCFormat p_format); static Error (*_image_compress_bptc_rd_func)(Image *, UsedChannels p_channels); + static Error (*_image_compress_bc_rd_func)(Image *, UsedChannels p_channels); static void (*_image_decompress_bc)(Image *); static void (*_image_decompress_bptc)(Image *); diff --git a/core/io/pck_packer.cpp b/core/io/pck_packer.cpp index a7c715c318..93179d9a11 100644 --- a/core/io/pck_packer.cpp +++ b/core/io/pck_packer.cpp @@ -47,12 +47,12 @@ static int _get_pad(int p_alignment, int p_n) { } void PCKPacker::_bind_methods() { - ClassDB::bind_method(D_METHOD("pck_start", "pck_name", "alignment", "key", "encrypt_directory"), &PCKPacker::pck_start, DEFVAL(32), DEFVAL("0000000000000000000000000000000000000000000000000000000000000000"), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("pck_start", "pck_path", "alignment", "key", "encrypt_directory"), &PCKPacker::pck_start, DEFVAL(32), DEFVAL("0000000000000000000000000000000000000000000000000000000000000000"), DEFVAL(false)); ClassDB::bind_method(D_METHOD("add_file", "pck_path", "source_path", "encrypt"), &PCKPacker::add_file, DEFVAL(false)); ClassDB::bind_method(D_METHOD("flush", "verbose"), &PCKPacker::flush, DEFVAL(false)); } -Error PCKPacker::pck_start(const String &p_file, int p_alignment, const String &p_key, bool p_encrypt_directory) { +Error PCKPacker::pck_start(const String &p_pck_path, int p_alignment, const String &p_key, bool p_encrypt_directory) { ERR_FAIL_COND_V_MSG((p_key.is_empty() || !p_key.is_valid_hex_number(false) || p_key.length() != 64), ERR_CANT_CREATE, "Invalid Encryption Key (must be 64 characters long)."); ERR_FAIL_COND_V_MSG(p_alignment <= 0, ERR_CANT_CREATE, "Invalid alignment, must be greater then 0."); @@ -83,8 +83,8 @@ Error PCKPacker::pck_start(const String &p_file, int p_alignment, const String & } enc_dir = p_encrypt_directory; - file = FileAccess::open(p_file, FileAccess::WRITE); - ERR_FAIL_COND_V_MSG(file.is_null(), ERR_CANT_CREATE, "Can't open file to write: " + String(p_file) + "."); + file = FileAccess::open(p_pck_path, FileAccess::WRITE); + ERR_FAIL_COND_V_MSG(file.is_null(), ERR_CANT_CREATE, "Can't open file to write: " + String(p_pck_path) + "."); alignment = p_alignment; @@ -106,7 +106,7 @@ Error PCKPacker::pck_start(const String &p_file, int p_alignment, const String & return OK; } -Error PCKPacker::add_file(const String &p_file, const String &p_src, bool p_encrypt) { +Error PCKPacker::add_file(const String &p_pck_path, const String &p_src, bool p_encrypt) { ERR_FAIL_COND_V_MSG(file.is_null(), ERR_INVALID_PARAMETER, "File must be opened before use."); Ref<FileAccess> f = FileAccess::open(p_src, FileAccess::READ); @@ -117,7 +117,7 @@ Error PCKPacker::add_file(const String &p_file, const String &p_src, bool p_encr File pf; // Simplify path here and on every 'files' access so that paths that have extra '/' // symbols in them still match to the MD5 hash for the saved path. - pf.path = p_file.simplify_path(); + pf.path = p_pck_path.simplify_path(); pf.src_path = p_src; pf.ofs = ofs; pf.size = f->get_length(); diff --git a/core/io/pck_packer.h b/core/io/pck_packer.h index 8764fc90a0..5aac833532 100644 --- a/core/io/pck_packer.h +++ b/core/io/pck_packer.h @@ -58,8 +58,8 @@ class PCKPacker : public RefCounted { Vector<File> files; public: - Error pck_start(const String &p_file, int p_alignment = 32, const String &p_key = "0000000000000000000000000000000000000000000000000000000000000000", bool p_encrypt_directory = false); - Error add_file(const String &p_file, const String &p_src, bool p_encrypt = false); + Error pck_start(const String &p_pck_path, int p_alignment = 32, const String &p_key = "0000000000000000000000000000000000000000000000000000000000000000", bool p_encrypt_directory = false); + Error add_file(const String &p_pck_path, const String &p_src, bool p_encrypt = false); Error flush(bool p_verbose = false); PCKPacker() {} diff --git a/core/io/resource.cpp b/core/io/resource.cpp index 6177cba6a4..5f8a4b85a4 100644 --- a/core/io/resource.cpp +++ b/core/io/resource.cpp @@ -96,6 +96,7 @@ String Resource::get_path() const { void Resource::set_path_cache(const String &p_path) { path_cache = p_path; + GDVIRTUAL_CALL(_set_path_cache, p_path); } String Resource::generate_scene_unique_id() { @@ -188,6 +189,7 @@ void Resource::disconnect_changed(const Callable &p_callable) { } void Resource::reset_state() { + GDVIRTUAL_CALL(_reset_state); } Error Resource::copy_from(const Ref<Resource> &p_resource) { @@ -495,9 +497,9 @@ void Resource::set_as_translation_remapped(bool p_remapped) { } } -#ifdef TOOLS_ENABLED //helps keep IDs same number when loading/saving scenes. -1 clears ID and it Returns -1 when no id stored void Resource::set_id_for_path(const String &p_path, const String &p_id) { +#ifdef TOOLS_ENABLED if (p_id.is_empty()) { ResourceCache::path_cache_lock.write_lock(); ResourceCache::resource_path_cache[p_path].erase(get_path()); @@ -507,9 +509,11 @@ void Resource::set_id_for_path(const String &p_path, const String &p_id) { ResourceCache::resource_path_cache[p_path][get_path()] = p_id; ResourceCache::path_cache_lock.write_unlock(); } +#endif } String Resource::get_id_for_path(const String &p_path) const { +#ifdef TOOLS_ENABLED ResourceCache::path_cache_lock.read_lock(); if (ResourceCache::resource_path_cache[p_path].has(get_path())) { String result = ResourceCache::resource_path_cache[p_path][get_path()]; @@ -519,13 +523,16 @@ String Resource::get_id_for_path(const String &p_path) const { ResourceCache::path_cache_lock.read_unlock(); return ""; } -} +#else + return ""; #endif +} void Resource::_bind_methods() { ClassDB::bind_method(D_METHOD("set_path", "path"), &Resource::_set_path); ClassDB::bind_method(D_METHOD("take_over_path", "path"), &Resource::_take_over_path); ClassDB::bind_method(D_METHOD("get_path"), &Resource::get_path); + ClassDB::bind_method(D_METHOD("set_path_cache", "path"), &Resource::set_path_cache); ClassDB::bind_method(D_METHOD("set_name", "name"), &Resource::set_name); ClassDB::bind_method(D_METHOD("get_name"), &Resource::get_name); ClassDB::bind_method(D_METHOD("get_rid"), &Resource::get_rid); @@ -533,6 +540,12 @@ void Resource::_bind_methods() { ClassDB::bind_method(D_METHOD("is_local_to_scene"), &Resource::is_local_to_scene); ClassDB::bind_method(D_METHOD("get_local_scene"), &Resource::get_local_scene); ClassDB::bind_method(D_METHOD("setup_local_to_scene"), &Resource::setup_local_to_scene); + ClassDB::bind_method(D_METHOD("reset_state"), &Resource::reset_state); + + ClassDB::bind_method(D_METHOD("set_id_for_path", "path", "id"), &Resource::set_id_for_path); + ClassDB::bind_method(D_METHOD("get_id_for_path", "path"), &Resource::get_id_for_path); + + ClassDB::bind_method(D_METHOD("is_built_in"), &Resource::is_built_in); ClassDB::bind_static_method("Resource", D_METHOD("generate_scene_unique_id"), &Resource::generate_scene_unique_id); ClassDB::bind_method(D_METHOD("set_scene_unique_id", "id"), &Resource::set_scene_unique_id); @@ -552,6 +565,8 @@ void Resource::_bind_methods() { GDVIRTUAL_BIND(_setup_local_to_scene); GDVIRTUAL_BIND(_get_rid); + GDVIRTUAL_BIND(_reset_state); + GDVIRTUAL_BIND(_set_path_cache, "path"); } Resource::Resource() : diff --git a/core/io/resource.h b/core/io/resource.h index 2c1a431255..8966c0233c 100644 --- a/core/io/resource.h +++ b/core/io/resource.h @@ -89,6 +89,9 @@ protected: GDVIRTUAL0RC(RID, _get_rid); + GDVIRTUAL1C(_set_path_cache, String); + GDVIRTUAL0(_reset_state); + public: static Node *(*_get_local_scene_func)(); //used by editor static void (*_update_configuration_warning)(); //used by editor @@ -144,11 +147,9 @@ public: virtual RID get_rid() const; // some resources may offer conversion to RID -#ifdef TOOLS_ENABLED //helps keep IDs same number when loading/saving scenes. -1 clears ID and it Returns -1 when no id stored void set_id_for_path(const String &p_path, const String &p_id); String get_id_for_path(const String &p_path) const; -#endif Resource(); ~Resource(); diff --git a/core/io/resource_loader.cpp b/core/io/resource_loader.cpp index f29f9eef98..f026d5416c 100644 --- a/core/io/resource_loader.cpp +++ b/core/io/resource_loader.cpp @@ -303,10 +303,6 @@ Ref<Resource> ResourceLoader::_load(const String &p_path, const String &p_origin return res; } - if (r_error) { - *r_error = ERR_FILE_UNRECOGNIZED; - } - ERR_FAIL_COND_V_MSG(found, Ref<Resource>(), vformat("Failed loading resource: %s. Make sure resources have been imported by opening the project in the editor at least once.", p_path)); @@ -860,7 +856,7 @@ Ref<Resource> ResourceLoader::_load_complete_inner(LoadToken &p_load_token, Erro } } core_bind::Semaphore done; - MessageQueue::get_main_singleton()->push_callable(callable_mp(&done, &core_bind::Semaphore::post)); + MessageQueue::get_main_singleton()->push_callable(callable_mp(&done, &core_bind::Semaphore::post).bind(1)); done.wait(); } } diff --git a/core/math/a_star.cpp b/core/math/a_star.cpp index c53fd3d330..c85201a973 100644 --- a/core/math/a_star.cpp +++ b/core/math/a_star.cpp @@ -319,11 +319,11 @@ Vector3 AStar3D::get_closest_position_in_segment(const Vector3 &p_point) const { return closest_point; } -bool AStar3D::_solve(Point *begin_point, Point *end_point) { +bool AStar3D::_solve(Point *begin_point, Point *end_point, bool p_allow_partial_path) { last_closest_point = nullptr; pass++; - if (!end_point->enabled) { + if (!end_point->enabled && !p_allow_partial_path) { return false; } @@ -443,7 +443,7 @@ Vector<Vector3> AStar3D::get_point_path(int64_t p_from_id, int64_t p_to_id, bool Point *begin_point = a; Point *end_point = b; - bool found_route = _solve(begin_point, end_point); + bool found_route = _solve(begin_point, end_point, p_allow_partial_path); if (!found_route) { if (!p_allow_partial_path || last_closest_point == nullptr) { return Vector<Vector3>(); @@ -497,7 +497,7 @@ Vector<int64_t> AStar3D::get_id_path(int64_t p_from_id, int64_t p_to_id, bool p_ Point *begin_point = a; Point *end_point = b; - bool found_route = _solve(begin_point, end_point); + bool found_route = _solve(begin_point, end_point, p_allow_partial_path); if (!found_route) { if (!p_allow_partial_path || last_closest_point == nullptr) { return Vector<int64_t>(); @@ -726,7 +726,7 @@ Vector<Vector2> AStar2D::get_point_path(int64_t p_from_id, int64_t p_to_id, bool AStar3D::Point *begin_point = a; AStar3D::Point *end_point = b; - bool found_route = _solve(begin_point, end_point); + bool found_route = _solve(begin_point, end_point, p_allow_partial_path); if (!found_route) { if (!p_allow_partial_path || astar.last_closest_point == nullptr) { return Vector<Vector2>(); @@ -780,7 +780,7 @@ Vector<int64_t> AStar2D::get_id_path(int64_t p_from_id, int64_t p_to_id, bool p_ AStar3D::Point *begin_point = a; AStar3D::Point *end_point = b; - bool found_route = _solve(begin_point, end_point); + bool found_route = _solve(begin_point, end_point, p_allow_partial_path); if (!found_route) { if (!p_allow_partial_path || astar.last_closest_point == nullptr) { return Vector<int64_t>(); @@ -816,11 +816,11 @@ Vector<int64_t> AStar2D::get_id_path(int64_t p_from_id, int64_t p_to_id, bool p_ return path; } -bool AStar2D::_solve(AStar3D::Point *begin_point, AStar3D::Point *end_point) { +bool AStar2D::_solve(AStar3D::Point *begin_point, AStar3D::Point *end_point, bool p_allow_partial_path) { astar.last_closest_point = nullptr; astar.pass++; - if (!end_point->enabled) { + if (!end_point->enabled && !p_allow_partial_path) { return false; } diff --git a/core/math/a_star.h b/core/math/a_star.h index 143a3bec61..cbaafc1018 100644 --- a/core/math/a_star.h +++ b/core/math/a_star.h @@ -115,7 +115,7 @@ class AStar3D : public RefCounted { HashSet<Segment, Segment> segments; Point *last_closest_point = nullptr; - bool _solve(Point *begin_point, Point *end_point); + bool _solve(Point *begin_point, Point *end_point, bool p_allow_partial_path); protected: static void _bind_methods(); @@ -171,7 +171,7 @@ class AStar2D : public RefCounted { GDCLASS(AStar2D, RefCounted); AStar3D astar; - bool _solve(AStar3D::Point *begin_point, AStar3D::Point *end_point); + bool _solve(AStar3D::Point *begin_point, AStar3D::Point *end_point, bool p_allow_partial_path); protected: static void _bind_methods(); diff --git a/core/math/a_star_grid_2d.cpp b/core/math/a_star_grid_2d.cpp index c40ee5b4d7..7e0e982c62 100644 --- a/core/math/a_star_grid_2d.cpp +++ b/core/math/a_star_grid_2d.cpp @@ -491,11 +491,11 @@ void AStarGrid2D::_get_nbors(Point *p_point, LocalVector<Point *> &r_nbors) { } } -bool AStarGrid2D::_solve(Point *p_begin_point, Point *p_end_point) { +bool AStarGrid2D::_solve(Point *p_begin_point, Point *p_end_point, bool p_allow_partial_path) { last_closest_point = nullptr; pass++; - if (_get_solid_unchecked(p_end_point->id)) { + if (_get_solid_unchecked(p_end_point->id) && !p_allow_partial_path) { return false; } @@ -647,7 +647,7 @@ Vector<Vector2> AStarGrid2D::get_point_path(const Vector2i &p_from_id, const Vec Point *begin_point = a; Point *end_point = b; - bool found_route = _solve(begin_point, end_point); + bool found_route = _solve(begin_point, end_point, p_allow_partial_path); if (!found_route) { if (!p_allow_partial_path || last_closest_point == nullptr) { return Vector<Vector2>(); @@ -700,7 +700,7 @@ TypedArray<Vector2i> AStarGrid2D::get_id_path(const Vector2i &p_from_id, const V Point *begin_point = a; Point *end_point = b; - bool found_route = _solve(begin_point, end_point); + bool found_route = _solve(begin_point, end_point, p_allow_partial_path); if (!found_route) { if (!p_allow_partial_path || last_closest_point == nullptr) { return TypedArray<Vector2i>(); diff --git a/core/math/a_star_grid_2d.h b/core/math/a_star_grid_2d.h index f5ac472f09..0536b8109b 100644 --- a/core/math/a_star_grid_2d.h +++ b/core/math/a_star_grid_2d.h @@ -159,8 +159,8 @@ private: // Internal routines. void _get_nbors(Point *p_point, LocalVector<Point *> &r_nbors); Point *_jump(Point *p_from, Point *p_to); + bool _solve(Point *p_begin_point, Point *p_end_point, bool p_allow_partial_path); Point *_forced_successor(int32_t p_x, int32_t p_y, int32_t p_dx, int32_t p_dy, bool p_inclusive = false); - bool _solve(Point *p_begin_point, Point *p_end_point); protected: static void _bind_methods(); diff --git a/core/math/basis.h b/core/math/basis.h index 5c1a5fbdda..236d666103 100644 --- a/core/math/basis.h +++ b/core/math/basis.h @@ -41,11 +41,11 @@ struct [[nodiscard]] Basis { Vector3(0, 0, 1) }; - _FORCE_INLINE_ const Vector3 &operator[](int p_axis) const { - return rows[p_axis]; + _FORCE_INLINE_ const Vector3 &operator[](int p_row) const { + return rows[p_row]; } - _FORCE_INLINE_ Vector3 &operator[](int p_axis) { - return rows[p_axis]; + _FORCE_INLINE_ Vector3 &operator[](int p_row) { + return rows[p_row]; } void invert(); diff --git a/core/object/object.cpp b/core/object/object.cpp index 000d5328b4..b3a4ec6e2e 100644 --- a/core/object/object.cpp +++ b/core/object/object.cpp @@ -44,14 +44,17 @@ #ifdef DEBUG_ENABLED struct _ObjectDebugLock { - Object *obj; + ObjectID obj_id; _ObjectDebugLock(Object *p_obj) { - obj = p_obj; - obj->_lock_index.ref(); + obj_id = p_obj->get_instance_id(); + p_obj->_lock_index.ref(); } ~_ObjectDebugLock() { - obj->_lock_index.unref(); + Object *obj_ptr = ObjectDB::get_instance(obj_id); + if (likely(obj_ptr)) { + obj_ptr->_lock_index.unref(); + } } }; @@ -1454,6 +1457,24 @@ bool Object::is_connected(const StringName &p_signal, const Callable &p_callable return s->slot_map.has(*p_callable.get_base_comparator()); } +bool Object::has_connections(const StringName &p_signal) const { + const SignalData *s = signal_map.getptr(p_signal); + if (!s) { + bool signal_is_valid = ClassDB::has_signal(get_class_name(), p_signal); + if (signal_is_valid) { + return false; + } + + if (!script.is_null() && Ref<Script>(script)->has_script_signal(p_signal)) { + return false; + } + + ERR_FAIL_V_MSG(false, "Nonexistent signal: " + p_signal + "."); + } + + return !s->slot_map.is_empty(); +} + void Object::disconnect(const StringName &p_signal, const Callable &p_callable) { _disconnect(p_signal, p_callable); } @@ -1524,21 +1545,21 @@ void Object::initialize_class() { initialized = true; } +StringName Object::get_translation_domain() const { + return _translation_domain; +} + +void Object::set_translation_domain(const StringName &p_domain) { + _translation_domain = p_domain; +} + String Object::tr(const StringName &p_message, const StringName &p_context) const { if (!_can_translate || !TranslationServer::get_singleton()) { return p_message; } - if (Engine::get_singleton()->is_editor_hint() || Engine::get_singleton()->is_project_manager_hint()) { - String tr_msg = TranslationServer::get_singleton()->extractable_translate(p_message, p_context); - if (!tr_msg.is_empty() && tr_msg != p_message) { - return tr_msg; - } - - return TranslationServer::get_singleton()->tool_translate(p_message, p_context); - } - - return TranslationServer::get_singleton()->translate(p_message, p_context); + const Ref<TranslationDomain> domain = TranslationServer::get_singleton()->get_or_add_domain(get_translation_domain()); + return domain->translate(p_message, p_context); } String Object::tr_n(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const { @@ -1550,16 +1571,8 @@ String Object::tr_n(const StringName &p_message, const StringName &p_message_plu return p_message_plural; } - if (Engine::get_singleton()->is_editor_hint() || Engine::get_singleton()->is_project_manager_hint()) { - String tr_msg = TranslationServer::get_singleton()->extractable_translate_plural(p_message, p_message_plural, p_n, p_context); - if (!tr_msg.is_empty() && tr_msg != p_message && tr_msg != p_message_plural) { - return tr_msg; - } - - return TranslationServer::get_singleton()->tool_translate_plural(p_message, p_message_plural, p_n, p_context); - } - - return TranslationServer::get_singleton()->translate_plural(p_message, p_message_plural, p_n, p_context); + const Ref<TranslationDomain> domain = TranslationServer::get_singleton()->get_or_add_domain(get_translation_domain()); + return domain->translate_plural(p_message, p_message_plural, p_n, p_context); } void Object::_clear_internal_resource_paths(const Variant &p_var) { @@ -1702,6 +1715,7 @@ void Object::_bind_methods() { ClassDB::bind_method(D_METHOD("connect", "signal", "callable", "flags"), &Object::connect, DEFVAL(0)); ClassDB::bind_method(D_METHOD("disconnect", "signal", "callable"), &Object::disconnect); ClassDB::bind_method(D_METHOD("is_connected", "signal", "callable"), &Object::is_connected); + ClassDB::bind_method(D_METHOD("has_connections", "signal"), &Object::has_connections); ClassDB::bind_method(D_METHOD("set_block_signals", "enable"), &Object::set_block_signals); ClassDB::bind_method(D_METHOD("is_blocking_signals"), &Object::is_blocking_signals); @@ -1711,6 +1725,8 @@ void Object::_bind_methods() { ClassDB::bind_method(D_METHOD("can_translate_messages"), &Object::can_translate_messages); ClassDB::bind_method(D_METHOD("tr", "message", "context"), &Object::tr, DEFVAL(StringName())); ClassDB::bind_method(D_METHOD("tr_n", "message", "plural_message", "n", "context"), &Object::tr_n, DEFVAL(StringName())); + ClassDB::bind_method(D_METHOD("get_translation_domain"), &Object::get_translation_domain); + ClassDB::bind_method(D_METHOD("set_translation_domain", "domain"), &Object::set_translation_domain); ClassDB::bind_method(D_METHOD("is_queued_for_deletion"), &Object::is_queued_for_deletion); ClassDB::bind_method(D_METHOD("cancel_free"), &Object::cancel_free); diff --git a/core/object/object.h b/core/object/object.h index 6d22f320af..763e2974b9 100644 --- a/core/object/object.h +++ b/core/object/object.h @@ -665,6 +665,8 @@ private: Object(bool p_reference); protected: + StringName _translation_domain; + _FORCE_INLINE_ bool _instance_binding_reference(bool p_reference) { bool can_die = true; if (_instance_bindings) { @@ -930,6 +932,7 @@ public: MTVIRTUAL Error connect(const StringName &p_signal, const Callable &p_callable, uint32_t p_flags = 0); MTVIRTUAL void disconnect(const StringName &p_signal, const Callable &p_callable); MTVIRTUAL bool is_connected(const StringName &p_signal, const Callable &p_callable) const; + MTVIRTUAL bool has_connections(const StringName &p_signal) const; template <typename... VarArgs> void call_deferred(const StringName &p_name, VarArgs... p_args) { @@ -954,6 +957,9 @@ public: _FORCE_INLINE_ void set_message_translation(bool p_enable) { _can_translate = p_enable; } _FORCE_INLINE_ bool can_translate_messages() const { return _can_translate; } + virtual StringName get_translation_domain() const; + virtual void set_translation_domain(const StringName &p_domain); + #ifdef TOOLS_ENABLED virtual void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const; void editor_set_section_unfold(const String &p_section, bool p_unfolded); diff --git a/core/object/script_language.cpp b/core/object/script_language.cpp index d5b7bc768d..c5856a8a81 100644 --- a/core/object/script_language.cpp +++ b/core/object/script_language.cpp @@ -174,6 +174,8 @@ void Script::_bind_methods() { ClassDB::bind_method(D_METHOD("is_tool"), &Script::is_tool); ClassDB::bind_method(D_METHOD("is_abstract"), &Script::is_abstract); + ClassDB::bind_method(D_METHOD("get_rpc_config"), &Script::get_rpc_config); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "source_code", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_source_code", "get_source_code"); } @@ -189,7 +191,17 @@ void Script::reload_from_file() { set_source_code(rel->get_source_code()); set_last_modified_time(rel->get_last_modified_time()); - reload(); + // Only reload the script when there are no compilation errors to prevent printing the error messages twice. + if (rel->is_valid()) { + if (Engine::get_singleton()->is_editor_hint() && is_tool()) { + get_language()->reload_tool_script(this, true); + } else { + // It's important to set p_keep_state to true in order to manage reloading scripts + // that are currently instantiated. + reload(true); + } + } + #else Resource::reload_from_file(); #endif diff --git a/core/object/script_language.h b/core/object/script_language.h index d9e2ab1d3c..3ddfbb3e7d 100644 --- a/core/object/script_language.h +++ b/core/object/script_language.h @@ -112,7 +112,10 @@ class Script : public Resource { OBJ_SAVE_TYPE(Script); protected: - virtual bool editor_can_reload_from_file() override { return false; } // this is handled by editor better + // Scripts are reloaded via the Script Editor when edited in Godot, + // the LSP server when edited in a connected external editor, or + // through EditorFileSystem::_update_script_documentation when updated directly on disk. + virtual bool editor_can_reload_from_file() override { return false; } void _notification(int p_what); static void _bind_methods(); @@ -182,7 +185,7 @@ public: virtual bool is_placeholder_fallback_enabled() const { return false; } - virtual const Variant get_rpc_config() const = 0; + virtual Variant get_rpc_config() const = 0; Script() {} }; diff --git a/core/object/script_language_extension.h b/core/object/script_language_extension.h index c9344f5799..bc773c5ad3 100644 --- a/core/object/script_language_extension.h +++ b/core/object/script_language_extension.h @@ -205,7 +205,7 @@ public: GDVIRTUAL0RC(Variant, _get_rpc_config) - virtual const Variant get_rpc_config() const override { + virtual Variant get_rpc_config() const override { Variant ret; GDVIRTUAL_REQUIRED_CALL(_get_rpc_config, ret); return ret; diff --git a/core/object/worker_thread_pool.cpp b/core/object/worker_thread_pool.cpp index cf396c2676..08903d6196 100644 --- a/core/object/worker_thread_pool.cpp +++ b/core/object/worker_thread_pool.cpp @@ -180,13 +180,17 @@ void WorkerThreadPool::_process_task(Task *p_task) { void WorkerThreadPool::_thread_function(void *p_user) { ThreadData *thread_data = (ThreadData *)p_user; + while (true) { Task *task_to_process = nullptr; { MutexLock lock(singleton->task_mutex); - if (singleton->exit_threads) { - return; + + bool exit = singleton->_handle_runlevel(thread_data, lock); + if (unlikely(exit)) { + break; } + thread_data->signaled = false; if (singleton->task_queue.first()) { @@ -194,7 +198,6 @@ void WorkerThreadPool::_thread_function(void *p_user) { singleton->task_queue.remove(singleton->task_queue.first()); } else { thread_data->cond_var.wait(lock); - DEV_ASSERT(singleton->exit_threads || thread_data->signaled); } } @@ -204,19 +207,24 @@ void WorkerThreadPool::_thread_function(void *p_user) { } } -void WorkerThreadPool::_post_tasks_and_unlock(Task **p_tasks, uint32_t p_count, bool p_high_priority) { +void WorkerThreadPool::_post_tasks(Task **p_tasks, uint32_t p_count, bool p_high_priority, MutexLock<BinaryMutex> &p_lock) { // Fall back to processing on the calling thread if there are no worker threads. // Separated into its own variable to make it easier to extend this logic // in custom builds. bool process_on_calling_thread = threads.size() == 0; if (process_on_calling_thread) { - task_mutex.unlock(); + p_lock.temp_unlock(); for (uint32_t i = 0; i < p_count; i++) { _process_task(p_tasks[i]); } + p_lock.temp_relock(); return; } + while (runlevel == RUNLEVEL_EXIT_LANGUAGES) { + control_cond_var.wait(p_lock); + } + uint32_t to_process = 0; uint32_t to_promote = 0; @@ -238,8 +246,6 @@ void WorkerThreadPool::_post_tasks_and_unlock(Task **p_tasks, uint32_t p_count, } _notify_threads(caller_pool_thread, to_process, to_promote); - - task_mutex.unlock(); } void WorkerThreadPool::_notify_threads(const ThreadData *p_current_thread_data, uint32_t p_process_count, uint32_t p_promote_count) { @@ -323,9 +329,8 @@ WorkerThreadPool::TaskID WorkerThreadPool::add_native_task(void (*p_func)(void * } WorkerThreadPool::TaskID WorkerThreadPool::_add_task(const Callable &p_callable, void (*p_func)(void *), void *p_userdata, BaseTemplateUserdata *p_template_userdata, bool p_high_priority, const String &p_description) { - ERR_FAIL_COND_V_MSG(threads.is_empty(), INVALID_TASK_ID, "Can't add a task because the WorkerThreadPool is either not initialized yet or already terminated."); + MutexLock<BinaryMutex> lock(task_mutex); - task_mutex.lock(); // Get a free task Task *task = task_allocator.alloc(); TaskID id = last_task++; @@ -337,7 +342,7 @@ WorkerThreadPool::TaskID WorkerThreadPool::_add_task(const Callable &p_callable, task->template_userdata = p_template_userdata; tasks.insert(id, task); - _post_tasks_and_unlock(&task, 1, p_high_priority); + _post_tasks(&task, 1, p_high_priority, lock); return id; } @@ -444,22 +449,34 @@ void WorkerThreadPool::_unlock_unlockable_mutexes() { void WorkerThreadPool::_wait_collaboratively(ThreadData *p_caller_pool_thread, Task *p_task) { // Keep processing tasks until the condition to stop waiting is met. -#define IS_WAIT_OVER (unlikely(p_task == ThreadData::YIELDING) ? p_caller_pool_thread->yield_is_over : p_task->completed) - while (true) { Task *task_to_process = nullptr; bool relock_unlockables = false; { MutexLock lock(task_mutex); + bool was_signaled = p_caller_pool_thread->signaled; p_caller_pool_thread->signaled = false; - if (IS_WAIT_OVER) { - if (unlikely(p_task == ThreadData::YIELDING)) { + bool exit = _handle_runlevel(p_caller_pool_thread, lock); + if (unlikely(exit)) { + break; + } + + bool wait_is_over = false; + if (unlikely(p_task == ThreadData::YIELDING)) { + if (p_caller_pool_thread->yield_is_over) { p_caller_pool_thread->yield_is_over = false; + wait_is_over = true; } + } else { + if (p_task->completed) { + wait_is_over = true; + } + } - if (!exit_threads && was_signaled) { + if (wait_is_over) { + if (was_signaled) { // This thread was awaken for some additional reason, but it's about to exit. // Let's find out what may be pending and forward the requests. uint32_t to_process = task_queue.first() ? 1 : 0; @@ -474,28 +491,26 @@ void WorkerThreadPool::_wait_collaboratively(ThreadData *p_caller_pool_thread, T break; } - if (!exit_threads) { - if (p_caller_pool_thread->current_task->low_priority && low_priority_task_queue.first()) { - if (_try_promote_low_priority_task()) { - _notify_threads(p_caller_pool_thread, 1, 0); - } + if (p_caller_pool_thread->current_task->low_priority && low_priority_task_queue.first()) { + if (_try_promote_low_priority_task()) { + _notify_threads(p_caller_pool_thread, 1, 0); } + } - if (singleton->task_queue.first()) { - task_to_process = task_queue.first()->self(); - task_queue.remove(task_queue.first()); - } + if (singleton->task_queue.first()) { + task_to_process = task_queue.first()->self(); + task_queue.remove(task_queue.first()); + } - if (!task_to_process) { - p_caller_pool_thread->awaited_task = p_task; + if (!task_to_process) { + p_caller_pool_thread->awaited_task = p_task; - _unlock_unlockable_mutexes(); - relock_unlockables = true; - p_caller_pool_thread->cond_var.wait(lock); + _unlock_unlockable_mutexes(); + relock_unlockables = true; - DEV_ASSERT(exit_threads || p_caller_pool_thread->signaled || IS_WAIT_OVER); - p_caller_pool_thread->awaited_task = nullptr; - } + p_caller_pool_thread->cond_var.wait(lock); + + p_caller_pool_thread->awaited_task = nullptr; } } @@ -509,16 +524,65 @@ void WorkerThreadPool::_wait_collaboratively(ThreadData *p_caller_pool_thread, T } } +void WorkerThreadPool::_switch_runlevel(Runlevel p_runlevel) { + DEV_ASSERT(p_runlevel > runlevel); + runlevel = p_runlevel; + memset(&runlevel_data, 0, sizeof(runlevel_data)); + for (uint32_t i = 0; i < threads.size(); i++) { + threads[i].cond_var.notify_one(); + threads[i].signaled = true; + } + control_cond_var.notify_all(); +} + +// Returns whether threads have to exit. This may perform the check about handling needed. +bool WorkerThreadPool::_handle_runlevel(ThreadData *p_thread_data, MutexLock<BinaryMutex> &p_lock) { + bool exit = false; + switch (runlevel) { + case RUNLEVEL_NORMAL: { + } break; + case RUNLEVEL_PRE_EXIT_LANGUAGES: { + if (!p_thread_data->pre_exited_languages) { + if (!task_queue.first() && !low_priority_task_queue.first()) { + p_thread_data->pre_exited_languages = true; + runlevel_data.pre_exit_languages.num_idle_threads++; + control_cond_var.notify_all(); + } + } + } break; + case RUNLEVEL_EXIT_LANGUAGES: { + if (!p_thread_data->exited_languages) { + p_lock.temp_unlock(); + ScriptServer::thread_exit(); + p_lock.temp_relock(); + p_thread_data->exited_languages = true; + runlevel_data.exit_languages.num_exited_threads++; + control_cond_var.notify_all(); + } + } break; + case RUNLEVEL_EXIT: { + exit = true; + } break; + } + return exit; +} + void WorkerThreadPool::yield() { int th_index = get_thread_index(); ERR_FAIL_COND_MSG(th_index == -1, "This function can only be called from a worker thread."); _wait_collaboratively(&threads[th_index], ThreadData::YIELDING); - // If this long-lived task started before the scripting server was initialized, - // now is a good time to have scripting languages ready for the current thread. - // Otherwise, such a piece of setup won't happen unless another task has been - // run during the collaborative wait. - ScriptServer::thread_enter(); + task_mutex.lock(); + if (runlevel < RUNLEVEL_EXIT_LANGUAGES) { + // If this long-lived task started before the scripting server was initialized, + // now is a good time to have scripting languages ready for the current thread. + // Otherwise, such a piece of setup won't happen unless another task has been + // run during the collaborative wait. + task_mutex.unlock(); + ScriptServer::thread_enter(); + } else { + task_mutex.unlock(); + } } void WorkerThreadPool::notify_yield_over(TaskID p_task_id) { @@ -543,13 +607,13 @@ void WorkerThreadPool::notify_yield_over(TaskID p_task_id) { } WorkerThreadPool::GroupID WorkerThreadPool::_add_group_task(const Callable &p_callable, void (*p_func)(void *, uint32_t), void *p_userdata, BaseTemplateUserdata *p_template_userdata, int p_elements, int p_tasks, bool p_high_priority, const String &p_description) { - ERR_FAIL_COND_V_MSG(threads.is_empty(), INVALID_TASK_ID, "Can't add a group task because the WorkerThreadPool is either not initialized yet or already terminated."); ERR_FAIL_COND_V(p_elements < 0, INVALID_TASK_ID); if (p_tasks < 0) { p_tasks = MAX(1u, threads.size()); } - task_mutex.lock(); + MutexLock<BinaryMutex> lock(task_mutex); + Group *group = group_allocator.alloc(); GroupID id = last_task++; group->max = p_elements; @@ -584,7 +648,7 @@ WorkerThreadPool::GroupID WorkerThreadPool::_add_group_task(const Callable &p_ca groups[id] = group; - _post_tasks_and_unlock(tasks_posted, p_tasks, p_high_priority); + _post_tasks(tasks_posted, p_tasks, p_high_priority, lock); return id; } @@ -687,6 +751,9 @@ void WorkerThreadPool::thread_exit_unlock_allowance_zone(uint32_t p_zone_id) { void WorkerThreadPool::init(int p_thread_count, float p_low_priority_task_ratio) { ERR_FAIL_COND(threads.size() > 0); + + runlevel = RUNLEVEL_NORMAL; + if (p_thread_count < 0) { p_thread_count = OS::get_singleton()->get_default_thread_pool_size(); } @@ -704,6 +771,26 @@ void WorkerThreadPool::init(int p_thread_count, float p_low_priority_task_ratio) } } +void WorkerThreadPool::exit_languages_threads() { + if (threads.size() == 0) { + return; + } + + MutexLock lock(task_mutex); + + // Wait until all threads are idle. + _switch_runlevel(RUNLEVEL_PRE_EXIT_LANGUAGES); + while (runlevel_data.pre_exit_languages.num_idle_threads != threads.size()) { + control_cond_var.wait(lock); + } + + // Wait until all threads have detached from scripting languages. + _switch_runlevel(RUNLEVEL_EXIT_LANGUAGES); + while (runlevel_data.exit_languages.num_exited_threads != threads.size()) { + control_cond_var.wait(lock); + } +} + void WorkerThreadPool::finish() { if (threads.size() == 0) { return; @@ -716,15 +803,10 @@ void WorkerThreadPool::finish() { print_error("Task waiting was never re-claimed: " + E->self()->description); E = E->next(); } - } - { - MutexLock lock(task_mutex); - exit_threads = true; - } - for (ThreadData &data : threads) { - data.cond_var.notify_one(); + _switch_runlevel(RUNLEVEL_EXIT); } + for (ThreadData &data : threads) { data.thread.wait_to_finish(); } @@ -755,5 +837,5 @@ WorkerThreadPool::WorkerThreadPool() { } WorkerThreadPool::~WorkerThreadPool() { - DEV_ASSERT(threads.size() == 0 && "finish() hasn't been called!"); + finish(); } diff --git a/core/object/worker_thread_pool.h b/core/object/worker_thread_pool.h index 6374dbe8c7..62296ac040 100644 --- a/core/object/worker_thread_pool.h +++ b/core/object/worker_thread_pool.h @@ -114,17 +114,35 @@ private: Thread thread; bool signaled : 1; bool yield_is_over : 1; + bool pre_exited_languages : 1; + bool exited_languages : 1; Task *current_task = nullptr; Task *awaited_task = nullptr; // Null if not awaiting the condition variable, or special value (YIELDING). ConditionVariable cond_var; ThreadData() : signaled(false), - yield_is_over(false) {} + yield_is_over(false), + pre_exited_languages(false), + exited_languages(false) {} }; TightLocalVector<ThreadData> threads; - bool exit_threads = false; + enum Runlevel { + RUNLEVEL_NORMAL, + RUNLEVEL_PRE_EXIT_LANGUAGES, // Block adding new tasks + RUNLEVEL_EXIT_LANGUAGES, // All threads detach from scripting threads. + RUNLEVEL_EXIT, + } runlevel = RUNLEVEL_NORMAL; + union { // Cleared on every runlevel change. + struct { + uint32_t num_idle_threads; + } pre_exit_languages; + struct { + uint32_t num_exited_threads; + } exit_languages; + } runlevel_data; + ConditionVariable control_cond_var; HashMap<Thread::ID, int> thread_ids; HashMap< @@ -152,7 +170,7 @@ private: void _process_task(Task *task); - void _post_tasks_and_unlock(Task **p_tasks, uint32_t p_count, bool p_high_priority); + void _post_tasks(Task **p_tasks, uint32_t p_count, bool p_high_priority, MutexLock<BinaryMutex> &p_lock); void _notify_threads(const ThreadData *p_current_thread_data, uint32_t p_process_count, uint32_t p_promote_count); bool _try_promote_low_priority_task(); @@ -193,6 +211,9 @@ private: void _wait_collaboratively(ThreadData *p_caller_pool_thread, Task *p_task); + void _switch_runlevel(Runlevel p_runlevel); + bool _handle_runlevel(ThreadData *p_thread_data, MutexLock<BinaryMutex> &p_lock); + #ifdef THREADS_ENABLED static uint32_t _thread_enter_unlock_allowance_zone(THREADING_NAMESPACE::unique_lock<THREADING_NAMESPACE::mutex> &p_ulock); #endif @@ -256,6 +277,7 @@ public: #endif void init(int p_thread_count = -1, float p_low_priority_task_ratio = 0.3); + void exit_languages_threads(); void finish(); WorkerThreadPool(); ~WorkerThreadPool(); diff --git a/core/register_core_types.cpp b/core/register_core_types.cpp index 220ed9da31..3a578d01a6 100644 --- a/core/register_core_types.cpp +++ b/core/register_core_types.cpp @@ -107,6 +107,8 @@ static Time *_time = nullptr; static core_bind::Geometry2D *_geometry_2d = nullptr; static core_bind::Geometry3D *_geometry_3d = nullptr; +static WorkerThreadPool *worker_thread_pool = nullptr; + extern Mutex _global_mutex; static GDExtensionManager *gdextension_manager = nullptr; @@ -231,6 +233,7 @@ void register_core_types() { GDREGISTER_CLASS(MainLoop); GDREGISTER_CLASS(Translation); + GDREGISTER_CLASS(TranslationDomain); GDREGISTER_CLASS(OptimizedTranslation); GDREGISTER_CLASS(UndoRedo); GDREGISTER_CLASS(TriangleMesh); @@ -295,6 +298,8 @@ void register_core_types() { GDREGISTER_NATIVE_STRUCT(AudioFrame, "float left;float right"); GDREGISTER_NATIVE_STRUCT(ScriptLanguageExtensionProfilingInfo, "StringName signature;uint64_t call_count;uint64_t total_time;uint64_t self_time"); + worker_thread_pool = memnew(WorkerThreadPool); + OS::get_singleton()->benchmark_end_measure("Core", "Register Types"); } @@ -345,7 +350,7 @@ void register_core_singletons() { Engine::get_singleton()->add_singleton(Engine::Singleton("Time", Time::get_singleton())); Engine::get_singleton()->add_singleton(Engine::Singleton("GDExtensionManager", GDExtensionManager::get_singleton())); Engine::get_singleton()->add_singleton(Engine::Singleton("ResourceUID", ResourceUID::get_singleton())); - Engine::get_singleton()->add_singleton(Engine::Singleton("WorkerThreadPool", WorkerThreadPool::get_singleton())); + Engine::get_singleton()->add_singleton(Engine::Singleton("WorkerThreadPool", worker_thread_pool)); OS::get_singleton()->benchmark_end_measure("Core", "Register Singletons"); } @@ -378,6 +383,8 @@ void unregister_core_types() { // Destroy singletons in reverse order to ensure dependencies are not broken. + memdelete(worker_thread_pool); + memdelete(_engine_debugger); memdelete(_marshalls); memdelete(_classdb); diff --git a/core/string/translation_domain.cpp b/core/string/translation_domain.cpp new file mode 100644 index 0000000000..b44eb40366 --- /dev/null +++ b/core/string/translation_domain.cpp @@ -0,0 +1,165 @@ +/**************************************************************************/ +/* translation_domain.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 "translation_domain.h" + +#include "core/string/translation.h" +#include "core/string/translation_server.h" + +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; + + for (const Ref<Translation> &E : translations) { + ERR_CONTINUE(E.is_null()); + int score = TranslationServer::get_singleton()->compare_locales(p_locale, E->get_locale()); + if (score > 0 && score >= best_score) { + const StringName r = E->get_message(p_message, p_context); + if (!r) { + continue; + } + res = r; + best_score = score; + if (score == 10) { + break; // Exact match, skip the rest. + } + } + } + + return res; +} + +StringName TranslationDomain::get_message_from_translations(const String &p_locale, const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const { + StringName res; + int best_score = 0; + + for (const Ref<Translation> &E : translations) { + ERR_CONTINUE(E.is_null()); + int score = TranslationServer::get_singleton()->compare_locales(p_locale, E->get_locale()); + if (score > 0 && score >= best_score) { + const StringName r = E->get_plural_message(p_message, p_message_plural, p_n, p_context); + if (!r) { + continue; + } + res = r; + best_score = score; + if (score == 10) { + break; // Exact match, skip the rest. + } + } + } + + return res; +} + +PackedStringArray TranslationDomain::get_loaded_locales() const { + PackedStringArray locales; + for (const Ref<Translation> &E : translations) { + ERR_CONTINUE(E.is_null()); + locales.push_back(E->get_locale()); + } + return locales; +} + +Ref<Translation> TranslationDomain::get_translation_object(const String &p_locale) const { + Ref<Translation> res; + int best_score = 0; + + for (const Ref<Translation> &E : translations) { + ERR_CONTINUE(E.is_null()); + + int score = TranslationServer::get_singleton()->compare_locales(p_locale, E->get_locale()); + if (score > 0 && score >= best_score) { + res = E; + best_score = score; + if (score == 10) { + break; // Exact match, skip the rest. + } + } + } + return res; +} + +void TranslationDomain::add_translation(const Ref<Translation> &p_translation) { + translations.insert(p_translation); +} + +void TranslationDomain::remove_translation(const Ref<Translation> &p_translation) { + translations.erase(p_translation); +} + +void TranslationDomain::clear() { + translations.clear(); +} + +StringName TranslationDomain::translate(const StringName &p_message, const StringName &p_context) const { + const String &locale = TranslationServer::get_singleton()->get_locale(); + StringName res = get_message_from_translations(locale, p_message, p_context); + + const String &fallback = TranslationServer::get_singleton()->get_fallback_locale(); + if (!res && fallback.length() >= 2) { + res = get_message_from_translations(fallback, p_message, p_context); + } + + if (!res) { + return p_message; + } + return res; +} + +StringName TranslationDomain::translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const { + const String &locale = TranslationServer::get_singleton()->get_locale(); + StringName res = get_message_from_translations(locale, p_message, p_message_plural, p_n, p_context); + + const String &fallback = TranslationServer::get_singleton()->get_fallback_locale(); + if (!res && fallback.length() >= 2) { + res = get_message_from_translations(fallback, p_message, p_message_plural, p_n, p_context); + } + + if (!res) { + if (p_n == 1) { + return p_message; + } + return p_message_plural; + } + return res; +} + +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); + ClassDB::bind_method(D_METHOD("remove_translation", "translation"), &TranslationDomain::remove_translation); + 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())); +} + +TranslationDomain::TranslationDomain() { +} diff --git a/core/string/translation_domain.h b/core/string/translation_domain.h new file mode 100644 index 0000000000..6139967217 --- /dev/null +++ b/core/string/translation_domain.h @@ -0,0 +1,65 @@ +/**************************************************************************/ +/* translation_domain.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 TRANSLATION_DOMAIN_H +#define TRANSLATION_DOMAIN_H + +#include "core/object/ref_counted.h" + +class Translation; + +class TranslationDomain : public RefCounted { + GDCLASS(TranslationDomain, RefCounted); + + HashSet<Ref<Translation>> translations; + +protected: + static void _bind_methods(); + +public: + // Methods in this section are not intended for scripting. + StringName get_message_from_translations(const String &p_locale, const StringName &p_message, const StringName &p_context) const; + StringName get_message_from_translations(const String &p_locale, const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const; + PackedStringArray get_loaded_locales() const; + +public: + Ref<Translation> get_translation_object(const String &p_locale) const; + + void add_translation(const Ref<Translation> &p_translation); + void remove_translation(const Ref<Translation> &p_translation); + void clear(); + + 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; + + TranslationDomain(); +}; + +#endif // TRANSLATION_DOMAIN_H diff --git a/core/string/translation_server.cpp b/core/string/translation_server.cpp index d4aa152340..c6b818a49b 100644 --- a/core/string/translation_server.cpp +++ b/core/string/translation_server.cpp @@ -404,69 +404,36 @@ String TranslationServer::get_locale() const { return locale; } -PackedStringArray TranslationServer::get_loaded_locales() const { - PackedStringArray locales; - for (const Ref<Translation> &E : translations) { - const Ref<Translation> &t = E; - ERR_FAIL_COND_V(t.is_null(), PackedStringArray()); - String l = t->get_locale(); - - locales.push_back(l); - } +String TranslationServer::get_fallback_locale() const { + return fallback; +} - return locales; +PackedStringArray TranslationServer::get_loaded_locales() const { + return main_domain->get_loaded_locales(); } void TranslationServer::add_translation(const Ref<Translation> &p_translation) { - translations.insert(p_translation); + main_domain->add_translation(p_translation); } void TranslationServer::remove_translation(const Ref<Translation> &p_translation) { - translations.erase(p_translation); + main_domain->remove_translation(p_translation); } Ref<Translation> TranslationServer::get_translation_object(const String &p_locale) { - Ref<Translation> res; - int best_score = 0; - - for (const Ref<Translation> &E : translations) { - const Ref<Translation> &t = E; - ERR_FAIL_COND_V(t.is_null(), nullptr); - String l = t->get_locale(); - - int score = compare_locales(p_locale, l); - if (score > 0 && score >= best_score) { - res = t; - best_score = score; - if (score == 10) { - break; // Exact match, skip the rest. - } - } - } - return res; + return main_domain->get_translation_object(p_locale); } void TranslationServer::clear() { - translations.clear(); + main_domain->clear(); } StringName TranslationServer::translate(const StringName &p_message, const StringName &p_context) const { - // Match given message against the translation catalog for the project locale. - if (!enabled) { return p_message; } - StringName res = _get_message_from_translations(p_message, p_context, locale, false); - - if (!res && fallback.length() >= 2) { - res = _get_message_from_translations(p_message, p_context, fallback, false); - } - - if (!res) { - return pseudolocalization_enabled ? pseudolocalize(p_message) : p_message; - } - + const StringName res = main_domain->translate(p_message, p_context); return pseudolocalization_enabled ? pseudolocalize(res) : res; } @@ -478,51 +445,7 @@ StringName TranslationServer::translate_plural(const StringName &p_message, cons return p_message_plural; } - StringName res = _get_message_from_translations(p_message, p_context, locale, true, p_message_plural, p_n); - - if (!res && fallback.length() >= 2) { - res = _get_message_from_translations(p_message, p_context, fallback, true, p_message_plural, p_n); - } - - if (!res) { - if (p_n == 1) { - return p_message; - } - return p_message_plural; - } - - return res; -} - -StringName TranslationServer::_get_message_from_translations(const StringName &p_message, const StringName &p_context, const String &p_locale, bool plural, const String &p_message_plural, int p_n) const { - StringName res; - int best_score = 0; - - for (const Ref<Translation> &E : translations) { - const Ref<Translation> &t = E; - ERR_FAIL_COND_V(t.is_null(), p_message); - String l = t->get_locale(); - - int score = compare_locales(p_locale, l); - if (score > 0 && score >= best_score) { - StringName r; - if (!plural) { - r = t->get_message(p_message, p_context); - } else { - r = t->get_plural_message(p_message, p_message_plural, p_n, p_context); - } - if (!r) { - continue; - } - res = r; - best_score = score; - if (score == 10) { - break; // Exact match, skip the rest. - } - } - } - - return res; + return main_domain->translate_plural(p_message, p_message_plural, p_n, p_context); } TranslationServer *TranslationServer::singleton = nullptr; @@ -549,6 +472,34 @@ bool TranslationServer::_load_translations(const String &p_from) { return false; } +bool TranslationServer::has_domain(const StringName &p_domain) const { + if (p_domain == StringName()) { + return true; + } + return custom_domains.has(p_domain); +} + +Ref<TranslationDomain> TranslationServer::get_or_add_domain(const StringName &p_domain) { + if (p_domain == StringName()) { + return main_domain; + } + const Ref<TranslationDomain> *domain = custom_domains.getptr(p_domain); + if (domain) { + if (domain->is_valid()) { + return *domain; + } + ERR_PRINT("Bug (please report): Found invalid translation domain."); + } + Ref<TranslationDomain> new_domain = memnew(TranslationDomain); + custom_domains[p_domain] = new_domain; + return new_domain; +} + +void TranslationServer::remove_domain(const StringName &p_domain) { + ERR_FAIL_COND_MSG(p_domain == StringName(), "Cannot remove main translation domain."); + custom_domains.erase(p_domain); +} + void TranslationServer::setup() { String test = GLOBAL_DEF("internationalization/locale/test", ""); test = test.strip_edges(); @@ -574,140 +525,45 @@ void TranslationServer::setup() { #endif } -void TranslationServer::set_tool_translation(const Ref<Translation> &p_translation) { - tool_translation = p_translation; -} - -Ref<Translation> TranslationServer::get_tool_translation() const { - return tool_translation; -} - String TranslationServer::get_tool_locale() { #ifdef TOOLS_ENABLED if (Engine::get_singleton()->is_editor_hint() || Engine::get_singleton()->is_project_manager_hint()) { - if (TranslationServer::get_singleton()->get_tool_translation().is_valid()) { - return tool_translation->get_locale(); - } else { + const PackedStringArray &locales = editor_domain->get_loaded_locales(); + if (locales.is_empty()) { return "en"; } + return locales[0]; } else { #else { #endif // Look for best matching loaded translation. - String best_locale = "en"; - int best_score = 0; - - for (const Ref<Translation> &E : translations) { - const Ref<Translation> &t = E; - ERR_FAIL_COND_V(t.is_null(), best_locale); - String l = t->get_locale(); - - int score = compare_locales(locale, l); - if (score > 0 && score >= best_score) { - best_locale = l; - best_score = score; - if (score == 10) { - break; // Exact match, skip the rest. - } - } + Ref<Translation> t = main_domain->get_translation_object(locale); + if (t.is_null()) { + return "en"; } - return best_locale; + return t->get_locale(); } } StringName TranslationServer::tool_translate(const StringName &p_message, const StringName &p_context) const { - if (tool_translation.is_valid()) { - StringName r = tool_translation->get_message(p_message, p_context); - if (r) { - return r; - } - } - return p_message; + return editor_domain->translate(p_message, p_context); } StringName TranslationServer::tool_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const { - if (tool_translation.is_valid()) { - StringName r = tool_translation->get_plural_message(p_message, p_message_plural, p_n, p_context); - if (r) { - return r; - } - } - - if (p_n == 1) { - return p_message; - } - return p_message_plural; -} - -void TranslationServer::set_property_translation(const Ref<Translation> &p_translation) { - property_translation = p_translation; + return editor_domain->translate_plural(p_message, p_message_plural, p_n, p_context); } StringName TranslationServer::property_translate(const StringName &p_message, const StringName &p_context) const { - if (property_translation.is_valid()) { - StringName r = property_translation->get_message(p_message, p_context); - if (r) { - return r; - } - } - return p_message; -} - -void TranslationServer::set_doc_translation(const Ref<Translation> &p_translation) { - doc_translation = p_translation; + return property_domain->translate(p_message, p_context); } StringName TranslationServer::doc_translate(const StringName &p_message, const StringName &p_context) const { - if (doc_translation.is_valid()) { - StringName r = doc_translation->get_message(p_message, p_context); - if (r) { - return r; - } - } - return p_message; + return doc_domain->translate(p_message, p_context); } StringName TranslationServer::doc_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const { - if (doc_translation.is_valid()) { - StringName r = doc_translation->get_plural_message(p_message, p_message_plural, p_n, p_context); - if (r) { - return r; - } - } - - if (p_n == 1) { - return p_message; - } - return p_message_plural; -} - -void TranslationServer::set_extractable_translation(const Ref<Translation> &p_translation) { - extractable_translation = p_translation; -} - -StringName TranslationServer::extractable_translate(const StringName &p_message, const StringName &p_context) const { - if (extractable_translation.is_valid()) { - StringName r = extractable_translation->get_message(p_message, p_context); - if (r) { - return r; - } - } - return p_message; -} - -StringName TranslationServer::extractable_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const { - if (extractable_translation.is_valid()) { - StringName r = extractable_translation->get_plural_message(p_message, p_message_plural, p_n, p_context); - if (r) { - return r; - } - } - - if (p_n == 1) { - return p_message; - } - return p_message_plural; + return doc_domain->translate_plural(p_message, p_message_plural, p_n, p_context); } bool TranslationServer::is_pseudolocalization_enabled() const { @@ -925,6 +781,10 @@ void TranslationServer::_bind_methods() { ClassDB::bind_method(D_METHOD("remove_translation", "translation"), &TranslationServer::remove_translation); ClassDB::bind_method(D_METHOD("get_translation_object", "locale"), &TranslationServer::get_translation_object); + ClassDB::bind_method(D_METHOD("has_domain", "domain"), &TranslationServer::has_domain); + ClassDB::bind_method(D_METHOD("get_or_add_domain", "domain"), &TranslationServer::get_or_add_domain); + ClassDB::bind_method(D_METHOD("remove_domain", "domain"), &TranslationServer::remove_domain); + ClassDB::bind_method(D_METHOD("clear"), &TranslationServer::clear); ClassDB::bind_method(D_METHOD("get_loaded_locales"), &TranslationServer::get_loaded_locales); @@ -947,5 +807,9 @@ void TranslationServer::load_translations() { TranslationServer::TranslationServer() { singleton = this; + main_domain.instantiate(); + editor_domain = get_or_add_domain("godot.editor"); + property_domain = get_or_add_domain("godot.properties"); + doc_domain = get_or_add_domain("godot.documentation"); init_locale_info(); } diff --git a/core/string/translation_server.h b/core/string/translation_server.h index bb285ab19c..272fa1f11c 100644 --- a/core/string/translation_server.h +++ b/core/string/translation_server.h @@ -32,6 +32,7 @@ #define TRANSLATION_SERVER_H #include "core/string/translation.h" +#include "core/string/translation_domain.h" class TranslationServer : public Object { GDCLASS(TranslationServer, Object); @@ -39,11 +40,11 @@ class TranslationServer : public Object { String locale = "en"; String fallback; - HashSet<Ref<Translation>> translations; - Ref<Translation> tool_translation; - Ref<Translation> property_translation; - Ref<Translation> doc_translation; - Ref<Translation> extractable_translation; + Ref<TranslationDomain> main_domain; + Ref<TranslationDomain> editor_domain; + Ref<TranslationDomain> property_domain; + Ref<TranslationDomain> doc_domain; + HashMap<StringName, Ref<TranslationDomain>> custom_domains; bool enabled = true; @@ -70,8 +71,6 @@ class TranslationServer : public Object { bool _load_translations(const String &p_from); String _standardize_locale(const String &p_locale, bool p_add_defaults) const; - StringName _get_message_from_translations(const StringName &p_message, const StringName &p_context, const String &p_locale, bool plural, const String &p_message_plural = "", int p_n = 0) const; - static void _bind_methods(); struct LocaleScriptInfo { @@ -99,6 +98,7 @@ public: void set_locale(const String &p_locale); String get_locale() const; + String get_fallback_locale() const; Ref<Translation> get_translation_object(const String &p_locale); Vector<String> get_all_languages() const; @@ -131,18 +131,15 @@ public: int compare_locales(const String &p_locale_a, const String &p_locale_b) const; String get_tool_locale(); - void set_tool_translation(const Ref<Translation> &p_translation); - Ref<Translation> get_tool_translation() const; StringName tool_translate(const StringName &p_message, const StringName &p_context = "") const; StringName tool_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context = "") const; - void set_property_translation(const Ref<Translation> &p_translation); StringName property_translate(const StringName &p_message, const StringName &p_context = "") const; - void set_doc_translation(const Ref<Translation> &p_translation); StringName doc_translate(const StringName &p_message, const StringName &p_context = "") const; StringName doc_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context = "") const; - void set_extractable_translation(const Ref<Translation> &p_translation); - StringName extractable_translate(const StringName &p_message, const StringName &p_context = "") const; - StringName extractable_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context = "") const; + + bool has_domain(const StringName &p_domain) const; + Ref<TranslationDomain> get_or_add_domain(const StringName &p_domain); + void remove_domain(const StringName &p_domain); void setup(); diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp index 2683addd4b..391a203d5b 100644 --- a/core/string/ustring.cpp +++ b/core/string/ustring.cpp @@ -4626,7 +4626,7 @@ bool String::is_absolute_path() const { String String::validate_ascii_identifier() const { if (is_empty()) { - return "_"; // Empty string is not a valid identifier; + return "_"; // Empty string is not a valid identifier. } String result; @@ -4647,6 +4647,29 @@ String String::validate_ascii_identifier() const { return result; } +String String::validate_unicode_identifier() const { + if (is_empty()) { + return "_"; // Empty string is not a valid identifier. + } + + String result; + if (is_unicode_identifier_start(operator[](0))) { + result = *this; + } else { + result = "_" + *this; + } + + int len = result.length(); + char32_t *buffer = result.ptrw(); + for (int i = 0; i < len; i++) { + if (!is_unicode_identifier_continue(buffer[i])) { + buffer[i] = '_'; + } + } + + return result; +} + bool String::is_valid_ascii_identifier() const { int len = length(); diff --git a/core/string/ustring.h b/core/string/ustring.h index 11f15031f9..5d4b209c25 100644 --- a/core/string/ustring.h +++ b/core/string/ustring.h @@ -460,6 +460,7 @@ public: static String get_invalid_node_name_characters(bool p_allow_internal = false); String validate_node_name() const; String validate_ascii_identifier() const; + String validate_unicode_identifier() const; String validate_filename() const; bool is_valid_ascii_identifier() const; diff --git a/core/variant/array.cpp b/core/variant/array.cpp index 869499e668..3e62d3dffa 100644 --- a/core/variant/array.cpp +++ b/core/variant/array.cpp @@ -369,6 +369,34 @@ int Array::find(const Variant &p_value, int p_from) const { return ret; } +int Array::find_custom(const Callable &p_callable, int p_from) const { + int ret = -1; + + if (p_from < 0 || size() == 0) { + return ret; + } + + const Variant *argptrs[1]; + + for (int i = p_from; i < size(); i++) { + const Variant &val = _p->array[i]; + argptrs[0] = &val; + Variant res; + Callable::CallError ce; + p_callable.callp(argptrs, 1, res, ce); + if (unlikely(ce.error != Callable::CallError::CALL_OK)) { + ERR_FAIL_V_MSG(ret, "Error calling method from 'find_custom': " + Variant::get_callable_error_text(p_callable, argptrs, 1, ce)); + } + + ERR_FAIL_COND_V_MSG(res.get_type() != Variant::Type::BOOL, ret, "Error on method from 'find_custom': Return type of callable must be boolean."); + if (res.operator bool()) { + return i; + } + } + + return ret; +} + int Array::rfind(const Variant &p_value, int p_from) const { if (_p->array.size() == 0) { return -1; @@ -394,6 +422,41 @@ int Array::rfind(const Variant &p_value, int p_from) const { return -1; } +int Array::rfind_custom(const Callable &p_callable, int p_from) const { + if (_p->array.size() == 0) { + return -1; + } + + if (p_from < 0) { + // Relative offset from the end. + p_from = _p->array.size() + p_from; + } + if (p_from < 0 || p_from >= _p->array.size()) { + // Limit to array boundaries. + p_from = _p->array.size() - 1; + } + + const Variant *argptrs[1]; + + for (int i = p_from; i >= 0; i--) { + const Variant &val = _p->array[i]; + argptrs[0] = &val; + Variant res; + Callable::CallError ce; + p_callable.callp(argptrs, 1, res, ce); + if (unlikely(ce.error != Callable::CallError::CALL_OK)) { + ERR_FAIL_V_MSG(-1, "Error calling method from 'rfind_custom': " + Variant::get_callable_error_text(p_callable, argptrs, 1, ce)); + } + + ERR_FAIL_COND_V_MSG(res.get_type() != Variant::Type::BOOL, -1, "Error on method from 'rfind_custom': Return type of callable must be boolean."); + if (res.operator bool()) { + return i; + } + } + + return -1; +} + int Array::count(const Variant &p_value) const { Variant value = p_value; ERR_FAIL_COND_V(!_p->typed.validate(value, "count"), 0); @@ -761,7 +824,7 @@ Variant Array::max() const { return Variant(); //not a valid comparison } if (bool(ret)) { - //is less + //is greater maxval = test; } } diff --git a/core/variant/array.h b/core/variant/array.h index 12824ee3f6..6c3bae6ccb 100644 --- a/core/variant/array.h +++ b/core/variant/array.h @@ -152,7 +152,9 @@ public: void reverse(); int find(const Variant &p_value, int p_from = 0) const; + int find_custom(const Callable &p_callable, int p_from = 0) const; int rfind(const Variant &p_value, int p_from = -1) const; + int rfind_custom(const Callable &p_callable, int p_from = -1) const; int count(const Variant &p_value) const; bool has(const Variant &p_value) const; diff --git a/core/variant/callable.cpp b/core/variant/callable.cpp index 9dff5c1e91..bb2d0313f6 100644 --- a/core/variant/callable.cpp +++ b/core/variant/callable.cpp @@ -545,6 +545,13 @@ bool Signal::is_connected(const Callable &p_callable) const { return obj->is_connected(name, p_callable); } +bool Signal::has_connections() const { + Object *obj = get_object(); + ERR_FAIL_NULL_V(obj, false); + + return obj->has_connections(name); +} + Array Signal::get_connections() const { Object *obj = get_object(); if (!obj) { diff --git a/core/variant/callable.h b/core/variant/callable.h index 63757d9d6e..e3c940a0e5 100644 --- a/core/variant/callable.h +++ b/core/variant/callable.h @@ -192,6 +192,7 @@ public: Error connect(const Callable &p_callable, uint32_t p_flags = 0); void disconnect(const Callable &p_callable); bool is_connected(const Callable &p_callable) const; + bool has_connections() const; Array get_connections() const; Signal(const Object *p_object, const StringName &p_name); diff --git a/core/variant/dictionary.cpp b/core/variant/dictionary.cpp index 0754814d35..2db754438f 100644 --- a/core/variant/dictionary.cpp +++ b/core/variant/dictionary.cpp @@ -83,35 +83,64 @@ Variant Dictionary::get_value_at_index(int p_index) const { return Variant(); } +// WARNING: This operator does not validate the value type. For scripting/extensions this is +// done in `variant_setget.cpp`. Consider using `set()` if the data might be invalid. Variant &Dictionary::operator[](const Variant &p_key) { - if (unlikely(_p->read_only)) { - if (likely(_p->variant_map.has(p_key))) { - *_p->read_only = _p->variant_map[p_key]; + Variant key = p_key; + if (unlikely(!_p->typed_key.validate(key, "use `operator[]`"))) { + if (unlikely(!_p->typed_fallback)) { + _p->typed_fallback = memnew(Variant); + } + VariantInternal::initialize(_p->typed_fallback, _p->typed_value.type); + return *_p->typed_fallback; + } else if (unlikely(_p->read_only)) { + if (likely(_p->variant_map.has(key))) { + *_p->read_only = _p->variant_map[key]; } else { - *_p->read_only = Variant(); + VariantInternal::initialize(_p->read_only, _p->typed_value.type); } - return *_p->read_only; } else { - return _p->variant_map[p_key]; + if (unlikely(!_p->variant_map.has(key))) { + VariantInternal::initialize(&_p->variant_map[key], _p->typed_value.type); + } + return _p->variant_map[key]; } } const Variant &Dictionary::operator[](const Variant &p_key) const { - // Will not insert key, so no conversion is necessary. - return _p->variant_map[p_key]; + Variant key = p_key; + if (unlikely(!_p->typed_key.validate(key, "use `operator[]`"))) { + if (unlikely(!_p->typed_fallback)) { + _p->typed_fallback = memnew(Variant); + } + VariantInternal::initialize(_p->typed_fallback, _p->typed_value.type); + return *_p->typed_fallback; + } else { + // Will not insert key, so no initialization is necessary. + return _p->variant_map[key]; + } } const Variant *Dictionary::getptr(const Variant &p_key) const { - HashMap<Variant, Variant, VariantHasher, StringLikeVariantComparator>::ConstIterator E(_p->variant_map.find(p_key)); + Variant key = p_key; + if (unlikely(!_p->typed_key.validate(key, "getptr"))) { + return nullptr; + } + HashMap<Variant, Variant, VariantHasher, StringLikeVariantComparator>::ConstIterator E(_p->variant_map.find(key)); if (!E) { return nullptr; } return &E->value; } +// WARNING: This method does not validate the value type. Variant *Dictionary::getptr(const Variant &p_key) { - HashMap<Variant, Variant, VariantHasher, StringLikeVariantComparator>::Iterator E(_p->variant_map.find(p_key)); + Variant key = p_key; + if (unlikely(!_p->typed_key.validate(key, "getptr"))) { + return nullptr; + } + HashMap<Variant, Variant, VariantHasher, StringLikeVariantComparator>::Iterator E(_p->variant_map.find(key)); if (!E) { return nullptr; } @@ -158,6 +187,16 @@ Variant Dictionary::get_or_add(const Variant &p_key, const Variant &p_default) { return *result; } +bool Dictionary::set(const Variant &p_key, const Variant &p_value) { + ERR_FAIL_COND_V_MSG(_p->read_only, false, "Dictionary is in read-only state."); + Variant key = p_key; + ERR_FAIL_COND_V(!_p->typed_key.validate(key, "set"), false); + Variant value = p_value; + ERR_FAIL_COND_V(!_p->typed_value.validate(value, "set"), false); + _p->variant_map[key] = value; + return true; +} + int Dictionary::size() const { return _p->variant_map.size(); } diff --git a/core/variant/dictionary.h b/core/variant/dictionary.h index 57fbefc8f2..5f3ce40219 100644 --- a/core/variant/dictionary.h +++ b/core/variant/dictionary.h @@ -59,6 +59,7 @@ public: Variant get_valid(const Variant &p_key) const; Variant get(const Variant &p_key, const Variant &p_default) const; Variant get_or_add(const Variant &p_key, const Variant &p_default); + bool set(const Variant &p_key, const Variant &p_value); int size() const; bool is_empty() const; diff --git a/core/variant/variant_call.cpp b/core/variant/variant_call.cpp index 2da94de875..63fb5e8d94 100644 --- a/core/variant/variant_call.cpp +++ b/core/variant/variant_call.cpp @@ -2121,6 +2121,7 @@ static void _register_variant_builtin_methods_misc() { bind_method(Signal, disconnect, sarray("callable"), varray()); bind_method(Signal, is_connected, sarray("callable"), varray()); bind_method(Signal, get_connections, sarray(), varray()); + bind_method(Signal, has_connections, sarray(), varray()); bind_custom(Signal, emit, _VariantCall::func_Signal_emit, false, Variant); @@ -2305,7 +2306,9 @@ static void _register_variant_builtin_methods_array() { bind_method(Array, back, sarray(), varray()); bind_method(Array, pick_random, sarray(), varray()); bind_method(Array, find, sarray("what", "from"), varray(0)); + bind_method(Array, find_custom, sarray("method", "from"), varray(0)); bind_method(Array, rfind, sarray("what", "from"), varray(-1)); + bind_method(Array, rfind_custom, sarray("method", "from"), varray(-1)); bind_method(Array, count, sarray("value"), varray()); bind_method(Array, has, sarray("value"), varray()); bind_method(Array, pop_back, sarray(), varray()); diff --git a/core/variant/variant_op.h b/core/variant/variant_op.h index ac39a4135f..0bd8b830e0 100644 --- a/core/variant/variant_op.h +++ b/core/variant/variant_op.h @@ -923,7 +923,10 @@ public: static inline void validated_evaluate(const Variant *left, const Variant *right, Variant *r_ret) { bool valid = true; String result = do_mod(*VariantGetInternalPtr<S>::get_ptr(left), &valid); - ERR_FAIL_COND_MSG(!valid, result); + if (unlikely(!valid)) { + *VariantGetInternalPtr<String>::get_ptr(r_ret) = *VariantGetInternalPtr<S>::get_ptr(left); + ERR_FAIL_MSG(vformat("String formatting error: %s.", result)); + } *VariantGetInternalPtr<String>::get_ptr(r_ret) = result; } static void ptr_evaluate(const void *left, const void *right, void *r_ret) { @@ -948,7 +951,10 @@ public: static inline void validated_evaluate(const Variant *left, const Variant *right, Variant *r_ret) { bool valid = true; String result = do_mod(*VariantGetInternalPtr<S>::get_ptr(left), *VariantGetInternalPtr<Array>::get_ptr(right), &valid); - ERR_FAIL_COND_MSG(!valid, result); + if (unlikely(!valid)) { + *VariantGetInternalPtr<String>::get_ptr(r_ret) = *VariantGetInternalPtr<S>::get_ptr(left); + ERR_FAIL_MSG(vformat("String formatting error: %s.", result)); + } *VariantGetInternalPtr<String>::get_ptr(r_ret) = result; } static void ptr_evaluate(const void *left, const void *right, void *r_ret) { @@ -976,7 +982,10 @@ public: static inline void validated_evaluate(const Variant *left, const Variant *right, Variant *r_ret) { bool valid = true; String result = do_mod(*VariantGetInternalPtr<S>::get_ptr(left), right->get_validated_object(), &valid); - ERR_FAIL_COND_MSG(!valid, result); + if (unlikely(!valid)) { + *VariantGetInternalPtr<String>::get_ptr(r_ret) = *VariantGetInternalPtr<S>::get_ptr(left); + ERR_FAIL_MSG(vformat("String formatting error: %s.", result)); + } *VariantGetInternalPtr<String>::get_ptr(r_ret) = result; } static void ptr_evaluate(const void *left, const void *right, void *r_ret) { @@ -1003,7 +1012,10 @@ public: static inline void validated_evaluate(const Variant *left, const Variant *right, Variant *r_ret) { bool valid = true; String result = do_mod(*VariantGetInternalPtr<S>::get_ptr(left), *VariantGetInternalPtr<T>::get_ptr(right), &valid); - ERR_FAIL_COND_MSG(!valid, result); + if (unlikely(!valid)) { + *VariantGetInternalPtr<String>::get_ptr(r_ret) = *VariantGetInternalPtr<S>::get_ptr(left); + ERR_FAIL_MSG(vformat("String formatting error: %s.", result)); + } *VariantGetInternalPtr<String>::get_ptr(r_ret) = result; } static void ptr_evaluate(const void *left, const void *right, void *r_ret) { @@ -1492,7 +1504,10 @@ public: } static inline void validated_evaluate(const Variant *left, const Variant *right, Variant *r_ret) { Object *l = right->get_validated_object(); - ERR_FAIL_NULL(l); + if (unlikely(!l)) { + *VariantGetInternalPtr<bool>::get_ptr(r_ret) = false; + ERR_FAIL_MSG("Invalid base object for 'in'."); + } const String &a = *VariantGetInternalPtr<String>::get_ptr(left); bool valid; @@ -1526,7 +1541,10 @@ public: } static inline void validated_evaluate(const Variant *left, const Variant *right, Variant *r_ret) { Object *l = right->get_validated_object(); - ERR_FAIL_NULL(l); + if (unlikely(!l)) { + *VariantGetInternalPtr<bool>::get_ptr(r_ret) = false; + ERR_FAIL_MSG("Invalid base object for 'in'."); + } const StringName &a = *VariantGetInternalPtr<StringName>::get_ptr(left); bool valid; diff --git a/core/variant/variant_setget.cpp b/core/variant/variant_setget.cpp index b60ff83cf1..1652f81d99 100644 --- a/core/variant/variant_setget.cpp +++ b/core/variant/variant_setget.cpp @@ -252,20 +252,7 @@ void Variant::set_named(const StringName &p_member, const Variant &p_value, bool } } else if (type == Variant::DICTIONARY) { Dictionary &dict = *VariantGetInternalPtr<Dictionary>::get_ptr(this); - - if (dict.is_read_only()) { - r_valid = false; - return; - } - - Variant *v = dict.getptr(p_member); - if (v) { - *v = p_value; - } else { - dict[p_member] = p_value; - } - - r_valid = true; + r_valid = dict.set(p_member, p_value); } else { r_valid = false; } @@ -721,26 +708,16 @@ struct VariantIndexedSetGet_Dictionary { PtrToArg<Variant>::encode(*ptr, member); } static void set(Variant *base, int64_t index, const Variant *value, bool *valid, bool *oob) { - if (VariantGetInternalPtr<Dictionary>::get_ptr(base)->is_read_only()) { - *valid = false; - *oob = true; - return; - } - (*VariantGetInternalPtr<Dictionary>::get_ptr(base))[index] = *value; - *oob = false; - *valid = true; + *valid = VariantGetInternalPtr<Dictionary>::get_ptr(base)->set(index, *value); + *oob = VariantGetInternalPtr<Dictionary>::get_ptr(base)->is_read_only(); } static void validated_set(Variant *base, int64_t index, const Variant *value, bool *oob) { - if (VariantGetInternalPtr<Dictionary>::get_ptr(base)->is_read_only()) { - *oob = true; - return; - } - (*VariantGetInternalPtr<Dictionary>::get_ptr(base))[index] = *value; - *oob = false; + VariantGetInternalPtr<Dictionary>::get_ptr(base)->set(index, *value); + *oob = VariantGetInternalPtr<Dictionary>::get_ptr(base)->is_read_only(); } static void ptr_set(void *base, int64_t index, const void *member) { Dictionary &v = *reinterpret_cast<Dictionary *>(base); - v[index] = PtrToArg<Variant>::convert(member); + v.set(index, PtrToArg<Variant>::convert(member)); } static Variant::Type get_index_type() { return Variant::NIL; } static uint32_t get_index_usage() { return PROPERTY_USAGE_DEFAULT; } @@ -1010,16 +987,11 @@ struct VariantKeyedSetGetDictionary { PtrToArg<Variant>::encode(*ptr, value); } static void set(Variant *base, const Variant *key, const Variant *value, bool *r_valid) { - if (VariantGetInternalPtr<Dictionary>::get_ptr(base)->is_read_only()) { - *r_valid = false; - return; - } - (*VariantGetInternalPtr<Dictionary>::get_ptr(base))[*key] = *value; - *r_valid = true; + *r_valid = VariantGetInternalPtr<Dictionary>::get_ptr(base)->set(*key, *value); } static void ptr_set(void *base, const void *key, const void *value) { Dictionary &v = *reinterpret_cast<Dictionary *>(base); - v[PtrToArg<Variant>::convert(key)] = PtrToArg<Variant>::convert(value); + v.set(PtrToArg<Variant>::convert(key), PtrToArg<Variant>::convert(value)); } static bool has(const Variant *base, const Variant *key, bool *r_valid) { |