summaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/config/engine.cpp8
-rw-r--r--core/config/engine.h3
-rw-r--r--core/config/project_settings.cpp3
-rw-r--r--core/core_bind.cpp32
-rw-r--r--core/core_bind.h16
-rw-r--r--core/input/input_event.cpp2
-rw-r--r--core/io/image.cpp24
-rw-r--r--core/io/image.h1
-rw-r--r--core/io/pck_packer.cpp12
-rw-r--r--core/io/pck_packer.h4
-rw-r--r--core/io/resource.cpp19
-rw-r--r--core/io/resource.h5
-rw-r--r--core/io/resource_loader.cpp6
-rw-r--r--core/math/a_star.cpp16
-rw-r--r--core/math/a_star.h4
-rw-r--r--core/math/a_star_grid_2d.cpp8
-rw-r--r--core/math/a_star_grid_2d.h2
-rw-r--r--core/math/basis.h8
-rw-r--r--core/object/object.cpp64
-rw-r--r--core/object/object.h6
-rw-r--r--core/object/script_language.cpp14
-rw-r--r--core/object/script_language.h7
-rw-r--r--core/object/script_language_extension.h2
-rw-r--r--core/object/worker_thread_pool.cpp178
-rw-r--r--core/object/worker_thread_pool.h28
-rw-r--r--core/register_core_types.cpp9
-rw-r--r--core/string/translation_domain.cpp165
-rw-r--r--core/string/translation_domain.h65
-rw-r--r--core/string/translation_server.cpp254
-rw-r--r--core/string/translation_server.h25
-rw-r--r--core/string/ustring.cpp25
-rw-r--r--core/string/ustring.h1
-rw-r--r--core/variant/array.cpp65
-rw-r--r--core/variant/array.h2
-rw-r--r--core/variant/callable.cpp7
-rw-r--r--core/variant/callable.h1
-rw-r--r--core/variant/dictionary.cpp59
-rw-r--r--core/variant/dictionary.h1
-rw-r--r--core/variant/variant_call.cpp3
-rw-r--r--core/variant/variant_op.h30
-rw-r--r--core/variant/variant_setget.cpp44
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) {