diff options
Diffstat (limited to 'core')
136 files changed, 6795 insertions, 3766 deletions
diff --git a/core/SCsub b/core/SCsub index 1bd4eae16c..c8267ae960 100644 --- a/core/SCsub +++ b/core/SCsub @@ -140,7 +140,7 @@ if env["builtin_zstd"]: "decompress/zstd_decompress_block.c", "decompress/zstd_decompress.c", ] - if env["platform"] in ["android", "ios", "linuxbsd", "macos"]: + if env["platform"] in ["android", "ios", "linuxbsd", "macos"] and env["arch"] == "x86_64": # Match platforms with ZSTD_ASM_SUPPORTED in common/portability_macros.h thirdparty_zstd_sources.append("decompress/huf_decompress_amd64.S") thirdparty_zstd_sources = [thirdparty_zstd_dir + file for file in thirdparty_zstd_sources] diff --git a/core/config/engine.cpp b/core/config/engine.cpp index 3574430cf7..9cdc21fe8e 100644 --- a/core/config/engine.cpp +++ b/core/config/engine.cpp @@ -263,6 +263,10 @@ bool Engine::is_generate_spirv_debug_info_enabled() const { return generate_spirv_debug_info; } +bool Engine::is_extra_gpu_memory_tracking_enabled() const { + return extra_gpu_memory_tracking; +} + 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 7e617d8773..f858eba328 100644 --- a/core/config/engine.h +++ b/core/config/engine.h @@ -72,6 +72,7 @@ private: bool abort_on_gpu_errors = false; bool use_validation_layers = false; bool generate_spirv_debug_info = false; + bool extra_gpu_memory_tracking = false; int32_t gpu_idx = -1; uint64_t _process_frames = 0; @@ -181,6 +182,7 @@ public: bool is_abort_on_gpu_errors_enabled() const; bool is_validation_layers_enabled() const; bool is_generate_spirv_debug_info_enabled() const; + bool is_extra_gpu_memory_tracking_enabled() const; int32_t get_gpu_index() const; void increment_frames_drawn(); diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp index 768540a0fa..32f36e01f9 100644 --- a/core/config/project_settings.cpp +++ b/core/config/project_settings.cpp @@ -329,9 +329,9 @@ bool ProjectSettings::_set(const StringName &p_name, const Variant &p_value) { String path = p_value; if (path.begins_with("*")) { autoload.is_singleton = true; - autoload.path = path.substr(1); + autoload.path = path.substr(1).simplify_path(); } else { - autoload.path = path; + autoload.path = path.simplify_path(); } add_autoload(autoload); } else if (p_name.operator String().begins_with("global_group/")) { @@ -1472,10 +1472,6 @@ ProjectSettings::ProjectSettings() { GLOBAL_DEF(PropertyInfo(Variant::INT, "display/window/size/window_height_override", PROPERTY_HINT_RANGE, "0,4320,1,or_greater"), 0); // 8K resolution GLOBAL_DEF("display/window/energy_saving/keep_screen_on", true); -#ifdef TOOLS_ENABLED - GLOBAL_DEF("display/window/energy_saving/keep_screen_on.editor_hint", false); -#endif - GLOBAL_DEF("animation/warnings/check_invalid_track_paths", true); GLOBAL_DEF("animation/warnings/check_angle_interpolation_type_conflicting", true); @@ -1489,15 +1485,6 @@ ProjectSettings::ProjectSettings() { GLOBAL_DEF(PropertyInfo(Variant::INT, "audio/general/ios/session_category", PROPERTY_HINT_ENUM, "Ambient,Multi Route,Play and Record,Playback,Record,Solo Ambient"), 0); GLOBAL_DEF("audio/general/ios/mix_with_others", false); - PackedStringArray extensions; - extensions.push_back("gd"); - if (Engine::get_singleton()->has_singleton("GodotSharp")) { - extensions.push_back("cs"); - } - extensions.push_back("gdshader"); - - GLOBAL_DEF(PropertyInfo(Variant::PACKED_STRING_ARRAY, "editor/script/search_in_file_extensions"), extensions); - _add_builtin_input_map(); // Keep the enum values in sync with the `DisplayServer::ScreenOrientation` enum. @@ -1515,6 +1502,7 @@ ProjectSettings::ProjectSettings() { GLOBAL_DEF_BASIC(PropertyInfo(Variant::STRING, "display/window/stretch/scale_mode", PROPERTY_HINT_ENUM, "fractional,integer"), "fractional"); GLOBAL_DEF(PropertyInfo(Variant::INT, "debug/settings/profiler/max_functions", PROPERTY_HINT_RANGE, "128,65535,1"), 16384); + GLOBAL_DEF_RST(PropertyInfo(Variant::INT, "debug/settings/profiler/max_timestamp_query_elements", PROPERTY_HINT_RANGE, "256,65535,1"), 256); GLOBAL_DEF(PropertyInfo(Variant::BOOL, "compression/formats/zstd/long_distance_matching"), Compression::zstd_long_distance_matching); GLOBAL_DEF(PropertyInfo(Variant::INT, "compression/formats/zstd/compression_level", PROPERTY_HINT_RANGE, "1,22,1"), Compression::zstd_level); @@ -1569,6 +1557,11 @@ ProjectSettings::ProjectSettings() { GLOBAL_DEF("collada/use_ambient", false); + // Input settings + GLOBAL_DEF_BASIC("input_devices/pointing/android/enable_long_press_as_right_click", false); + GLOBAL_DEF_BASIC("input_devices/pointing/android/enable_pan_and_scale_gestures", false); + GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "input_devices/pointing/android/rotary_input_scroll_axis", PROPERTY_HINT_ENUM, "Horizontal,Vertical"), 1); + // These properties will not show up in the dialog. If you want to exclude whole groups, use add_hidden_prefix(). GLOBAL_DEF_INTERNAL("application/config/features", PackedStringArray()); GLOBAL_DEF_INTERNAL("internationalization/locale/translation_remaps", PackedStringArray()); diff --git a/core/string/translation.compat.inc b/core/core_bind.compat.inc index d792d4a6fc..3e8ac3c5de 100644 --- a/core/string/translation.compat.inc +++ b/core/core_bind.compat.inc @@ -1,5 +1,5 @@ /**************************************************************************/ -/* translation.compat.inc */ +/* core_bind.compat.inc */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -30,17 +30,28 @@ #ifndef DISABLE_DEPRECATED -void Translation::_bind_compatibility_methods() { - ClassDB::bind_compatibility_method(D_METHOD("add_message", "src_message", "xlated_message", "context"), &Translation::add_message, DEFVAL("")); - ClassDB::bind_compatibility_method(D_METHOD("add_plural_message", "src_message", "xlated_messages", "context"), &Translation::add_plural_message, DEFVAL("")); - ClassDB::bind_compatibility_method(D_METHOD("get_message", "src_message", "context"), &Translation::get_message, DEFVAL("")); - ClassDB::bind_compatibility_method(D_METHOD("get_plural_message", "src_message", "src_plural_message", "n", "context"), &Translation::get_plural_message, DEFVAL("")); - ClassDB::bind_compatibility_method(D_METHOD("erase_message", "src_message", "context"), &Translation::erase_message, DEFVAL("")); +namespace core_bind { + +// Semaphore + +void Semaphore::_post_bind_compat_93605() { + post(1); +} + +void Semaphore::_bind_compatibility_methods() { + ClassDB::bind_compatibility_method(D_METHOD("post"), &Semaphore::_post_bind_compat_93605); } -void TranslationServer::_bind_compatibility_methods() { - ClassDB::bind_compatibility_method(D_METHOD("translate", "message", "context"), &TranslationServer::translate, DEFVAL("")); - ClassDB::bind_compatibility_method(D_METHOD("translate_plural", "message", "plural_message", "n", "context"), &TranslationServer::translate_plural, DEFVAL("")); +// OS + +Dictionary OS::_execute_with_pipe_bind_compat_94434(const String &p_path, const Vector<String> &p_arguments) { + return execute_with_pipe(p_path, p_arguments, true); +} + +void OS::_bind_compatibility_methods() { + ClassDB::bind_compatibility_method(D_METHOD("execute_with_pipe", "path", "arguments"), &OS::_execute_with_pipe_bind_compat_94434); } -#endif +} // namespace core_bind + +#endif // DISABLE_DEPRECATED diff --git a/core/core_bind.cpp b/core/core_bind.cpp index a1b7b81111..b27981d56b 100644 --- a/core/core_bind.cpp +++ b/core/core_bind.cpp @@ -29,6 +29,7 @@ /**************************************************************************/ #include "core_bind.h" +#include "core_bind.compat.inc" #include "core/config/project_settings.h" #include "core/crypto/crypto_core.h" @@ -56,8 +57,11 @@ Error ResourceLoader::load_threaded_request(const String &p_path, const String & ResourceLoader::ThreadLoadStatus ResourceLoader::load_threaded_get_status(const String &p_path, Array r_progress) { float progress = 0; ::ResourceLoader::ThreadLoadStatus tls = ::ResourceLoader::load_threaded_get_status(p_path, &progress); - r_progress.resize(1); - r_progress[0] = progress; + // Default array should never be modified, it causes the hash of the method to change. + if (!ClassDB::is_default_array_arg(r_progress)) { + r_progress.resize(1); + r_progress[0] = progress; + } return (ThreadLoadStatus)tls; } @@ -115,6 +119,11 @@ bool ResourceLoader::has_cached(const String &p_path) { return ResourceCache::has(local_path); } +Ref<Resource> ResourceLoader::get_cached_ref(const String &p_path) { + String local_path = ProjectSettings::get_singleton()->localize_path(p_path); + return ResourceCache::get_ref(local_path); +} + bool ResourceLoader::exists(const String &p_path, const String &p_type_hint) { return ::ResourceLoader::exists(p_path, p_type_hint); } @@ -125,7 +134,7 @@ ResourceUID::ID ResourceLoader::get_resource_uid(const String &p_path) { void ResourceLoader::_bind_methods() { ClassDB::bind_method(D_METHOD("load_threaded_request", "path", "type_hint", "use_sub_threads", "cache_mode"), &ResourceLoader::load_threaded_request, DEFVAL(""), DEFVAL(false), DEFVAL(CACHE_MODE_REUSE)); - ClassDB::bind_method(D_METHOD("load_threaded_get_status", "path", "progress"), &ResourceLoader::load_threaded_get_status, DEFVAL(Array())); + ClassDB::bind_method(D_METHOD("load_threaded_get_status", "path", "progress"), &ResourceLoader::load_threaded_get_status, DEFVAL_ARRAY); ClassDB::bind_method(D_METHOD("load_threaded_get", "path"), &ResourceLoader::load_threaded_get); ClassDB::bind_method(D_METHOD("load", "path", "type_hint", "cache_mode"), &ResourceLoader::load, DEFVAL(""), DEFVAL(CACHE_MODE_REUSE)); @@ -135,6 +144,7 @@ void ResourceLoader::_bind_methods() { ClassDB::bind_method(D_METHOD("set_abort_on_missing_resources", "abort"), &ResourceLoader::set_abort_on_missing_resources); ClassDB::bind_method(D_METHOD("get_dependencies", "path"), &ResourceLoader::get_dependencies); ClassDB::bind_method(D_METHOD("has_cached", "path"), &ResourceLoader::has_cached); + ClassDB::bind_method(D_METHOD("get_cached_ref", "path"), &ResourceLoader::get_cached_ref); ClassDB::bind_method(D_METHOD("exists", "path", "type_hint"), &ResourceLoader::exists, DEFVAL("")); ClassDB::bind_method(D_METHOD("get_resource_uid", "path"), &ResourceLoader::get_resource_uid); @@ -300,19 +310,22 @@ int OS::execute(const String &p_path, const Vector<String> &p_arguments, Array r String pipe; int exitcode = 0; Error err = ::OS::get_singleton()->execute(p_path, args, &pipe, &exitcode, p_read_stderr, nullptr, p_open_console); - r_output.push_back(pipe); + // Default array should never be modified, it causes the hash of the method to change. + if (!ClassDB::is_default_array_arg(r_output)) { + r_output.push_back(pipe); + } if (err != OK) { return -1; } return exitcode; } -Dictionary OS::execute_with_pipe(const String &p_path, const Vector<String> &p_arguments) { +Dictionary OS::execute_with_pipe(const String &p_path, const Vector<String> &p_arguments, bool p_blocking) { List<String> args; for (const String &arg : p_arguments) { args.push_back(arg); } - return ::OS::get_singleton()->execute_with_pipe(p_path, args); + return ::OS::get_singleton()->execute_with_pipe(p_path, args, p_blocking); } int OS::create_instance(const Vector<String> &p_arguments) { @@ -611,8 +624,8 @@ void OS::_bind_methods() { ClassDB::bind_method(D_METHOD("get_system_font_path_for_text", "font_name", "text", "locale", "script", "weight", "stretch", "italic"), &OS::get_system_font_path_for_text, DEFVAL(String()), DEFVAL(String()), DEFVAL(400), DEFVAL(100), DEFVAL(false)); ClassDB::bind_method(D_METHOD("get_executable_path"), &OS::get_executable_path); ClassDB::bind_method(D_METHOD("read_string_from_stdin"), &OS::read_string_from_stdin); - ClassDB::bind_method(D_METHOD("execute", "path", "arguments", "output", "read_stderr", "open_console"), &OS::execute, DEFVAL(Array()), DEFVAL(false), DEFVAL(false)); - ClassDB::bind_method(D_METHOD("execute_with_pipe", "path", "arguments"), &OS::execute_with_pipe); + ClassDB::bind_method(D_METHOD("execute", "path", "arguments", "output", "read_stderr", "open_console"), &OS::execute, DEFVAL_ARRAY, DEFVAL(false), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("execute_with_pipe", "path", "arguments", "blocking"), &OS::execute_with_pipe, DEFVAL(true)); ClassDB::bind_method(D_METHOD("create_process", "path", "arguments", "open_console"), &OS::create_process, DEFVAL(false)); ClassDB::bind_method(D_METHOD("create_instance", "arguments"), &OS::create_instance); ClassDB::bind_method(D_METHOD("kill", "pid"), &OS::kill); @@ -692,6 +705,7 @@ void OS::_bind_methods() { BIND_ENUM_CONSTANT(RENDERING_DRIVER_VULKAN); BIND_ENUM_CONSTANT(RENDERING_DRIVER_OPENGL3); BIND_ENUM_CONSTANT(RENDERING_DRIVER_D3D12); + BIND_ENUM_CONSTANT(RENDERING_DRIVER_METAL); BIND_ENUM_CONSTANT(SYSTEM_DIR_DESKTOP); BIND_ENUM_CONSTANT(SYSTEM_DIR_DCIM); @@ -1209,14 +1223,15 @@ bool Semaphore::try_wait() { return semaphore.try_wait(); } -void Semaphore::post() { - semaphore.post(); +void Semaphore::post(int p_count) { + ERR_FAIL_COND(p_count <= 0); + semaphore.post(p_count); } void Semaphore::_bind_methods() { ClassDB::bind_method(D_METHOD("wait"), &Semaphore::wait); ClassDB::bind_method(D_METHOD("try_wait"), &Semaphore::try_wait); - ClassDB::bind_method(D_METHOD("post"), &Semaphore::post); + ClassDB::bind_method(D_METHOD("post", "count"), &Semaphore::post, DEFVAL(1)); } ////// Mutex ////// @@ -1440,6 +1455,14 @@ TypedArray<Dictionary> ClassDB::class_get_property_list(const StringName &p_clas return ret; } +StringName ClassDB::class_get_property_getter(const StringName &p_class, const StringName &p_property) { + return ::ClassDB::get_property_getter(p_class, p_property); +} + +StringName ClassDB::class_get_property_setter(const StringName &p_class, const StringName &p_property) { + return ::ClassDB::get_property_setter(p_class, p_property); +} + Variant ClassDB::class_get_property(Object *p_object, const StringName &p_property) const { Variant ret; ::ClassDB::get_property(p_object, p_property, ret); @@ -1492,6 +1515,23 @@ TypedArray<Dictionary> ClassDB::class_get_method_list(const StringName &p_class, return ret; } +Variant ClassDB::class_call_static_method(const Variant **p_arguments, int p_argcount, Callable::CallError &r_call_error) { + if (p_argcount < 2) { + r_call_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; + return Variant::NIL; + } + if (!p_arguments[0]->is_string() || !p_arguments[1]->is_string()) { + r_call_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + return Variant::NIL; + } + StringName class_ = *p_arguments[0]; + StringName method = *p_arguments[1]; + const MethodBind *bind = ::ClassDB::get_method(class_, method); + ERR_FAIL_NULL_V_MSG(bind, Variant::NIL, "Cannot find static method."); + ERR_FAIL_COND_V_MSG(!bind->is_static(), Variant::NIL, "Method is not static."); + return bind->call(nullptr, p_arguments + 2, p_argcount - 2, r_call_error); +} + PackedStringArray ClassDB::class_get_integer_constant_list(const StringName &p_class, bool p_no_inheritance) const { List<String> constants; ::ClassDB::get_integer_constant_list(p_class, &constants, p_no_inheritance); @@ -1601,6 +1641,8 @@ void ClassDB::_bind_methods() { ::ClassDB::bind_method(D_METHOD("class_get_signal_list", "class", "no_inheritance"), &ClassDB::class_get_signal_list, DEFVAL(false)); ::ClassDB::bind_method(D_METHOD("class_get_property_list", "class", "no_inheritance"), &ClassDB::class_get_property_list, DEFVAL(false)); + ::ClassDB::bind_method(D_METHOD("class_get_property_getter", "class", "property"), &ClassDB::class_get_property_getter); + ::ClassDB::bind_method(D_METHOD("class_get_property_setter", "class", "property"), &ClassDB::class_get_property_setter); ::ClassDB::bind_method(D_METHOD("class_get_property", "object", "property"), &ClassDB::class_get_property); ::ClassDB::bind_method(D_METHOD("class_set_property", "object", "property", "value"), &ClassDB::class_set_property); @@ -1612,6 +1654,8 @@ void ClassDB::_bind_methods() { ::ClassDB::bind_method(D_METHOD("class_get_method_list", "class", "no_inheritance"), &ClassDB::class_get_method_list, DEFVAL(false)); + ::ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "class_call_static_method", &ClassDB::class_call_static_method, MethodInfo("class_call_static_method", PropertyInfo(Variant::STRING_NAME, "class"), PropertyInfo(Variant::STRING_NAME, "method"))); + ::ClassDB::bind_method(D_METHOD("class_get_integer_constant_list", "class", "no_inheritance"), &ClassDB::class_get_integer_constant_list, DEFVAL(false)); ::ClassDB::bind_method(D_METHOD("class_has_integer_constant", "class", "name"), &ClassDB::class_has_integer_constant); @@ -1738,7 +1782,7 @@ Object *Engine::get_singleton_object(const StringName &p_name) const { void Engine::register_singleton(const StringName &p_name, Object *p_object) { ERR_FAIL_COND_MSG(has_singleton(p_name), "Singleton already registered: " + String(p_name)); - ERR_FAIL_COND_MSG(!String(p_name).is_valid_identifier(), "Singleton name is not a valid identifier: " + p_name); + ERR_FAIL_COND_MSG(!String(p_name).is_valid_ascii_identifier(), "Singleton name is not a valid identifier: " + p_name); ::Engine::Singleton s; s.class_name = p_name; s.name = p_name; diff --git a/core/core_bind.h b/core/core_bind.h index b142a2fbbd..7e2686c848 100644 --- a/core/core_bind.h +++ b/core/core_bind.h @@ -73,7 +73,7 @@ public: static ResourceLoader *get_singleton() { return singleton; } Error load_threaded_request(const String &p_path, const String &p_type_hint = "", bool p_use_sub_threads = false, CacheMode p_cache_mode = CACHE_MODE_REUSE); - ThreadLoadStatus load_threaded_get_status(const String &p_path, Array r_progress = Array()); + ThreadLoadStatus load_threaded_get_status(const String &p_path, Array r_progress = ClassDB::default_array_arg); Ref<Resource> load_threaded_get(const String &p_path); Ref<Resource> load(const String &p_path, const String &p_type_hint = "", CacheMode p_cache_mode = CACHE_MODE_REUSE); @@ -83,6 +83,7 @@ public: void set_abort_on_missing_resources(bool p_abort); PackedStringArray get_dependencies(const String &p_path); bool has_cached(const String &p_path); + Ref<Resource> get_cached_ref(const String &p_path); bool exists(const String &p_path, const String &p_type_hint = ""); ResourceUID::ID get_resource_uid(const String &p_path); @@ -127,11 +128,18 @@ protected: static void _bind_methods(); static OS *singleton; +#ifndef DISABLE_DEPRECATED + Dictionary _execute_with_pipe_bind_compat_94434(const String &p_path, const Vector<String> &p_arguments); + + static void _bind_compatibility_methods(); +#endif + public: enum RenderingDriver { RENDERING_DRIVER_VULKAN, RENDERING_DRIVER_OPENGL3, RENDERING_DRIVER_D3D12, + RENDERING_DRIVER_METAL, }; PackedByteArray get_entropy(int p_bytes); @@ -158,8 +166,8 @@ public: Vector<String> get_system_font_path_for_text(const String &p_font_name, const String &p_text, const String &p_locale = String(), const String &p_script = String(), int p_weight = 400, int p_stretch = 100, bool p_italic = false) const; String get_executable_path() const; String read_string_from_stdin(); - int execute(const String &p_path, const Vector<String> &p_arguments, Array r_output = Array(), bool p_read_stderr = false, bool p_open_console = false); - Dictionary execute_with_pipe(const String &p_path, const Vector<String> &p_arguments); + int execute(const String &p_path, const Vector<String> &p_arguments, Array r_output = ClassDB::default_array_arg, bool p_read_stderr = false, bool p_open_console = false); + Dictionary execute_with_pipe(const String &p_path, const Vector<String> &p_arguments, bool p_blocking = true); int create_process(const String &p_path, const Vector<String> &p_arguments, bool p_open_console = false); int create_instance(const Vector<String> &p_arguments); Error kill(int p_pid); @@ -389,12 +397,17 @@ class Semaphore : public RefCounted { GDCLASS(Semaphore, RefCounted); ::Semaphore semaphore; +protected: static void _bind_methods(); +#ifndef DISABLE_DEPRECATED + void _post_bind_compat_93605(); + static void _bind_compatibility_methods(); +#endif // DISABLE_DEPRECATED public: void wait(); bool try_wait(); - void post(); + void post(int p_count = 1); }; class Thread : public RefCounted { @@ -447,6 +460,8 @@ public: TypedArray<Dictionary> class_get_signal_list(const StringName &p_class, bool p_no_inheritance = false) const; TypedArray<Dictionary> class_get_property_list(const StringName &p_class, bool p_no_inheritance = false) const; + StringName class_get_property_getter(const StringName &p_class, const StringName &p_property); + StringName class_get_property_setter(const StringName &p_class, const StringName &p_property); Variant class_get_property(Object *p_object, const StringName &p_property) const; Error class_set_property(Object *p_object, const StringName &p_property, const Variant &p_value) const; @@ -457,6 +472,7 @@ public: int class_get_method_argument_count(const StringName &p_class, const StringName &p_method, bool p_no_inheritance = false) const; TypedArray<Dictionary> class_get_method_list(const StringName &p_class, bool p_no_inheritance = false) const; + Variant class_call_static_method(const Variant **p_arguments, int p_argcount, Callable::CallError &r_call_error); PackedStringArray class_get_integer_constant_list(const StringName &p_class, bool p_no_inheritance = false) const; bool class_has_integer_constant(const StringName &p_class, const StringName &p_name) const; diff --git a/core/core_constants.cpp b/core/core_constants.cpp index 5322e39ec0..68af5abf66 100644 --- a/core/core_constants.cpp +++ b/core/core_constants.cpp @@ -671,6 +671,7 @@ void register_global_constants() { BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_INT_IS_OBJECTID); BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_INT_IS_POINTER); BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_ARRAY_TYPE); + BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_DICTIONARY_TYPE); BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_LOCALE_ID); BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_LOCALIZABLE_STRING); BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_NODE_TYPE); diff --git a/core/crypto/crypto.cpp b/core/crypto/crypto.cpp index d3d0079410..62bacadf91 100644 --- a/core/crypto/crypto.cpp +++ b/core/crypto/crypto.cpp @@ -36,10 +36,10 @@ /// Resources -CryptoKey *(*CryptoKey::_create)() = nullptr; -CryptoKey *CryptoKey::create() { +CryptoKey *(*CryptoKey::_create)(bool p_notify_postinitialize) = nullptr; +CryptoKey *CryptoKey::create(bool p_notify_postinitialize) { if (_create) { - return _create(); + return _create(p_notify_postinitialize); } return nullptr; } @@ -52,10 +52,10 @@ void CryptoKey::_bind_methods() { ClassDB::bind_method(D_METHOD("load_from_string", "string_key", "public_only"), &CryptoKey::load_from_string, DEFVAL(false)); } -X509Certificate *(*X509Certificate::_create)() = nullptr; -X509Certificate *X509Certificate::create() { +X509Certificate *(*X509Certificate::_create)(bool p_notify_postinitialize) = nullptr; +X509Certificate *X509Certificate::create(bool p_notify_postinitialize) { if (_create) { - return _create(); + return _create(p_notify_postinitialize); } return nullptr; } @@ -116,10 +116,10 @@ void HMACContext::_bind_methods() { ClassDB::bind_method(D_METHOD("finish"), &HMACContext::finish); } -HMACContext *(*HMACContext::_create)() = nullptr; -HMACContext *HMACContext::create() { +HMACContext *(*HMACContext::_create)(bool p_notify_postinitialize) = nullptr; +HMACContext *HMACContext::create(bool p_notify_postinitialize) { if (_create) { - return _create(); + return _create(p_notify_postinitialize); } ERR_FAIL_V_MSG(nullptr, "HMACContext is not available when the mbedtls module is disabled."); } @@ -127,10 +127,10 @@ HMACContext *HMACContext::create() { /// Crypto void (*Crypto::_load_default_certificates)(const String &p_path) = nullptr; -Crypto *(*Crypto::_create)() = nullptr; -Crypto *Crypto::create() { +Crypto *(*Crypto::_create)(bool p_notify_postinitialize) = nullptr; +Crypto *Crypto::create(bool p_notify_postinitialize) { if (_create) { - return _create(); + return _create(p_notify_postinitialize); } ERR_FAIL_V_MSG(nullptr, "Crypto is not available when the mbedtls module is disabled."); } diff --git a/core/crypto/crypto.h b/core/crypto/crypto.h index 16649422cf..c19e6b6773 100644 --- a/core/crypto/crypto.h +++ b/core/crypto/crypto.h @@ -42,10 +42,10 @@ class CryptoKey : public Resource { protected: static void _bind_methods(); - static CryptoKey *(*_create)(); + static CryptoKey *(*_create)(bool p_notify_postinitialize); public: - static CryptoKey *create(); + static CryptoKey *create(bool p_notify_postinitialize = true); virtual Error load(const String &p_path, bool p_public_only = false) = 0; virtual Error save(const String &p_path, bool p_public_only = false) = 0; virtual String save_to_string(bool p_public_only = false) = 0; @@ -58,10 +58,10 @@ class X509Certificate : public Resource { protected: static void _bind_methods(); - static X509Certificate *(*_create)(); + static X509Certificate *(*_create)(bool p_notify_postinitialize); public: - static X509Certificate *create(); + static X509Certificate *create(bool p_notify_postinitialize = true); virtual Error load(const String &p_path) = 0; virtual Error load_from_memory(const uint8_t *p_buffer, int p_len) = 0; virtual Error save(const String &p_path) = 0; @@ -106,10 +106,10 @@ class HMACContext : public RefCounted { protected: static void _bind_methods(); - static HMACContext *(*_create)(); + static HMACContext *(*_create)(bool p_notify_postinitialize); public: - static HMACContext *create(); + static HMACContext *create(bool p_notify_postinitialize = true); virtual Error start(HashingContext::HashType p_hash_type, const PackedByteArray &p_key) = 0; virtual Error update(const PackedByteArray &p_data) = 0; @@ -124,11 +124,11 @@ class Crypto : public RefCounted { protected: static void _bind_methods(); - static Crypto *(*_create)(); + static Crypto *(*_create)(bool p_notify_postinitialize); static void (*_load_default_certificates)(const String &p_path); public: - static Crypto *create(); + static Crypto *create(bool p_notify_postinitialize = true); static void load_default_certificates(const String &p_path); virtual PackedByteArray generate_random_bytes(int p_bytes) = 0; diff --git a/core/debugger/remote_debugger.cpp b/core/debugger/remote_debugger.cpp index bd30da3047..e2ed7245a2 100644 --- a/core/debugger/remote_debugger.cpp +++ b/core/debugger/remote_debugger.cpp @@ -39,6 +39,7 @@ #include "core/io/resource_loader.h" #include "core/object/script_language.h" #include "core/os/os.h" +#include "servers/display_server.h" class RemoteDebugger::PerformanceProfiler : public EngineProfiler { Object *performance = nullptr; @@ -539,7 +540,7 @@ void RemoteDebugger::debug(bool p_can_continue, bool p_is_error_breakpoint) { OS::get_singleton()->delay_usec(10000); if (Thread::get_caller_id() == Thread::get_main_id()) { // If this is a busy loop on the main thread, events still need to be processed. - OS::get_singleton()->process_and_drop_events(); + DisplayServer::get_singleton()->force_process_and_drop_events(); } } } diff --git a/core/debugger/remote_debugger_peer.cpp b/core/debugger/remote_debugger_peer.cpp index 21a9014626..9dca47a0b4 100644 --- a/core/debugger/remote_debugger_peer.cpp +++ b/core/debugger/remote_debugger_peer.cpp @@ -144,9 +144,8 @@ void RemoteDebuggerPeerTCP::_read_in() { Error err = decode_variant(var, buf, in_pos, &read); ERR_CONTINUE(read != in_pos || err != OK); ERR_CONTINUE_MSG(var.get_type() != Variant::ARRAY, "Malformed packet received, not an Array."); - mutex.lock(); + MutexLock lock(mutex); in_queue.push_back(var); - mutex.unlock(); } } } diff --git a/core/doc_data.cpp b/core/doc_data.cpp index 672a36c35c..f40e878d52 100644 --- a/core/doc_data.cpp +++ b/core/doc_data.cpp @@ -33,6 +33,8 @@ String DocData::get_default_value_string(const Variant &p_value) { if (p_value.get_type() == Variant::ARRAY) { return Variant(Array(p_value, 0, StringName(), Variant())).get_construct_string().replace("\n", " "); + } else if (p_value.get_type() == Variant::DICTIONARY) { + return Variant(Dictionary(p_value, 0, StringName(), Variant(), 0, StringName(), Variant())).get_construct_string().replace("\n", " "); } else { return p_value.get_construct_string().replace("\n", " "); } @@ -57,6 +59,8 @@ void DocData::return_doc_from_retinfo(DocData::MethodDoc &p_method, const Proper p_method.return_type = p_retinfo.class_name; } else if (p_retinfo.type == Variant::ARRAY && p_retinfo.hint == PROPERTY_HINT_ARRAY_TYPE) { p_method.return_type = p_retinfo.hint_string + "[]"; + } else if (p_retinfo.type == Variant::DICTIONARY && p_retinfo.hint == PROPERTY_HINT_DICTIONARY_TYPE) { + p_method.return_type = "Dictionary[" + p_retinfo.hint_string.replace(";", ", ") + "]"; } else if (p_retinfo.hint == PROPERTY_HINT_RESOURCE_TYPE) { p_method.return_type = p_retinfo.hint_string; } else if (p_retinfo.type == Variant::NIL && p_retinfo.usage & PROPERTY_USAGE_NIL_IS_VARIANT) { @@ -89,6 +93,8 @@ void DocData::argument_doc_from_arginfo(DocData::ArgumentDoc &p_argument, const p_argument.type = p_arginfo.class_name; } else if (p_arginfo.type == Variant::ARRAY && p_arginfo.hint == PROPERTY_HINT_ARRAY_TYPE) { p_argument.type = p_arginfo.hint_string + "[]"; + } else if (p_arginfo.type == Variant::DICTIONARY && p_arginfo.hint == PROPERTY_HINT_DICTIONARY_TYPE) { + p_argument.type = "Dictionary[" + p_arginfo.hint_string.replace(";", ", ") + "]"; } else if (p_arginfo.hint == PROPERTY_HINT_RESOURCE_TYPE) { p_argument.type = p_arginfo.hint_string; } else if (p_arginfo.type == Variant::NIL) { diff --git a/core/doc_data.h b/core/doc_data.h index 04bd55eaba..6a7f4355db 100644 --- a/core/doc_data.h +++ b/core/doc_data.h @@ -522,6 +522,10 @@ public: String type; String data_type; String description; + bool is_deprecated = false; + String deprecated_message; + bool is_experimental = false; + String experimental_message; String default_value; String keywords; bool operator<(const ThemeItemDoc &p_theme_item) const { @@ -550,6 +554,16 @@ public: doc.description = p_dict["description"]; } + if (p_dict.has("deprecated")) { + doc.is_deprecated = true; + doc.deprecated_message = p_dict["deprecated"]; + } + + if (p_dict.has("experimental")) { + doc.is_experimental = true; + doc.experimental_message = p_dict["experimental"]; + } + if (p_dict.has("default_value")) { doc.default_value = p_dict["default_value"]; } @@ -579,6 +593,14 @@ public: dict["description"] = p_doc.description; } + if (p_doc.is_deprecated) { + dict["deprecated"] = p_doc.deprecated_message; + } + + if (p_doc.is_experimental) { + dict["experimental"] = p_doc.experimental_message; + } + if (!p_doc.default_value.is_empty()) { dict["default_value"] = p_doc.default_value; } diff --git a/core/error/error_list.h b/core/error/error_list.h index abc637106a..cdf06eb06d 100644 --- a/core/error/error_list.h +++ b/core/error/error_list.h @@ -41,6 +41,7 @@ * - Are added to the Error enum in core/error/error_list.h * - Have a description added to error_names in core/error/error_list.cpp * - Are bound with BIND_CORE_ENUM_CONSTANT() in core/core_constants.cpp + * - Have a matching Android version in platform/android/java/lib/src/org/godotengine/godot/error/Error.kt */ enum Error { diff --git a/core/error/error_macros.cpp b/core/error/error_macros.cpp index 8376c0aaf8..813ee7684f 100644 --- a/core/error/error_macros.cpp +++ b/core/error/error_macros.cpp @@ -34,6 +34,12 @@ #include "core/os/os.h" #include "core/string/ustring.h" +// Optional physics interpolation warnings try to include the path to the relevant node. +#if defined(DEBUG_ENABLED) && defined(TOOLS_ENABLED) +#include "core/config/project_settings.h" +#include "scene/main/node.h" +#endif + static ErrorHandlerList *error_handler_list = nullptr; void add_error_handler(ErrorHandlerList *p_handler) { @@ -128,3 +134,48 @@ void _err_print_index_error(const char *p_function, const char *p_file, int p_li void _err_flush_stdout() { fflush(stdout); } + +// Prevent error spam by limiting the warnings to a certain frequency. +void _physics_interpolation_warning(const char *p_function, const char *p_file, int p_line, ObjectID p_id, const char *p_warn_string) { +#if defined(DEBUG_ENABLED) && defined(TOOLS_ENABLED) + const uint32_t warn_max = 2048; + const uint32_t warn_timeout_seconds = 15; + + static uint32_t warn_count = warn_max; + static uint32_t warn_timeout = warn_timeout_seconds; + + uint32_t time_now = UINT32_MAX; + + if (warn_count) { + warn_count--; + } + + if (!warn_count) { + time_now = OS::get_singleton()->get_ticks_msec() / 1000; + } + + if ((warn_count == 0) && (time_now >= warn_timeout)) { + warn_count = warn_max; + warn_timeout = time_now + warn_timeout_seconds; + + if (GLOBAL_GET("debug/settings/physics_interpolation/enable_warnings")) { + // UINT64_MAX means unused. + if (p_id.operator uint64_t() == UINT64_MAX) { + _err_print_error(p_function, p_file, p_line, "[Physics interpolation] " + String(p_warn_string) + " (possibly benign).", false, ERR_HANDLER_WARNING); + } else { + String node_name; + if (p_id.is_valid()) { + Node *node = Object::cast_to<Node>(ObjectDB::get_instance(p_id)); + if (node && node->is_inside_tree()) { + node_name = "\"" + String(node->get_path()) + "\""; + } else { + node_name = "\"unknown\""; + } + } + + _err_print_error(p_function, p_file, p_line, "[Physics interpolation] " + String(p_warn_string) + ": " + node_name + " (possibly benign).", false, ERR_HANDLER_WARNING); + } + } + } +#endif +} diff --git a/core/error/error_macros.h b/core/error/error_macros.h index ab7dbcbd44..19c16667d0 100644 --- a/core/error/error_macros.h +++ b/core/error/error_macros.h @@ -31,6 +31,7 @@ #ifndef ERROR_MACROS_H #define ERROR_MACROS_H +#include "core/object/object_id.h" #include "core/typedefs.h" #include <atomic> // We'd normally use safe_refcount.h, but that would cause circular includes. @@ -71,6 +72,8 @@ void _err_print_index_error(const char *p_function, const char *p_file, int p_li void _err_print_index_error(const char *p_function, const char *p_file, int p_line, int64_t p_index, int64_t p_size, const char *p_index_str, const char *p_size_str, const String &p_message, bool p_editor_notify = false, bool fatal = false); void _err_flush_stdout(); +void _physics_interpolation_warning(const char *p_function, const char *p_file, int p_line, ObjectID p_id, const char *p_warn_string); + #ifdef __GNUC__ //#define FUNCTION_STR __PRETTY_FUNCTION__ - too annoying #define FUNCTION_STR __FUNCTION__ @@ -832,4 +835,14 @@ void _err_flush_stdout(); #define DEV_CHECK_ONCE(m_cond) #endif +/** + * Physics Interpolation warnings. + * These are spam protection warnings. + */ +#define PHYSICS_INTERPOLATION_NODE_WARNING(m_object_id, m_string) \ + _physics_interpolation_warning(FUNCTION_STR, __FILE__, __LINE__, m_object_id, m_string) + +#define PHYSICS_INTERPOLATION_WARNING(m_string) \ + _physics_interpolation_warning(FUNCTION_STR, __FILE__, __LINE__, ObjectID(UINT64_MAX), m_string) + #endif // ERROR_MACROS_H diff --git a/core/extension/extension_api_dump.cpp b/core/extension/extension_api_dump.cpp index 848b6f3886..4042d6b80d 100644 --- a/core/extension/extension_api_dump.cpp +++ b/core/extension/extension_api_dump.cpp @@ -60,6 +60,9 @@ static String get_property_info_type_name(const PropertyInfo &p_info) { if (p_info.type == Variant::ARRAY && (p_info.hint == PROPERTY_HINT_ARRAY_TYPE)) { return String("typedarray::") + p_info.hint_string; } + if (p_info.type == Variant::DICTIONARY && (p_info.hint == PROPERTY_HINT_DICTIONARY_TYPE)) { + return String("typeddictionary::") + p_info.hint_string; + } if (p_info.type == Variant::INT && (p_info.usage & (PROPERTY_USAGE_CLASS_IS_ENUM))) { return String("enum::") + String(p_info.class_name); } @@ -85,7 +88,7 @@ static String get_property_info_type_name(const PropertyInfo &p_info) { } static String get_type_meta_name(const GodotTypeInfo::Metadata metadata) { - static const char *argmeta[11] = { "none", "int8", "int16", "int32", "int64", "uint8", "uint16", "uint32", "uint64", "float", "double" }; + static const char *argmeta[13] = { "none", "int8", "int16", "int32", "int64", "uint8", "uint16", "uint32", "uint64", "float", "double", "char16", "char32" }; return argmeta[metadata]; } diff --git a/core/extension/gdextension.cpp b/core/extension/gdextension.cpp index 47628e4ea0..7cba5cb161 100644 --- a/core/extension/gdextension.cpp +++ b/core/extension/gdextension.cpp @@ -32,11 +32,9 @@ #include "gdextension.compat.inc" #include "core/config/project_settings.h" -#include "core/io/dir_access.h" #include "core/object/class_db.h" #include "core/object/method_bind.h" -#include "core/os/os.h" -#include "core/version.h" +#include "gdextension_library_loader.h" #include "gdextension_manager.h" extern void gdextension_setup_interface(); @@ -48,146 +46,6 @@ String GDExtension::get_extension_list_config_file() { return ProjectSettings::get_singleton()->get_project_data_path().path_join("extension_list.cfg"); } -Vector<SharedObject> GDExtension::find_extension_dependencies(const String &p_path, Ref<ConfigFile> p_config, std::function<bool(String)> p_has_feature) { - Vector<SharedObject> dependencies_shared_objects; - if (p_config->has_section("dependencies")) { - List<String> config_dependencies; - p_config->get_section_keys("dependencies", &config_dependencies); - - for (const String &dependency : config_dependencies) { - Vector<String> dependency_tags = dependency.split("."); - bool all_tags_met = true; - for (int i = 0; i < dependency_tags.size(); i++) { - String tag = dependency_tags[i].strip_edges(); - if (!p_has_feature(tag)) { - all_tags_met = false; - break; - } - } - - if (all_tags_met) { - Dictionary dependency_value = p_config->get_value("dependencies", dependency); - for (const Variant *key = dependency_value.next(nullptr); key; key = dependency_value.next(key)) { - String dependency_path = *key; - String target_path = dependency_value[*key]; - if (dependency_path.is_relative_path()) { - dependency_path = p_path.get_base_dir().path_join(dependency_path); - } - dependencies_shared_objects.push_back(SharedObject(dependency_path, dependency_tags, target_path)); - } - break; - } - } - } - - return dependencies_shared_objects; -} - -String GDExtension::find_extension_library(const String &p_path, Ref<ConfigFile> p_config, std::function<bool(String)> p_has_feature, PackedStringArray *r_tags) { - // First, check the explicit libraries. - if (p_config->has_section("libraries")) { - List<String> libraries; - p_config->get_section_keys("libraries", &libraries); - - // Iterate the libraries, finding the best matching tags. - String best_library_path; - Vector<String> best_library_tags; - for (const String &E : libraries) { - Vector<String> tags = E.split("."); - bool all_tags_met = true; - for (int i = 0; i < tags.size(); i++) { - String tag = tags[i].strip_edges(); - if (!p_has_feature(tag)) { - all_tags_met = false; - break; - } - } - - if (all_tags_met && tags.size() > best_library_tags.size()) { - best_library_path = p_config->get_value("libraries", E); - best_library_tags = tags; - } - } - - if (!best_library_path.is_empty()) { - if (best_library_path.is_relative_path()) { - best_library_path = p_path.get_base_dir().path_join(best_library_path); - } - if (r_tags != nullptr) { - r_tags->append_array(best_library_tags); - } - return best_library_path; - } - } - - // Second, try to autodetect - String autodetect_library_prefix; - if (p_config->has_section_key("configuration", "autodetect_library_prefix")) { - autodetect_library_prefix = p_config->get_value("configuration", "autodetect_library_prefix"); - } - if (!autodetect_library_prefix.is_empty()) { - String autodetect_path = autodetect_library_prefix; - if (autodetect_path.is_relative_path()) { - autodetect_path = p_path.get_base_dir().path_join(autodetect_path); - } - - // Find the folder and file parts of the prefix. - String folder; - String file_prefix; - if (DirAccess::dir_exists_absolute(autodetect_path)) { - folder = autodetect_path; - } else if (DirAccess::dir_exists_absolute(autodetect_path.get_base_dir())) { - folder = autodetect_path.get_base_dir(); - file_prefix = autodetect_path.get_file(); - } else { - ERR_FAIL_V_MSG(String(), vformat("Error in extension: %s. Could not find folder for automatic detection of libraries files. autodetect_library_prefix=\"%s\"", p_path, autodetect_library_prefix)); - } - - // Open the folder. - Ref<DirAccess> dir = DirAccess::open(folder); - ERR_FAIL_COND_V_MSG(!dir.is_valid(), String(), vformat("Error in extension: %s. Could not open folder for automatic detection of libraries files. autodetect_library_prefix=\"%s\"", p_path, autodetect_library_prefix)); - - // Iterate the files and check the prefixes, finding the best matching file. - String best_file; - Vector<String> best_file_tags; - dir->list_dir_begin(); - String file_name = dir->_get_next(); - while (file_name != "") { - if (!dir->current_is_dir() && file_name.begins_with(file_prefix)) { - // Check if the files matches all requested feature tags. - String tags_str = file_name.trim_prefix(file_prefix); - tags_str = tags_str.trim_suffix(tags_str.get_extension()); - - Vector<String> tags = tags_str.split(".", false); - bool all_tags_met = true; - for (int i = 0; i < tags.size(); i++) { - String tag = tags[i].strip_edges(); - if (!p_has_feature(tag)) { - all_tags_met = false; - break; - } - } - - // If all tags are found in the feature list, and we found more tags than before, use this file. - if (all_tags_met && tags.size() > best_file_tags.size()) { - best_file_tags = tags; - best_file = file_name; - } - } - file_name = dir->_get_next(); - } - - if (!best_file.is_empty()) { - String library_path = folder.path_join(best_file); - if (r_tags != nullptr) { - r_tags->append_array(best_file_tags); - } - return library_path; - } - } - return String(); -} - class GDExtensionMethodBind : public MethodBind { GDExtensionClassMethodCall call_func; GDExtensionClassMethodValidatedCall validated_call_func; @@ -382,7 +240,7 @@ public: #ifndef DISABLE_DEPRECATED void GDExtension::_register_extension_class(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_parent_class_name, const GDExtensionClassCreationInfo *p_extension_funcs) { - const GDExtensionClassCreationInfo3 class_info3 = { + const GDExtensionClassCreationInfo4 class_info4 = { p_extension_funcs->is_virtual, // GDExtensionBool is_virtual; p_extension_funcs->is_abstract, // GDExtensionBool is_abstract; true, // GDExtensionBool is_exposed; @@ -398,25 +256,26 @@ void GDExtension::_register_extension_class(GDExtensionClassLibraryPtr p_library p_extension_funcs->to_string_func, // GDExtensionClassToString to_string_func; p_extension_funcs->reference_func, // GDExtensionClassReference reference_func; p_extension_funcs->unreference_func, // GDExtensionClassUnreference unreference_func; - p_extension_funcs->create_instance_func, // GDExtensionClassCreateInstance create_instance_func; /* this one is mandatory */ + nullptr, // GDExtensionClassCreateInstance2 create_instance_func; /* this one is mandatory */ p_extension_funcs->free_instance_func, // GDExtensionClassFreeInstance free_instance_func; /* this one is mandatory */ nullptr, // GDExtensionClassRecreateInstance recreate_instance_func; p_extension_funcs->get_virtual_func, // GDExtensionClassGetVirtual get_virtual_func; nullptr, // GDExtensionClassGetVirtualCallData get_virtual_call_data_func; nullptr, // GDExtensionClassCallVirtualWithData call_virtual_func; - p_extension_funcs->get_rid_func, // GDExtensionClassGetRID get_rid; p_extension_funcs->class_userdata, // void *class_userdata; }; const ClassCreationDeprecatedInfo legacy = { p_extension_funcs->notification_func, // GDExtensionClassNotification notification_func; p_extension_funcs->free_property_list_func, // GDExtensionClassFreePropertyList free_property_list_func; + p_extension_funcs->create_instance_func, // GDExtensionClassCreateInstance create_instance_func; + p_extension_funcs->get_rid_func, // GDExtensionClassGetRID get_rid; }; - _register_extension_class_internal(p_library, p_class_name, p_parent_class_name, &class_info3, &legacy); + _register_extension_class_internal(p_library, p_class_name, p_parent_class_name, &class_info4, &legacy); } void GDExtension::_register_extension_class2(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_parent_class_name, const GDExtensionClassCreationInfo2 *p_extension_funcs) { - const GDExtensionClassCreationInfo3 class_info3 = { + const GDExtensionClassCreationInfo4 class_info4 = { p_extension_funcs->is_virtual, // GDExtensionBool is_virtual; p_extension_funcs->is_abstract, // GDExtensionBool is_abstract; p_extension_funcs->is_exposed, // GDExtensionBool is_exposed; @@ -432,34 +291,71 @@ void GDExtension::_register_extension_class2(GDExtensionClassLibraryPtr p_librar p_extension_funcs->to_string_func, // GDExtensionClassToString to_string_func; p_extension_funcs->reference_func, // GDExtensionClassReference reference_func; p_extension_funcs->unreference_func, // GDExtensionClassUnreference unreference_func; - p_extension_funcs->create_instance_func, // GDExtensionClassCreateInstance create_instance_func; /* this one is mandatory */ + nullptr, // GDExtensionClassCreateInstance2 create_instance_func; /* this one is mandatory */ p_extension_funcs->free_instance_func, // GDExtensionClassFreeInstance free_instance_func; /* this one is mandatory */ p_extension_funcs->recreate_instance_func, // GDExtensionClassRecreateInstance recreate_instance_func; p_extension_funcs->get_virtual_func, // GDExtensionClassGetVirtual get_virtual_func; p_extension_funcs->get_virtual_call_data_func, // GDExtensionClassGetVirtualCallData get_virtual_call_data_func; p_extension_funcs->call_virtual_with_data_func, // GDExtensionClassCallVirtualWithData call_virtual_func; - p_extension_funcs->get_rid_func, // GDExtensionClassGetRID get_rid; p_extension_funcs->class_userdata, // void *class_userdata; }; const ClassCreationDeprecatedInfo legacy = { nullptr, // GDExtensionClassNotification notification_func; p_extension_funcs->free_property_list_func, // GDExtensionClassFreePropertyList free_property_list_func; + p_extension_funcs->create_instance_func, // GDExtensionClassCreateInstance create_instance_func; + p_extension_funcs->get_rid_func, // GDExtensionClassGetRID get_rid; }; - _register_extension_class_internal(p_library, p_class_name, p_parent_class_name, &class_info3, &legacy); + _register_extension_class_internal(p_library, p_class_name, p_parent_class_name, &class_info4, &legacy); } -#endif // DISABLE_DEPRECATED void GDExtension::_register_extension_class3(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_parent_class_name, const GDExtensionClassCreationInfo3 *p_extension_funcs) { + const GDExtensionClassCreationInfo4 class_info4 = { + p_extension_funcs->is_virtual, // GDExtensionBool is_virtual; + p_extension_funcs->is_abstract, // GDExtensionBool is_abstract; + p_extension_funcs->is_exposed, // GDExtensionBool is_exposed; + p_extension_funcs->is_runtime, // GDExtensionBool is_runtime; + p_extension_funcs->set_func, // GDExtensionClassSet set_func; + p_extension_funcs->get_func, // GDExtensionClassGet get_func; + p_extension_funcs->get_property_list_func, // GDExtensionClassGetPropertyList get_property_list_func; + p_extension_funcs->free_property_list_func, // GDExtensionClassFreePropertyList free_property_list_func; + p_extension_funcs->property_can_revert_func, // GDExtensionClassPropertyCanRevert property_can_revert_func; + p_extension_funcs->property_get_revert_func, // GDExtensionClassPropertyGetRevert property_get_revert_func; + p_extension_funcs->validate_property_func, // GDExtensionClassValidateProperty validate_property_func; + p_extension_funcs->notification_func, // GDExtensionClassNotification2 notification_func; + p_extension_funcs->to_string_func, // GDExtensionClassToString to_string_func; + p_extension_funcs->reference_func, // GDExtensionClassReference reference_func; + p_extension_funcs->unreference_func, // GDExtensionClassUnreference unreference_func; + nullptr, // GDExtensionClassCreateInstance2 create_instance_func; /* this one is mandatory */ + p_extension_funcs->free_instance_func, // GDExtensionClassFreeInstance free_instance_func; /* this one is mandatory */ + p_extension_funcs->recreate_instance_func, // GDExtensionClassRecreateInstance recreate_instance_func; + p_extension_funcs->get_virtual_func, // GDExtensionClassGetVirtual get_virtual_func; + p_extension_funcs->get_virtual_call_data_func, // GDExtensionClassGetVirtualCallData get_virtual_call_data_func; + p_extension_funcs->call_virtual_with_data_func, // GDExtensionClassCallVirtualWithData call_virtual_func; + p_extension_funcs->class_userdata, // void *class_userdata; + }; + + const ClassCreationDeprecatedInfo legacy = { + nullptr, // GDExtensionClassNotification notification_func; + nullptr, // GDExtensionClassFreePropertyList free_property_list_func; + p_extension_funcs->create_instance_func, // GDExtensionClassCreateInstance2 create_instance_func; + p_extension_funcs->get_rid_func, // GDExtensionClassGetRID get_rid; + }; + _register_extension_class_internal(p_library, p_class_name, p_parent_class_name, &class_info4, &legacy); +} + +#endif // DISABLE_DEPRECATED + +void GDExtension::_register_extension_class4(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_parent_class_name, const GDExtensionClassCreationInfo4 *p_extension_funcs) { _register_extension_class_internal(p_library, p_class_name, p_parent_class_name, p_extension_funcs); } -void GDExtension::_register_extension_class_internal(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_parent_class_name, const GDExtensionClassCreationInfo3 *p_extension_funcs, const ClassCreationDeprecatedInfo *p_deprecated_funcs) { +void GDExtension::_register_extension_class_internal(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_parent_class_name, const GDExtensionClassCreationInfo4 *p_extension_funcs, const ClassCreationDeprecatedInfo *p_deprecated_funcs) { GDExtension *self = reinterpret_cast<GDExtension *>(p_library); StringName class_name = *reinterpret_cast<const StringName *>(p_class_name); StringName parent_class_name = *reinterpret_cast<const StringName *>(p_parent_class_name); - ERR_FAIL_COND_MSG(!String(class_name).is_valid_identifier(), "Attempt to register extension class '" + class_name + "', which is not a valid class identifier."); + ERR_FAIL_COND_MSG(!String(class_name).is_valid_unicode_identifier(), "Attempt to register extension class '" + class_name + "', which is not a valid class identifier."); ERR_FAIL_COND_MSG(ClassDB::class_exists(class_name), "Attempt to register extension class '" + class_name + "', which appears to be already registered."); Extension *parent_extension = nullptr; @@ -530,6 +426,8 @@ void GDExtension::_register_extension_class_internal(GDExtensionClassLibraryPtr if (p_deprecated_funcs) { extension->gdextension.notification = p_deprecated_funcs->notification_func; extension->gdextension.free_property_list = p_deprecated_funcs->free_property_list_func; + extension->gdextension.create_instance = p_deprecated_funcs->create_instance_func; + extension->gdextension.get_rid = p_deprecated_funcs->get_rid_func; } #endif // DISABLE_DEPRECATED extension->gdextension.notification2 = p_extension_funcs->notification_func; @@ -537,13 +435,12 @@ void GDExtension::_register_extension_class_internal(GDExtensionClassLibraryPtr extension->gdextension.reference = p_extension_funcs->reference_func; extension->gdextension.unreference = p_extension_funcs->unreference_func; extension->gdextension.class_userdata = p_extension_funcs->class_userdata; - extension->gdextension.create_instance = p_extension_funcs->create_instance_func; + extension->gdextension.create_instance2 = p_extension_funcs->create_instance_func; extension->gdextension.free_instance = p_extension_funcs->free_instance_func; extension->gdextension.recreate_instance = p_extension_funcs->recreate_instance_func; extension->gdextension.get_virtual = p_extension_funcs->get_virtual_func; extension->gdextension.get_virtual_call_data = p_extension_funcs->get_virtual_call_data_func; extension->gdextension.call_virtual_with_data = p_extension_funcs->call_virtual_with_data_func; - extension->gdextension.get_rid = p_extension_funcs->get_rid_func; extension->gdextension.reloadable = self->reloadable; #ifdef TOOLS_ENABLED @@ -755,7 +652,13 @@ void GDExtension::_unregister_extension_class(GDExtensionClassLibraryPtr p_libra void GDExtension::_get_library_path(GDExtensionClassLibraryPtr p_library, GDExtensionUninitializedStringPtr r_path) { GDExtension *self = reinterpret_cast<GDExtension *>(p_library); - memnew_placement(r_path, String(self->library_path)); + Ref<GDExtensionLibraryLoader> library_loader = self->loader; + String library_path; + if (library_loader.is_valid()) { + library_path = library_loader->library_path; + } + + memnew_placement(r_path, String(library_path)); } HashMap<StringName, GDExtensionInterfaceFunctionPtr> GDExtension::gdextension_interface_functions; @@ -771,64 +674,32 @@ GDExtensionInterfaceFunctionPtr GDExtension::get_interface_function(const String return *function; } -Error GDExtension::open_library(const String &p_path, const String &p_entry_symbol, Vector<SharedObject> *p_dependencies) { - String abs_path = ProjectSettings::get_singleton()->globalize_path(p_path); - - Vector<String> abs_dependencies_paths; - if (p_dependencies != nullptr && !p_dependencies->is_empty()) { - for (const SharedObject &dependency : *p_dependencies) { - abs_dependencies_paths.push_back(ProjectSettings::get_singleton()->globalize_path(dependency.path)); - } - } - - String actual_lib_path; - OS::GDExtensionData data = { - true, // also_set_library_path - &actual_lib_path, // r_resolved_path - Engine::get_singleton()->is_editor_hint(), // generate_temp_files - &abs_dependencies_paths, // library_dependencies - }; - Error err = OS::get_singleton()->open_dynamic_library(abs_path, library, &data); - - if (actual_lib_path.get_file() != abs_path.get_file()) { - // If temporary files are generated, let's change the library path to point at the original, - // because that's what we want to check to see if it's changed. - library_path = actual_lib_path.get_base_dir().path_join(p_path.get_file()); - } else { - library_path = p_path; - } +Error GDExtension::open_library(const String &p_path, const Ref<GDExtensionLoader> &p_loader) { + ERR_FAIL_COND_V_MSG(p_loader.is_null(), FAILED, "Can't open GDExtension without a loader."); + loader = p_loader; - ERR_FAIL_COND_V_MSG(err == ERR_FILE_NOT_FOUND, err, "GDExtension dynamic library not found: " + abs_path); - ERR_FAIL_COND_V_MSG(err != OK, err, "Can't open GDExtension dynamic library: " + abs_path); + Error err = loader->open_library(p_path); - void *entry_funcptr = nullptr; + ERR_FAIL_COND_V_MSG(err == ERR_FILE_NOT_FOUND, err, "GDExtension dynamic library not found: " + p_path); + ERR_FAIL_COND_V_MSG(err != OK, err, "Can't open GDExtension dynamic library: " + p_path); - err = OS::get_singleton()->get_dynamic_library_symbol_handle(library, p_entry_symbol, entry_funcptr, false); + err = loader->initialize(&gdextension_get_proc_address, this, &initialization); if (err != OK) { - ERR_PRINT("GDExtension entry point '" + p_entry_symbol + "' not found in library " + abs_path); - OS::get_singleton()->close_dynamic_library(library); + // Errors already logged in initialize(). + loader->close_library(); return err; } - GDExtensionInitializationFunction initialization_function = (GDExtensionInitializationFunction)entry_funcptr; - GDExtensionBool ret = initialization_function(&gdextension_get_proc_address, this, &initialization); + level_initialized = -1; - if (ret) { - level_initialized = -1; - return OK; - } else { - ERR_PRINT("GDExtension initialization function '" + p_entry_symbol + "' returned an error."); - OS::get_singleton()->close_dynamic_library(library); - return FAILED; - } + return OK; } void GDExtension::close_library() { - ERR_FAIL_NULL(library); - OS::get_singleton()->close_dynamic_library(library); + ERR_FAIL_COND(!is_library_open()); + loader->close_library(); - library = nullptr; class_icon_paths.clear(); #ifdef TOOLS_ENABLED @@ -837,16 +708,16 @@ void GDExtension::close_library() { } bool GDExtension::is_library_open() const { - return library != nullptr; + return loader.is_valid() && loader->is_library_open(); } GDExtension::InitializationLevel GDExtension::get_minimum_library_initialization_level() const { - ERR_FAIL_NULL_V(library, INITIALIZATION_LEVEL_CORE); + ERR_FAIL_COND_V(!is_library_open(), INITIALIZATION_LEVEL_CORE); return InitializationLevel(initialization.minimum_initialization_level); } void GDExtension::initialize_library(InitializationLevel p_level) { - ERR_FAIL_NULL(library); + ERR_FAIL_COND(!is_library_open()); ERR_FAIL_COND_MSG(p_level <= int32_t(level_initialized), vformat("Level '%d' must be higher than the current level '%d'", p_level, level_initialized)); level_initialized = int32_t(p_level); @@ -856,7 +727,7 @@ void GDExtension::initialize_library(InitializationLevel p_level) { initialization.initialize(initialization.userdata, GDExtensionInitializationLevel(p_level)); } void GDExtension::deinitialize_library(InitializationLevel p_level) { - ERR_FAIL_NULL(library); + ERR_FAIL_COND(!is_library_open()); ERR_FAIL_COND(p_level > int32_t(level_initialized)); level_initialized = int32_t(p_level) - 1; @@ -880,7 +751,7 @@ GDExtension::GDExtension() { } GDExtension::~GDExtension() { - if (library != nullptr) { + if (is_library_open()) { close_library(); } #ifdef TOOLS_ENABLED @@ -897,8 +768,9 @@ void GDExtension::initialize_gdextensions() { #ifndef DISABLE_DEPRECATED register_interface_function("classdb_register_extension_class", (GDExtensionInterfaceFunctionPtr)&GDExtension::_register_extension_class); register_interface_function("classdb_register_extension_class2", (GDExtensionInterfaceFunctionPtr)&GDExtension::_register_extension_class2); -#endif // DISABLE_DEPRECATED register_interface_function("classdb_register_extension_class3", (GDExtensionInterfaceFunctionPtr)&GDExtension::_register_extension_class3); +#endif // DISABLE_DEPRECATED + register_interface_function("classdb_register_extension_class4", (GDExtensionInterfaceFunctionPtr)&GDExtension::_register_extension_class4); register_interface_function("classdb_register_extension_class_method", (GDExtensionInterfaceFunctionPtr)&GDExtension::_register_extension_class_method); register_interface_function("classdb_register_extension_class_virtual_method", (GDExtensionInterfaceFunctionPtr)&GDExtension::_register_extension_class_virtual_method); register_interface_function("classdb_register_extension_class_integer_constant", (GDExtensionInterfaceFunctionPtr)&GDExtension::_register_extension_class_integer_constant); @@ -918,142 +790,15 @@ void GDExtension::finalize_gdextensions() { Error GDExtensionResourceLoader::load_gdextension_resource(const String &p_path, Ref<GDExtension> &p_extension) { ERR_FAIL_COND_V_MSG(p_extension.is_valid() && p_extension->is_library_open(), ERR_ALREADY_IN_USE, "Cannot load GDExtension resource into already opened library."); - Ref<ConfigFile> config; - config.instantiate(); - - Error err = config->load(p_path); - - if (err != OK) { - ERR_PRINT("Error loading GDExtension configuration file: " + p_path); - return err; - } - - if (!config->has_section_key("configuration", "entry_symbol")) { - ERR_PRINT("GDExtension configuration file must contain a \"configuration/entry_symbol\" key: " + p_path); - return ERR_INVALID_DATA; - } - - String entry_symbol = config->get_value("configuration", "entry_symbol"); - - uint32_t compatibility_minimum[3] = { 0, 0, 0 }; - if (config->has_section_key("configuration", "compatibility_minimum")) { - String compat_string = config->get_value("configuration", "compatibility_minimum"); - Vector<int> parts = compat_string.split_ints("."); - for (int i = 0; i < parts.size(); i++) { - if (i >= 3) { - break; - } - if (parts[i] >= 0) { - compatibility_minimum[i] = parts[i]; - } - } - } else { - ERR_PRINT("GDExtension configuration file must contain a \"configuration/compatibility_minimum\" key: " + p_path); - return ERR_INVALID_DATA; - } - - if (compatibility_minimum[0] < 4 || (compatibility_minimum[0] == 4 && compatibility_minimum[1] == 0)) { - ERR_PRINT(vformat("GDExtension's compatibility_minimum (%d.%d.%d) must be at least 4.1.0: %s", compatibility_minimum[0], compatibility_minimum[1], compatibility_minimum[2], p_path)); - return ERR_INVALID_DATA; - } - - bool compatible = true; - // Check version lexicographically. - if (VERSION_MAJOR != compatibility_minimum[0]) { - compatible = VERSION_MAJOR > compatibility_minimum[0]; - } else if (VERSION_MINOR != compatibility_minimum[1]) { - compatible = VERSION_MINOR > compatibility_minimum[1]; - } else { - compatible = VERSION_PATCH >= compatibility_minimum[2]; - } - if (!compatible) { - ERR_PRINT(vformat("GDExtension only compatible with Godot version %d.%d.%d or later: %s", compatibility_minimum[0], compatibility_minimum[1], compatibility_minimum[2], p_path)); - return ERR_INVALID_DATA; - } - - // Optionally check maximum compatibility. - if (config->has_section_key("configuration", "compatibility_maximum")) { - uint32_t compatibility_maximum[3] = { 0, 0, 0 }; - String compat_string = config->get_value("configuration", "compatibility_maximum"); - Vector<int> parts = compat_string.split_ints("."); - for (int i = 0; i < 3; i++) { - if (i < parts.size() && parts[i] >= 0) { - compatibility_maximum[i] = parts[i]; - } else { - // If a version part is missing, set the maximum to an arbitrary high value. - compatibility_maximum[i] = 9999; - } - } - - compatible = true; - if (VERSION_MAJOR != compatibility_maximum[0]) { - compatible = VERSION_MAJOR < compatibility_maximum[0]; - } else if (VERSION_MINOR != compatibility_maximum[1]) { - compatible = VERSION_MINOR < compatibility_maximum[1]; - } -#if VERSION_PATCH - // #if check to avoid -Wtype-limits warning when 0. - else { - compatible = VERSION_PATCH <= compatibility_maximum[2]; - } -#endif - - if (!compatible) { - ERR_PRINT(vformat("GDExtension only compatible with Godot version %s or earlier: %s", compat_string, p_path)); - return ERR_INVALID_DATA; - } - } - - String library_path = GDExtension::find_extension_library(p_path, config, [](const String &p_feature) { return OS::get_singleton()->has_feature(p_feature); }); - - if (library_path.is_empty()) { - const String os_arch = OS::get_singleton()->get_name().to_lower() + "." + Engine::get_singleton()->get_architecture_name(); - ERR_PRINT(vformat("No GDExtension library found for current OS and architecture (%s) in configuration file: %s", os_arch, p_path)); - return ERR_FILE_NOT_FOUND; - } - - bool is_static_library = library_path.ends_with(".a") || library_path.ends_with(".xcframework"); - - if (!library_path.is_resource_file() && !library_path.is_absolute_path()) { - library_path = p_path.get_base_dir().path_join(library_path); - } - - if (p_extension.is_null()) { - p_extension.instantiate(); - } - -#ifdef TOOLS_ENABLED - p_extension->set_reloadable(config->get_value("configuration", "reloadable", false) && Engine::get_singleton()->is_extension_reloading_enabled()); - - p_extension->update_last_modified_time( - FileAccess::get_modified_time(p_path), - FileAccess::get_modified_time(library_path)); -#endif - - Vector<SharedObject> library_dependencies = GDExtension::find_extension_dependencies(p_path, config, [](const String &p_feature) { return OS::get_singleton()->has_feature(p_feature); }); - err = p_extension->open_library(is_static_library ? String() : library_path, entry_symbol, &library_dependencies); - if (err != OK) { - // Unreference the extension so that this loading can be considered a failure. - p_extension.unref(); - - // Errors already logged in open_library() - return err; - } - - // Handle icons if any are specified. - if (config->has_section("icons")) { - List<String> keys; - config->get_section_keys("icons", &keys); - for (const String &key : keys) { - String icon_path = config->get_value("icons", key); - if (icon_path.is_relative_path()) { - icon_path = p_path.get_base_dir().path_join(icon_path); - } + GDExtensionManager *extension_manager = GDExtensionManager::get_singleton(); - p_extension->class_icon_paths[key] = icon_path; - } + GDExtensionManager::LoadStatus status = extension_manager->load_extension(p_path); + if (status != GDExtensionManager::LOAD_STATUS_OK && status != GDExtensionManager::LOAD_STATUS_ALREADY_LOADED) { + // Errors already logged in load_extension(). + return FAILED; } + p_extension = extension_manager->get_extension(p_path); return OK; } @@ -1094,16 +839,7 @@ String GDExtensionResourceLoader::get_resource_type(const String &p_path) const #ifdef TOOLS_ENABLED bool GDExtension::has_library_changed() const { - // Check only that the last modified time is different (rather than checking - // that it's newer) since some OS's (namely Windows) will preserve the modified - // time by default when copying files. - if (FileAccess::get_modified_time(get_path()) != resource_last_modified_time) { - return true; - } - if (FileAccess::get_modified_time(library_path) != library_last_modified_time) { - return true; - } - return false; + return loader->has_library_changed(); } void GDExtension::prepare_reload() { diff --git a/core/extension/gdextension.h b/core/extension/gdextension.h index 9393e7399b..706bc7e189 100644 --- a/core/extension/gdextension.h +++ b/core/extension/gdextension.h @@ -31,13 +31,11 @@ #ifndef GDEXTENSION_H #define GDEXTENSION_H -#include <functional> - #include "core/extension/gdextension_interface.h" +#include "core/extension/gdextension_loader.h" #include "core/io/config_file.h" #include "core/io/resource_loader.h" #include "core/object/ref_counted.h" -#include "core/os/shared_object.h" class GDExtensionMethodBind; @@ -46,8 +44,8 @@ class GDExtension : public Resource { friend class GDExtensionManager; - void *library = nullptr; // pointer if valid, - String library_path; + Ref<GDExtensionLoader> loader; + bool reloadable = false; struct Extension { @@ -72,15 +70,18 @@ class GDExtension : public Resource { #ifndef DISABLE_DEPRECATED GDExtensionClassNotification notification_func = nullptr; GDExtensionClassFreePropertyList free_property_list_func = nullptr; + GDExtensionClassCreateInstance create_instance_func = nullptr; + GDExtensionClassGetRID get_rid_func = nullptr; #endif // DISABLE_DEPRECATED }; #ifndef DISABLE_DEPRECATED static void _register_extension_class(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_parent_class_name, const GDExtensionClassCreationInfo *p_extension_funcs); static void _register_extension_class2(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_parent_class_name, const GDExtensionClassCreationInfo2 *p_extension_funcs); -#endif // DISABLE_DEPRECATED static void _register_extension_class3(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_parent_class_name, const GDExtensionClassCreationInfo3 *p_extension_funcs); - static void _register_extension_class_internal(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_parent_class_name, const GDExtensionClassCreationInfo3 *p_extension_funcs, const ClassCreationDeprecatedInfo *p_deprecated_funcs = nullptr); +#endif // DISABLE_DEPRECATED + static void _register_extension_class4(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_parent_class_name, const GDExtensionClassCreationInfo4 *p_extension_funcs); + static void _register_extension_class_internal(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_parent_class_name, const GDExtensionClassCreationInfo4 *p_extension_funcs, const ClassCreationDeprecatedInfo *p_deprecated_funcs = nullptr); static void _register_extension_class_method(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, const GDExtensionClassMethodInfo *p_method_info); static void _register_extension_class_virtual_method(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, const GDExtensionClassVirtualMethodInfo *p_method_info); static void _register_extension_class_integer_constant(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_enum_name, GDExtensionConstStringNamePtr p_constant_name, GDExtensionInt p_constant_value, GDExtensionBool p_is_bitfield); @@ -96,8 +97,6 @@ class GDExtension : public Resource { int32_t level_initialized = -1; #ifdef TOOLS_ENABLED - uint64_t resource_last_modified_time = 0; - uint64_t library_last_modified_time = 0; bool is_reloading = false; Vector<GDExtensionMethodBind *> invalid_methods; Vector<ObjectID> instance_bindings; @@ -124,11 +123,12 @@ public: virtual bool editor_can_reload_from_file() override { return false; } // Reloading is handled in a special way. static String get_extension_list_config_file(); - static String find_extension_library(const String &p_path, Ref<ConfigFile> p_config, std::function<bool(String)> p_has_feature, PackedStringArray *r_tags = nullptr); - static Vector<SharedObject> find_extension_dependencies(const String &p_path, Ref<ConfigFile> p_config, std::function<bool(String)> p_has_feature); - Error open_library(const String &p_path, const String &p_entry_symbol, Vector<SharedObject> *p_dependencies = nullptr); + const Ref<GDExtensionLoader> get_loader() const { return loader; } + + Error open_library(const String &p_path, const Ref<GDExtensionLoader> &p_loader); void close_library(); + bool is_library_open() const; enum InitializationLevel { INITIALIZATION_LEVEL_CORE = GDEXTENSION_INITIALIZATION_CORE, @@ -146,17 +146,11 @@ protected: #endif public: - bool is_library_open() const; - #ifdef TOOLS_ENABLED bool is_reloadable() const { return reloadable; } void set_reloadable(bool p_reloadable) { reloadable = p_reloadable; } bool has_library_changed() const; - void update_last_modified_time(uint64_t p_resource_last_modified_time, uint64_t p_library_last_modified_time) { - resource_last_modified_time = p_resource_last_modified_time; - library_last_modified_time = p_library_last_modified_time; - } void track_instance_binding(Object *p_object); void untrack_instance_binding(Object *p_object); diff --git a/core/extension/gdextension_compat_hashes.cpp b/core/extension/gdextension_compat_hashes.cpp index ebbf795070..b07f5b1858 100644 --- a/core/extension/gdextension_compat_hashes.cpp +++ b/core/extension/gdextension_compat_hashes.cpp @@ -103,6 +103,14 @@ void GDExtensionCompatHashes::initialize() { mappings.insert("AcceptDialog", { { "add_button", 4158837846, 3328440682 }, }); + mappings.insert("AnimatedSprite2D", { + { "play", 2372066587, 3269405555 }, + { "play_backwards", 1421762485, 3323268493 }, + }); + mappings.insert("AnimatedSprite3D", { + { "play", 2372066587, 3269405555 }, + { "play_backwards", 1421762485, 3323268493 }, + }); mappings.insert("Animation", { { "add_track", 2393815928, 3843682357 }, { "track_insert_key", 1985425300, 808952278 }, @@ -146,6 +154,12 @@ void GDExtensionCompatHashes::initialize() { { "travel", 3683006648, 3823612587 }, { "start", 3683006648, 3823612587 }, }); + mappings.insert("AnimationPlayer", { + { "play", 3697947785, 3118260607 }, + { "play", 2221377757, 3118260607 }, + { "play_backwards", 3890664824, 2787282401 }, + { "play_with_capture", 3180464118, 1572969103 }, + }); mappings.insert("ArrayMesh", { { "add_surface_from_arrays", 172284304, 1796411378 }, }); @@ -247,13 +261,20 @@ void GDExtensionCompatHashes::initialize() { }); mappings.insert("DisplayServer", { { "global_menu_add_submenu_item", 3806306913, 2828985934 }, - { "global_menu_add_item", 3415468211, 3401266716 }, - { "global_menu_add_check_item", 3415468211, 3401266716 }, - { "global_menu_add_icon_item", 1700867534, 4245856523 }, - { "global_menu_add_icon_check_item", 1700867534, 4245856523 }, - { "global_menu_add_radio_check_item", 3415468211, 3401266716 }, - { "global_menu_add_icon_radio_check_item", 1700867534, 4245856523 }, - { "global_menu_add_multistate_item", 635750054, 3431222859 }, + { "global_menu_add_item", 3415468211, 3616842746 }, + { "global_menu_add_item", 3401266716, 3616842746 }, + { "global_menu_add_check_item", 3415468211, 3616842746 }, + { "global_menu_add_check_item", 3401266716, 3616842746 }, + { "global_menu_add_icon_item", 1700867534, 3867083847 }, + { "global_menu_add_icon_item", 4245856523, 3867083847 }, + { "global_menu_add_icon_check_item", 1700867534, 3867083847 }, + { "global_menu_add_icon_check_item", 4245856523, 3867083847 }, + { "global_menu_add_radio_check_item", 3415468211, 3616842746 }, + { "global_menu_add_radio_check_item", 3401266716, 3616842746 }, + { "global_menu_add_icon_radio_check_item", 1700867534, 3867083847 }, + { "global_menu_add_icon_radio_check_item", 4245856523, 3867083847 }, + { "global_menu_add_multistate_item", 635750054, 3297554655 }, + { "global_menu_add_multistate_item", 3431222859, 3297554655 }, { "global_menu_add_separator", 1041533178, 3214812433 }, { "tts_speak", 3741216677, 903992738 }, { "is_touchscreen_available", 4162880507, 3323674545 }, @@ -286,6 +307,12 @@ void GDExtensionCompatHashes::initialize() { { "virtual_keyboard_show", 860410478, 3042891259 }, #endif }); + mappings.insert("EditorExportPlatform", { + { "export_project_files", 425454869, 1063735070 }, + }); + mappings.insert("EditorProperty", { + { "emit_changed", 3069422438, 1822500399 }, + }); mappings.insert("ENetConnection", { { "create_host_bound", 866250949, 1515002313 }, { "connect_to_host", 385984708, 2171300490 }, @@ -453,18 +480,35 @@ void GDExtensionCompatHashes::initialize() { mappings.insert("MultiplayerAPI", { { "rpc", 1833408346, 2077486355 }, }); + mappings.insert("NativeMenu", { + { "add_item", 2553375659, 980552939 }, + { "add_check_item", 2553375659, 980552939 }, + { "add_icon_item", 2987595282, 1372188274 }, + { "add_icon_check_item", 2987595282, 1372188274 }, + { "add_radio_check_item", 2553375659, 980552939 }, + { "add_icon_radio_check_item", 2987595282, 1372188274 }, + { "add_multistate_item", 1558592568, 2674635658 }, + }); mappings.insert("NavigationMeshGenerator", { - { "parse_source_geometry_data", 3703028813, 685862123 }, - { "bake_from_source_geometry_data", 3669016597, 2469318639 }, + { "parse_source_geometry_data", 3703028813, 3172802542 }, + { "parse_source_geometry_data", 685862123, 3172802542 }, + { "bake_from_source_geometry_data", 3669016597, 1286748856 }, + { "bake_from_source_geometry_data", 2469318639, 1286748856 }, }); mappings.insert("NavigationServer2D", { { "map_get_path", 56240621, 3146466012 }, + { "parse_source_geometry_data", 1176164995, 1766905497 }, + { "bake_from_source_geometry_data", 2909414286, 2179660022 }, + { "bake_from_source_geometry_data_async", 2909414286, 2179660022 }, }); mappings.insert("NavigationServer3D", { { "map_get_path", 2121045993, 1187418690 }, - { "parse_source_geometry_data", 3703028813, 685862123 }, - { "bake_from_source_geometry_data", 3669016597, 2469318639 }, - { "bake_from_source_geometry_data_async", 3669016597, 2469318639 }, + { "parse_source_geometry_data", 3703028813, 3172802542 }, + { "parse_source_geometry_data", 685862123, 3172802542 }, + { "bake_from_source_geometry_data", 3669016597, 1286748856 }, + { "bake_from_source_geometry_data", 2469318639, 1286748856 }, + { "bake_from_source_geometry_data_async", 3669016597, 1286748856 }, + { "bake_from_source_geometry_data_async", 2469318639, 1286748856 }, }); mappings.insert("Node", { { "add_child", 3070154285, 3863233950 }, @@ -631,6 +675,11 @@ void GDExtensionCompatHashes::initialize() { mappings.insert("ProjectSettings", { { "load_resource_pack", 3001721055, 708980503 }, }); + mappings.insert("RDShaderFile", { + { "bake_from_source_geometry_data_async", 2469318639, 1286748856 }, + { "set_bytecode", 1558064255, 1526857008 }, + { "get_spirv", 3340165340, 2689310080 }, + }); mappings.insert("RegEx", { { "search", 4087180739, 3365977994 }, { "search_all", 3354100289, 849021363 }, diff --git a/core/extension/gdextension_interface.cpp b/core/extension/gdextension_interface.cpp index 85f83eecfd..ddf90f6130 100644 --- a/core/extension/gdextension_interface.cpp +++ b/core/extension/gdextension_interface.cpp @@ -1199,6 +1199,15 @@ static GDExtensionVariantPtr gdextension_dictionary_operator_index_const(GDExten return (GDExtensionVariantPtr)&self->operator[](*(const Variant *)p_key); } +void gdextension_dictionary_set_typed(GDExtensionTypePtr p_self, GDExtensionVariantType p_key_type, GDExtensionConstStringNamePtr p_key_class_name, GDExtensionConstVariantPtr p_key_script, GDExtensionVariantType p_value_type, GDExtensionConstStringNamePtr p_value_class_name, GDExtensionConstVariantPtr p_value_script) { + Dictionary *self = reinterpret_cast<Dictionary *>(p_self); + const StringName *key_class_name = reinterpret_cast<const StringName *>(p_key_class_name); + const Variant *key_script = reinterpret_cast<const Variant *>(p_key_script); + const StringName *value_class_name = reinterpret_cast<const StringName *>(p_value_class_name); + const Variant *value_script = reinterpret_cast<const Variant *>(p_value_script); + self->set_typed((uint32_t)p_key_type, *key_class_name, *key_script, (uint32_t)p_value_type, *value_class_name, *value_script); +} + /* OBJECT API */ static void gdextension_object_method_bind_call(GDExtensionMethodBindPtr p_method_bind, GDExtensionObjectPtr p_instance, const GDExtensionConstVariantPtr *p_args, GDExtensionInt p_arg_count, GDExtensionUninitializedVariantPtr r_return, GDExtensionCallError *r_error) { @@ -1299,7 +1308,7 @@ static void gdextension_object_call_script_method(GDExtensionObjectPtr p_object, const StringName method = *reinterpret_cast<const StringName *>(p_method); const Variant **args = (const Variant **)p_args; - Callable::CallError error; + Callable::CallError error; // TODO: Check `error`? memnew_placement(r_return, Variant); *(Variant *)r_return = o->callp(method, args, p_argument_count, error); @@ -1515,10 +1524,17 @@ static GDExtensionMethodBindPtr gdextension_classdb_get_method_bind(GDExtensionC return (GDExtensionMethodBindPtr)mb; } +#ifndef DISABLE_DEPRECATED static GDExtensionObjectPtr gdextension_classdb_construct_object(GDExtensionConstStringNamePtr p_classname) { const StringName classname = *reinterpret_cast<const StringName *>(p_classname); return (GDExtensionObjectPtr)ClassDB::instantiate_no_placeholders(classname); } +#endif + +static GDExtensionObjectPtr gdextension_classdb_construct_object2(GDExtensionConstStringNamePtr p_classname) { + const StringName classname = *reinterpret_cast<const StringName *>(p_classname); + return (GDExtensionObjectPtr)ClassDB::instantiate_without_postinitialization(classname); +} static void *gdextension_classdb_get_class_tag(GDExtensionConstStringNamePtr p_classname) { const StringName classname = *reinterpret_cast<const StringName *>(p_classname); @@ -1672,6 +1688,7 @@ void gdextension_setup_interface() { REGISTER_INTERFACE_FUNC(array_set_typed); REGISTER_INTERFACE_FUNC(dictionary_operator_index); REGISTER_INTERFACE_FUNC(dictionary_operator_index_const); + REGISTER_INTERFACE_FUNC(dictionary_set_typed); REGISTER_INTERFACE_FUNC(object_method_bind_call); REGISTER_INTERFACE_FUNC(object_method_bind_ptrcall); REGISTER_INTERFACE_FUNC(object_destroy); @@ -1701,7 +1718,10 @@ void gdextension_setup_interface() { #endif // DISABLE_DEPRECATED REGISTER_INTERFACE_FUNC(callable_custom_create2); REGISTER_INTERFACE_FUNC(callable_custom_get_userdata); +#ifndef DISABLE_DEPRECATED REGISTER_INTERFACE_FUNC(classdb_construct_object); +#endif // DISABLE_DEPRECATED + REGISTER_INTERFACE_FUNC(classdb_construct_object2); REGISTER_INTERFACE_FUNC(classdb_get_method_bind); REGISTER_INTERFACE_FUNC(classdb_get_class_tag); REGISTER_INTERFACE_FUNC(editor_add_plugin); diff --git a/core/extension/gdextension_interface.h b/core/extension/gdextension_interface.h index d6c1df9c00..9e3ce25698 100644 --- a/core/extension/gdextension_interface.h +++ b/core/extension/gdextension_interface.h @@ -268,6 +268,7 @@ typedef void (*GDExtensionClassReference)(GDExtensionClassInstancePtr p_instance typedef void (*GDExtensionClassUnreference)(GDExtensionClassInstancePtr p_instance); typedef void (*GDExtensionClassCallVirtual)(GDExtensionClassInstancePtr p_instance, const GDExtensionConstTypePtr *p_args, GDExtensionTypePtr r_ret); typedef GDExtensionObjectPtr (*GDExtensionClassCreateInstance)(void *p_class_userdata); +typedef GDExtensionObjectPtr (*GDExtensionClassCreateInstance2)(void *p_class_userdata, GDExtensionBool p_notify_postinitialize); typedef void (*GDExtensionClassFreeInstance)(void *p_class_userdata, GDExtensionClassInstancePtr p_instance); typedef GDExtensionClassInstancePtr (*GDExtensionClassRecreateInstance)(void *p_class_userdata, GDExtensionObjectPtr p_object); typedef GDExtensionClassCallVirtual (*GDExtensionClassGetVirtual)(void *p_class_userdata, GDExtensionConstStringNamePtr p_name); @@ -292,7 +293,7 @@ typedef struct { GDExtensionClassGetVirtual get_virtual_func; // Queries a virtual function by name and returns a callback to invoke the requested virtual function. GDExtensionClassGetRID get_rid_func; void *class_userdata; // Per-class user data, later accessible in instance bindings. -} GDExtensionClassCreationInfo; // Deprecated. Use GDExtensionClassCreationInfo3 instead. +} GDExtensionClassCreationInfo; // Deprecated. Use GDExtensionClassCreationInfo4 instead. typedef struct { GDExtensionBool is_virtual; @@ -325,7 +326,7 @@ typedef struct { GDExtensionClassCallVirtualWithData call_virtual_with_data_func; GDExtensionClassGetRID get_rid_func; void *class_userdata; // Per-class user data, later accessible in instance bindings. -} GDExtensionClassCreationInfo2; // Deprecated. Use GDExtensionClassCreationInfo3 instead. +} GDExtensionClassCreationInfo2; // Deprecated. Use GDExtensionClassCreationInfo4 instead. typedef struct { GDExtensionBool is_virtual; @@ -359,7 +360,40 @@ typedef struct { GDExtensionClassCallVirtualWithData call_virtual_with_data_func; GDExtensionClassGetRID get_rid_func; void *class_userdata; // Per-class user data, later accessible in instance bindings. -} GDExtensionClassCreationInfo3; +} GDExtensionClassCreationInfo3; // Deprecated. Use GDExtensionClassCreationInfo4 instead. + +typedef struct { + GDExtensionBool is_virtual; + GDExtensionBool is_abstract; + GDExtensionBool is_exposed; + GDExtensionBool is_runtime; + GDExtensionClassSet set_func; + GDExtensionClassGet get_func; + GDExtensionClassGetPropertyList get_property_list_func; + GDExtensionClassFreePropertyList2 free_property_list_func; + GDExtensionClassPropertyCanRevert property_can_revert_func; + GDExtensionClassPropertyGetRevert property_get_revert_func; + GDExtensionClassValidateProperty validate_property_func; + GDExtensionClassNotification2 notification_func; + GDExtensionClassToString to_string_func; + GDExtensionClassReference reference_func; + GDExtensionClassUnreference unreference_func; + GDExtensionClassCreateInstance2 create_instance_func; // (Default) constructor; mandatory. If the class is not instantiable, consider making it virtual or abstract. + GDExtensionClassFreeInstance free_instance_func; // Destructor; mandatory. + GDExtensionClassRecreateInstance recreate_instance_func; + // Queries a virtual function by name and returns a callback to invoke the requested virtual function. + GDExtensionClassGetVirtual get_virtual_func; + // Paired with `call_virtual_with_data_func`, this is an alternative to `get_virtual_func` for extensions that + // need or benefit from extra data when calling virtual functions. + // Returns user data that will be passed to `call_virtual_with_data_func`. + // Returning `NULL` from this function signals to Godot that the virtual function is not overridden. + // Data returned from this function should be managed by the extension and must be valid until the extension is deinitialized. + // You should supply either `get_virtual_func`, or `get_virtual_call_data_func` with `call_virtual_with_data_func`. + GDExtensionClassGetVirtualCallData get_virtual_call_data_func; + // Used to call virtual functions when `get_virtual_call_data_func` is not null. + GDExtensionClassCallVirtualWithData call_virtual_with_data_func; + void *class_userdata; // Per-class user data, later accessible in instance bindings. +} GDExtensionClassCreationInfo4; typedef void *GDExtensionClassLibraryPtr; @@ -386,7 +420,9 @@ typedef enum { GDEXTENSION_METHOD_ARGUMENT_METADATA_INT_IS_UINT32, GDEXTENSION_METHOD_ARGUMENT_METADATA_INT_IS_UINT64, GDEXTENSION_METHOD_ARGUMENT_METADATA_REAL_IS_FLOAT, - GDEXTENSION_METHOD_ARGUMENT_METADATA_REAL_IS_DOUBLE + GDEXTENSION_METHOD_ARGUMENT_METADATA_REAL_IS_DOUBLE, + GDEXTENSION_METHOD_ARGUMENT_METADATA_INT_IS_CHAR16, + GDEXTENSION_METHOD_ARGUMENT_METADATA_INT_IS_CHAR32, } GDExtensionClassMethodArgumentMetadata; typedef void (*GDExtensionClassMethodCall)(void *method_userdata, GDExtensionClassInstancePtr p_instance, const GDExtensionConstVariantPtr *p_args, GDExtensionInt p_argument_count, GDExtensionVariantPtr r_return, GDExtensionCallError *r_error); @@ -2337,6 +2373,22 @@ typedef GDExtensionVariantPtr (*GDExtensionInterfaceDictionaryOperatorIndex)(GDE */ typedef GDExtensionVariantPtr (*GDExtensionInterfaceDictionaryOperatorIndexConst)(GDExtensionConstTypePtr p_self, GDExtensionConstVariantPtr p_key); +/** + * @name dictionary_set_typed + * @since 4.4 + * + * Makes a Dictionary into a typed Dictionary. + * + * @param p_self A pointer to the Dictionary. + * @param p_key_type The type of Variant the Dictionary key will store. + * @param p_key_class_name A pointer to a StringName with the name of the object (if p_key_type is GDEXTENSION_VARIANT_TYPE_OBJECT). + * @param p_key_script A pointer to a Script object (if p_key_type is GDEXTENSION_VARIANT_TYPE_OBJECT and the base class is extended by a script). + * @param p_value_type The type of Variant the Dictionary value will store. + * @param p_value_class_name A pointer to a StringName with the name of the object (if p_value_type is GDEXTENSION_VARIANT_TYPE_OBJECT). + * @param p_value_script A pointer to a Script object (if p_value_type is GDEXTENSION_VARIANT_TYPE_OBJECT and the base class is extended by a script). + */ +typedef void (*GDExtensionInterfaceDictionarySetTyped)(GDExtensionTypePtr p_self, GDExtensionVariantType p_key_type, GDExtensionConstStringNamePtr p_key_class_name, GDExtensionConstVariantPtr p_key_script, GDExtensionVariantType p_value_type, GDExtensionConstStringNamePtr p_value_class_name, GDExtensionConstVariantPtr p_value_script); + /* INTERFACE: Object */ /** @@ -2680,6 +2732,7 @@ typedef void *(*GDExtensionInterfaceCallableCustomGetUserData)(GDExtensionConstT /** * @name classdb_construct_object * @since 4.1 + * @deprecated in Godot 4.4. Use `classdb_construct_object2` instead. * * Constructs an Object of the requested class. * @@ -2692,6 +2745,22 @@ typedef void *(*GDExtensionInterfaceCallableCustomGetUserData)(GDExtensionConstT typedef GDExtensionObjectPtr (*GDExtensionInterfaceClassdbConstructObject)(GDExtensionConstStringNamePtr p_classname); /** + * @name classdb_construct_object2 + * @since 4.4 + * + * Constructs an Object of the requested class. + * + * The passed class must be a built-in godot class, or an already-registered extension class. In both cases, object_set_instance() should be called to fully initialize the object. + * + * "NOTIFICATION_POSTINITIALIZE" must be sent after construction. + * + * @param p_classname A pointer to a StringName with the class name. + * + * @return A pointer to the newly created Object. + */ +typedef GDExtensionObjectPtr (*GDExtensionInterfaceClassdbConstructObject2)(GDExtensionConstStringNamePtr p_classname); + +/** * @name classdb_get_method_bind * @since 4.1 * @@ -2722,7 +2791,7 @@ typedef void *(*GDExtensionInterfaceClassdbGetClassTag)(GDExtensionConstStringNa /** * @name classdb_register_extension_class * @since 4.1 - * @deprecated in Godot 4.2. Use `classdb_register_extension_class3` instead. + * @deprecated in Godot 4.2. Use `classdb_register_extension_class4` instead. * * Registers an extension class in the ClassDB. * @@ -2738,7 +2807,7 @@ typedef void (*GDExtensionInterfaceClassdbRegisterExtensionClass)(GDExtensionCla /** * @name classdb_register_extension_class2 * @since 4.2 - * @deprecated in Godot 4.3. Use `classdb_register_extension_class3` instead. + * @deprecated in Godot 4.3. Use `classdb_register_extension_class4` instead. * * Registers an extension class in the ClassDB. * @@ -2754,6 +2823,7 @@ typedef void (*GDExtensionInterfaceClassdbRegisterExtensionClass2)(GDExtensionCl /** * @name classdb_register_extension_class3 * @since 4.3 + * @deprecated in Godot 4.4. Use `classdb_register_extension_class4` instead. * * Registers an extension class in the ClassDB. * @@ -2767,6 +2837,21 @@ typedef void (*GDExtensionInterfaceClassdbRegisterExtensionClass2)(GDExtensionCl typedef void (*GDExtensionInterfaceClassdbRegisterExtensionClass3)(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_parent_class_name, const GDExtensionClassCreationInfo3 *p_extension_funcs); /** + * @name classdb_register_extension_class4 + * @since 4.4 + * + * Registers an extension class in the ClassDB. + * + * Provided struct can be safely freed once the function returns. + * + * @param p_library A pointer the library received by the GDExtension's entry point function. + * @param p_class_name A pointer to a StringName with the class name. + * @param p_parent_class_name A pointer to a StringName with the parent class name. + * @param p_extension_funcs A pointer to a GDExtensionClassCreationInfo2 struct. + */ +typedef void (*GDExtensionInterfaceClassdbRegisterExtensionClass4)(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_parent_class_name, const GDExtensionClassCreationInfo4 *p_extension_funcs); + +/** * @name classdb_register_extension_class_method * @since 4.1 * @@ -2800,12 +2885,16 @@ typedef void (*GDExtensionInterfaceClassdbRegisterExtensionClassVirtualMethod)(G * * Registers an integer constant on an extension class in the ClassDB. * + * Note about registering bitfield values (if p_is_bitfield is true): even though p_constant_value is signed, language bindings are + * advised to treat bitfields as uint64_t, since this is generally clearer and can prevent mistakes like using -1 for setting all bits. + * Language APIs should thus provide an abstraction that registers bitfields (uint64_t) separately from regular constants (int64_t). + * * @param p_library A pointer the library received by the GDExtension's entry point function. * @param p_class_name A pointer to a StringName with the class name. * @param p_enum_name A pointer to a StringName with the enum name. * @param p_constant_name A pointer to a StringName with the constant name. * @param p_constant_value The constant value. - * @param p_is_bitfield Whether or not this is a bit field. + * @param p_is_bitfield Whether or not this constant is part of a bitfield. */ typedef void (*GDExtensionInterfaceClassdbRegisterExtensionClassIntegerConstant)(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_enum_name, GDExtensionConstStringNamePtr p_constant_name, GDExtensionInt p_constant_value, GDExtensionBool p_is_bitfield); diff --git a/core/extension/gdextension_library_loader.cpp b/core/extension/gdextension_library_loader.cpp new file mode 100644 index 0000000000..5ba4933c35 --- /dev/null +++ b/core/extension/gdextension_library_loader.cpp @@ -0,0 +1,390 @@ +/**************************************************************************/ +/* gdextension_library_loader.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 "gdextension_library_loader.h" + +#include "core/config/project_settings.h" +#include "core/io/dir_access.h" +#include "core/version.h" +#include "gdextension.h" + +Vector<SharedObject> GDExtensionLibraryLoader::find_extension_dependencies(const String &p_path, Ref<ConfigFile> p_config, std::function<bool(String)> p_has_feature) { + Vector<SharedObject> dependencies_shared_objects; + if (p_config->has_section("dependencies")) { + List<String> config_dependencies; + p_config->get_section_keys("dependencies", &config_dependencies); + + for (const String &dependency : config_dependencies) { + Vector<String> dependency_tags = dependency.split("."); + bool all_tags_met = true; + for (int i = 0; i < dependency_tags.size(); i++) { + String tag = dependency_tags[i].strip_edges(); + if (!p_has_feature(tag)) { + all_tags_met = false; + break; + } + } + + if (all_tags_met) { + Dictionary dependency_value = p_config->get_value("dependencies", dependency); + for (const Variant *key = dependency_value.next(nullptr); key; key = dependency_value.next(key)) { + String dependency_path = *key; + String target_path = dependency_value[*key]; + if (dependency_path.is_relative_path()) { + dependency_path = p_path.get_base_dir().path_join(dependency_path); + } + dependencies_shared_objects.push_back(SharedObject(dependency_path, dependency_tags, target_path)); + } + break; + } + } + } + + return dependencies_shared_objects; +} + +String GDExtensionLibraryLoader::find_extension_library(const String &p_path, Ref<ConfigFile> p_config, std::function<bool(String)> p_has_feature, PackedStringArray *r_tags) { + // First, check the explicit libraries. + if (p_config->has_section("libraries")) { + List<String> libraries; + p_config->get_section_keys("libraries", &libraries); + + // Iterate the libraries, finding the best matching tags. + String best_library_path; + Vector<String> best_library_tags; + for (const String &E : libraries) { + Vector<String> tags = E.split("."); + bool all_tags_met = true; + for (int i = 0; i < tags.size(); i++) { + String tag = tags[i].strip_edges(); + if (!p_has_feature(tag)) { + all_tags_met = false; + break; + } + } + + if (all_tags_met && tags.size() > best_library_tags.size()) { + best_library_path = p_config->get_value("libraries", E); + best_library_tags = tags; + } + } + + if (!best_library_path.is_empty()) { + if (best_library_path.is_relative_path()) { + best_library_path = p_path.get_base_dir().path_join(best_library_path); + } + if (r_tags != nullptr) { + r_tags->append_array(best_library_tags); + } + return best_library_path; + } + } + + // Second, try to autodetect. + String autodetect_library_prefix; + if (p_config->has_section_key("configuration", "autodetect_library_prefix")) { + autodetect_library_prefix = p_config->get_value("configuration", "autodetect_library_prefix"); + } + if (!autodetect_library_prefix.is_empty()) { + String autodetect_path = autodetect_library_prefix; + if (autodetect_path.is_relative_path()) { + autodetect_path = p_path.get_base_dir().path_join(autodetect_path); + } + + // Find the folder and file parts of the prefix. + String folder; + String file_prefix; + if (DirAccess::dir_exists_absolute(autodetect_path)) { + folder = autodetect_path; + } else if (DirAccess::dir_exists_absolute(autodetect_path.get_base_dir())) { + folder = autodetect_path.get_base_dir(); + file_prefix = autodetect_path.get_file(); + } else { + ERR_FAIL_V_MSG(String(), vformat("Error in extension: %s. Could not find folder for automatic detection of libraries files. autodetect_library_prefix=\"%s\"", p_path, autodetect_library_prefix)); + } + + // Open the folder. + Ref<DirAccess> dir = DirAccess::open(folder); + ERR_FAIL_COND_V_MSG(dir.is_null(), String(), vformat("Error in extension: %s. Could not open folder for automatic detection of libraries files. autodetect_library_prefix=\"%s\"", p_path, autodetect_library_prefix)); + + // Iterate the files and check the prefixes, finding the best matching file. + String best_file; + Vector<String> best_file_tags; + dir->list_dir_begin(); + String file_name = dir->_get_next(); + while (file_name != "") { + if (!dir->current_is_dir() && file_name.begins_with(file_prefix)) { + // Check if the files matches all requested feature tags. + String tags_str = file_name.trim_prefix(file_prefix); + tags_str = tags_str.trim_suffix(tags_str.get_extension()); + + Vector<String> tags = tags_str.split(".", false); + bool all_tags_met = true; + for (int i = 0; i < tags.size(); i++) { + String tag = tags[i].strip_edges(); + if (!p_has_feature(tag)) { + all_tags_met = false; + break; + } + } + + // If all tags are found in the feature list, and we found more tags than before, use this file. + if (all_tags_met && tags.size() > best_file_tags.size()) { + best_file_tags = tags; + best_file = file_name; + } + } + file_name = dir->_get_next(); + } + + if (!best_file.is_empty()) { + String library_path = folder.path_join(best_file); + if (r_tags != nullptr) { + r_tags->append_array(best_file_tags); + } + return library_path; + } + } + return String(); +} + +Error GDExtensionLibraryLoader::open_library(const String &p_path) { + Error err = parse_gdextension_file(p_path); + if (err != OK) { + return err; + } + + String abs_path = ProjectSettings::get_singleton()->globalize_path(library_path); + + Vector<String> abs_dependencies_paths; + if (!library_dependencies.is_empty()) { + for (const SharedObject &dependency : library_dependencies) { + abs_dependencies_paths.push_back(ProjectSettings::get_singleton()->globalize_path(dependency.path)); + } + } + + OS::GDExtensionData data = { + true, // also_set_library_path + &library_path, // r_resolved_path + Engine::get_singleton()->is_editor_hint(), // generate_temp_files + &abs_dependencies_paths, // library_dependencies + }; + + err = OS::get_singleton()->open_dynamic_library(is_static_library ? String() : abs_path, library, &data); + if (err != OK) { + return err; + } + + return OK; +} + +Error GDExtensionLibraryLoader::initialize(GDExtensionInterfaceGetProcAddress p_get_proc_address, const Ref<GDExtension> &p_extension, GDExtensionInitialization *r_initialization) { +#ifdef TOOLS_ENABLED + p_extension->set_reloadable(is_reloadable && Engine::get_singleton()->is_extension_reloading_enabled()); +#endif + + for (const KeyValue<String, String> &icon : class_icon_paths) { + p_extension->class_icon_paths[icon.key] = icon.value; + } + + void *entry_funcptr = nullptr; + + Error err = OS::get_singleton()->get_dynamic_library_symbol_handle(library, entry_symbol, entry_funcptr, false); + + if (err != OK) { + ERR_PRINT("GDExtension entry point '" + entry_symbol + "' not found in library " + library_path); + return err; + } + + GDExtensionInitializationFunction initialization_function = (GDExtensionInitializationFunction)entry_funcptr; + + GDExtensionBool ret = initialization_function(p_get_proc_address, p_extension.ptr(), r_initialization); + + if (ret) { + return OK; + } else { + ERR_PRINT("GDExtension initialization function '" + entry_symbol + "' returned an error."); + return FAILED; + } +} + +void GDExtensionLibraryLoader::close_library() { + OS::get_singleton()->close_dynamic_library(library); + library = nullptr; +} + +bool GDExtensionLibraryLoader::is_library_open() const { + return library != nullptr; +} + +bool GDExtensionLibraryLoader::has_library_changed() const { +#ifdef TOOLS_ENABLED + // Check only that the last modified time is different (rather than checking + // that it's newer) since some OS's (namely Windows) will preserve the modified + // time by default when copying files. + if (FileAccess::get_modified_time(resource_path) != resource_last_modified_time) { + return true; + } + if (FileAccess::get_modified_time(library_path) != library_last_modified_time) { + return true; + } +#endif + return false; +} + +Error GDExtensionLibraryLoader::parse_gdextension_file(const String &p_path) { + resource_path = p_path; + + Ref<ConfigFile> config; + config.instantiate(); + + Error err = config->load(p_path); + + if (err != OK) { + ERR_PRINT("Error loading GDExtension configuration file: " + p_path); + return err; + } + + if (!config->has_section_key("configuration", "entry_symbol")) { + ERR_PRINT("GDExtension configuration file must contain a \"configuration/entry_symbol\" key: " + p_path); + return ERR_INVALID_DATA; + } + + entry_symbol = config->get_value("configuration", "entry_symbol"); + + uint32_t compatibility_minimum[3] = { 0, 0, 0 }; + if (config->has_section_key("configuration", "compatibility_minimum")) { + String compat_string = config->get_value("configuration", "compatibility_minimum"); + Vector<int> parts = compat_string.split_ints("."); + for (int i = 0; i < parts.size(); i++) { + if (i >= 3) { + break; + } + if (parts[i] >= 0) { + compatibility_minimum[i] = parts[i]; + } + } + } else { + ERR_PRINT("GDExtension configuration file must contain a \"configuration/compatibility_minimum\" key: " + p_path); + return ERR_INVALID_DATA; + } + + if (compatibility_minimum[0] < 4 || (compatibility_minimum[0] == 4 && compatibility_minimum[1] == 0)) { + ERR_PRINT(vformat("GDExtension's compatibility_minimum (%d.%d.%d) must be at least 4.1.0: %s", compatibility_minimum[0], compatibility_minimum[1], compatibility_minimum[2], p_path)); + return ERR_INVALID_DATA; + } + + bool compatible = true; + // Check version lexicographically. + if (VERSION_MAJOR != compatibility_minimum[0]) { + compatible = VERSION_MAJOR > compatibility_minimum[0]; + } else if (VERSION_MINOR != compatibility_minimum[1]) { + compatible = VERSION_MINOR > compatibility_minimum[1]; + } else { + compatible = VERSION_PATCH >= compatibility_minimum[2]; + } + if (!compatible) { + ERR_PRINT(vformat("GDExtension only compatible with Godot version %d.%d.%d or later: %s", compatibility_minimum[0], compatibility_minimum[1], compatibility_minimum[2], p_path)); + return ERR_INVALID_DATA; + } + + // Optionally check maximum compatibility. + if (config->has_section_key("configuration", "compatibility_maximum")) { + uint32_t compatibility_maximum[3] = { 0, 0, 0 }; + String compat_string = config->get_value("configuration", "compatibility_maximum"); + Vector<int> parts = compat_string.split_ints("."); + for (int i = 0; i < 3; i++) { + if (i < parts.size() && parts[i] >= 0) { + compatibility_maximum[i] = parts[i]; + } else { + // If a version part is missing, set the maximum to an arbitrary high value. + compatibility_maximum[i] = 9999; + } + } + + compatible = true; + if (VERSION_MAJOR != compatibility_maximum[0]) { + compatible = VERSION_MAJOR < compatibility_maximum[0]; + } else if (VERSION_MINOR != compatibility_maximum[1]) { + compatible = VERSION_MINOR < compatibility_maximum[1]; + } +#if VERSION_PATCH + // #if check to avoid -Wtype-limits warning when 0. + else { + compatible = VERSION_PATCH <= compatibility_maximum[2]; + } +#endif + + if (!compatible) { + ERR_PRINT(vformat("GDExtension only compatible with Godot version %s or earlier: %s", compat_string, p_path)); + return ERR_INVALID_DATA; + } + } + + library_path = find_extension_library(p_path, config, [](const String &p_feature) { return OS::get_singleton()->has_feature(p_feature); }); + + if (library_path.is_empty()) { + const String os_arch = OS::get_singleton()->get_name().to_lower() + "." + Engine::get_singleton()->get_architecture_name(); + ERR_PRINT(vformat("No GDExtension library found for current OS and architecture (%s) in configuration file: %s", os_arch, p_path)); + return ERR_FILE_NOT_FOUND; + } + + is_static_library = library_path.ends_with(".a") || library_path.ends_with(".xcframework"); + + if (!library_path.is_resource_file() && !library_path.is_absolute_path()) { + library_path = p_path.get_base_dir().path_join(library_path); + } + +#ifdef TOOLS_ENABLED + is_reloadable = config->get_value("configuration", "reloadable", false); + + update_last_modified_time( + FileAccess::get_modified_time(resource_path), + FileAccess::get_modified_time(library_path)); +#endif + + library_dependencies = find_extension_dependencies(p_path, config, [](const String &p_feature) { return OS::get_singleton()->has_feature(p_feature); }); + + // Handle icons if any are specified. + if (config->has_section("icons")) { + List<String> keys; + config->get_section_keys("icons", &keys); + for (const String &key : keys) { + String icon_path = config->get_value("icons", key); + if (icon_path.is_relative_path()) { + icon_path = p_path.get_base_dir().path_join(icon_path); + } + + class_icon_paths[key] = icon_path; + } + } + + return OK; +} diff --git a/core/extension/gdextension_library_loader.h b/core/extension/gdextension_library_loader.h new file mode 100644 index 0000000000..f4372a75d4 --- /dev/null +++ b/core/extension/gdextension_library_loader.h @@ -0,0 +1,84 @@ +/**************************************************************************/ +/* gdextension_library_loader.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 GDEXTENSION_LIBRARY_LOADER_H +#define GDEXTENSION_LIBRARY_LOADER_H + +#include <functional> + +#include "core/extension/gdextension_loader.h" +#include "core/io/config_file.h" +#include "core/os/shared_object.h" + +class GDExtensionLibraryLoader : public GDExtensionLoader { + friend class GDExtensionManager; + friend class GDExtension; + +private: + String resource_path; + + void *library = nullptr; // pointer if valid. + String library_path; + String entry_symbol; + + bool is_static_library = false; + +#ifdef TOOLS_ENABLED + bool is_reloadable = false; +#endif + + Vector<SharedObject> library_dependencies; + + HashMap<String, String> class_icon_paths; + +#ifdef TOOLS_ENABLED + uint64_t resource_last_modified_time = 0; + uint64_t library_last_modified_time = 0; + + void update_last_modified_time(uint64_t p_resource_last_modified_time, uint64_t p_library_last_modified_time) { + resource_last_modified_time = p_resource_last_modified_time; + library_last_modified_time = p_library_last_modified_time; + } +#endif + +public: + static String find_extension_library(const String &p_path, Ref<ConfigFile> p_config, std::function<bool(String)> p_has_feature, PackedStringArray *r_tags = nullptr); + static Vector<SharedObject> find_extension_dependencies(const String &p_path, Ref<ConfigFile> p_config, std::function<bool(String)> p_has_feature); + + virtual Error open_library(const String &p_path) override; + virtual Error initialize(GDExtensionInterfaceGetProcAddress p_get_proc_address, const Ref<GDExtension> &p_extension, GDExtensionInitialization *r_initialization) override; + virtual void close_library() override; + virtual bool is_library_open() const override; + virtual bool has_library_changed() const override; + + Error parse_gdextension_file(const String &p_path); +}; + +#endif // GDEXTENSION_LIBRARY_LOADER_H diff --git a/core/object/object.compat.inc b/core/extension/gdextension_loader.h index bf1e99fc9b..7d779858b7 100644 --- a/core/object/object.compat.inc +++ b/core/extension/gdextension_loader.h @@ -1,5 +1,5 @@ /**************************************************************************/ -/* object.compat.inc */ +/* gdextension_loader.h */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,13 +28,20 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ -#ifndef DISABLE_DEPRECATED +#ifndef GDEXTENSION_LOADER_H +#define GDEXTENSION_LOADER_H -#include "core/object/class_db.h" +#include "core/object/ref_counted.h" -void Object::_bind_compatibility_methods() { - ClassDB::bind_compatibility_method(D_METHOD("tr", "message", "context"), &Object::tr, DEFVAL("")); - ClassDB::bind_compatibility_method(D_METHOD("tr_n", "message", "plural_message", "n", "context"), &Object::tr_n, DEFVAL("")); -} +class GDExtension; -#endif +class GDExtensionLoader : public RefCounted { +public: + virtual Error open_library(const String &p_path) = 0; + virtual Error initialize(GDExtensionInterfaceGetProcAddress p_get_proc_address, const Ref<GDExtension> &p_extension, GDExtensionInitialization *r_initialization) = 0; + virtual void close_library() = 0; + virtual bool is_library_open() const = 0; + virtual bool has_library_changed() const = 0; +}; + +#endif // GDEXTENSION_LOADER_H diff --git a/core/extension/gdextension_manager.cpp b/core/extension/gdextension_manager.cpp index 1ee9de0776..01efe0d96e 100644 --- a/core/extension/gdextension_manager.cpp +++ b/core/extension/gdextension_manager.cpp @@ -31,14 +31,19 @@ #include "gdextension_manager.h" #include "core/extension/gdextension_compat_hashes.h" +#include "core/extension/gdextension_library_loader.h" +#include "core/io/dir_access.h" #include "core/io/file_access.h" #include "core/object/script_language.h" -GDExtensionManager::LoadStatus GDExtensionManager::_load_extension_internal(const Ref<GDExtension> &p_extension) { +GDExtensionManager::LoadStatus GDExtensionManager::_load_extension_internal(const Ref<GDExtension> &p_extension, bool p_first_load) { if (level >= 0) { // Already initialized up to some level. - int32_t minimum_level = p_extension->get_minimum_library_initialization_level(); - if (minimum_level < MIN(level, GDExtension::INITIALIZATION_LEVEL_SCENE)) { - return LOAD_STATUS_NEEDS_RESTART; + int32_t minimum_level = 0; + if (!p_first_load) { + minimum_level = p_extension->get_minimum_library_initialization_level(); + if (minimum_level < MIN(level, GDExtension::INITIALIZATION_LEVEL_SCENE)) { + return LOAD_STATUS_NEEDS_RESTART; + } } // Initialize up to current level. for (int32_t i = minimum_level; i <= level; i++) { @@ -50,10 +55,20 @@ GDExtensionManager::LoadStatus GDExtensionManager::_load_extension_internal(cons gdextension_class_icon_paths[kv.key] = kv.value; } +#ifdef TOOLS_ENABLED + // Signals that a new extension is loaded so GDScript can register new class names. + emit_signal("extension_loaded", p_extension); +#endif + return LOAD_STATUS_OK; } GDExtensionManager::LoadStatus GDExtensionManager::_unload_extension_internal(const Ref<GDExtension> &p_extension) { +#ifdef TOOLS_ENABLED + // Signals that a new extension is unloading so GDScript can unregister class names. + emit_signal("extension_unloading", p_extension); +#endif + if (level >= 0) { // Already initialized up to some level. // Deinitialize down from current level. for (int32_t i = level; i >= GDExtension::INITIALIZATION_LEVEL_CORE; i--) { @@ -69,19 +84,31 @@ GDExtensionManager::LoadStatus GDExtensionManager::_unload_extension_internal(co } GDExtensionManager::LoadStatus GDExtensionManager::load_extension(const String &p_path) { + Ref<GDExtensionLibraryLoader> loader; + loader.instantiate(); + return GDExtensionManager::get_singleton()->load_extension_with_loader(p_path, loader); +} + +GDExtensionManager::LoadStatus GDExtensionManager::load_extension_with_loader(const String &p_path, const Ref<GDExtensionLoader> &p_loader) { + DEV_ASSERT(p_loader.is_valid()); + if (gdextension_map.has(p_path)) { return LOAD_STATUS_ALREADY_LOADED; } - Ref<GDExtension> extension = ResourceLoader::load(p_path); - if (extension.is_null()) { + + Ref<GDExtension> extension; + extension.instantiate(); + Error err = extension->open_library(p_path, p_loader); + if (err != OK) { return LOAD_STATUS_FAILED; } - LoadStatus status = _load_extension_internal(extension); + LoadStatus status = _load_extension_internal(extension, true); if (status != LOAD_STATUS_OK) { return status; } + extension->set_path(p_path); gdextension_map[p_path] = extension; return LOAD_STATUS_OK; } @@ -117,12 +144,12 @@ GDExtensionManager::LoadStatus GDExtensionManager::reload_extension(const String extension->close_library(); } - Error err = GDExtensionResourceLoader::load_gdextension_resource(p_path, extension); + Error err = extension->open_library(p_path, extension->loader); if (err != OK) { return LOAD_STATUS_FAILED; } - status = _load_extension_internal(extension); + status = _load_extension_internal(extension, false); if (status != LOAD_STATUS_OK) { return status; } @@ -261,6 +288,71 @@ void GDExtensionManager::reload_extensions() { #endif } +bool GDExtensionManager::ensure_extensions_loaded(const HashSet<String> &p_extensions) { + Vector<String> extensions_added; + Vector<String> extensions_removed; + + for (const String &E : p_extensions) { + if (!is_extension_loaded(E)) { + extensions_added.push_back(E); + } + } + + Vector<String> loaded_extensions = get_loaded_extensions(); + for (const String &loaded_extension : loaded_extensions) { + if (!p_extensions.has(loaded_extension)) { + // The extension may not have a .gdextension file. + if (!FileAccess::exists(loaded_extension)) { + extensions_removed.push_back(loaded_extension); + } + } + } + + String extension_list_config_file = GDExtension::get_extension_list_config_file(); + if (p_extensions.size()) { + if (extensions_added.size() || extensions_removed.size()) { + // Extensions were added or removed. + Ref<FileAccess> f = FileAccess::open(extension_list_config_file, FileAccess::WRITE); + for (const String &E : p_extensions) { + f->store_line(E); + } + } + } else { + if (loaded_extensions.size() || FileAccess::exists(extension_list_config_file)) { + // Extensions were removed. + Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES); + da->remove(extension_list_config_file); + } + } + + bool needs_restart = false; + for (const String &extension : extensions_added) { + GDExtensionManager::LoadStatus st = GDExtensionManager::get_singleton()->load_extension(extension); + if (st == GDExtensionManager::LOAD_STATUS_NEEDS_RESTART) { + needs_restart = true; + } + } + + for (const String &extension : extensions_removed) { + GDExtensionManager::LoadStatus st = GDExtensionManager::get_singleton()->unload_extension(extension); + if (st == GDExtensionManager::LOAD_STATUS_NEEDS_RESTART) { + needs_restart = true; + } + } + +#ifdef TOOLS_ENABLED + if (extensions_added.size() || extensions_removed.size()) { + // Emitting extensions_reloaded so EditorNode can reload Inspector and regenerate documentation. + emit_signal("extensions_reloaded"); + + // Reload all scripts to clear out old references. + callable_mp_static(&GDExtensionManager::_reload_all_scripts).call_deferred(); + } +#endif + + return needs_restart; +} + GDExtensionManager *GDExtensionManager::get_singleton() { return singleton; } @@ -281,6 +373,8 @@ void GDExtensionManager::_bind_methods() { BIND_ENUM_CONSTANT(LOAD_STATUS_NEEDS_RESTART); ADD_SIGNAL(MethodInfo("extensions_reloaded")); + ADD_SIGNAL(MethodInfo("extension_loaded", PropertyInfo(Variant::OBJECT, "extension", PROPERTY_HINT_RESOURCE_TYPE, "GDExtension"))); + ADD_SIGNAL(MethodInfo("extension_unloading", PropertyInfo(Variant::OBJECT, "extension", PROPERTY_HINT_RESOURCE_TYPE, "GDExtension"))); } GDExtensionManager *GDExtensionManager::singleton = nullptr; diff --git a/core/extension/gdextension_manager.h b/core/extension/gdextension_manager.h index 9386e356bb..39a600474c 100644 --- a/core/extension/gdextension_manager.h +++ b/core/extension/gdextension_manager.h @@ -54,7 +54,7 @@ public: }; private: - LoadStatus _load_extension_internal(const Ref<GDExtension> &p_extension); + LoadStatus _load_extension_internal(const Ref<GDExtension> &p_extension, bool p_first_load); LoadStatus _unload_extension_internal(const Ref<GDExtension> &p_extension); #ifdef TOOLS_ENABLED @@ -63,6 +63,7 @@ private: public: LoadStatus load_extension(const String &p_path); + LoadStatus load_extension_with_loader(const String &p_path, const Ref<GDExtensionLoader> &p_loader); LoadStatus reload_extension(const String &p_path); LoadStatus unload_extension(const String &p_path); bool is_extension_loaded(const String &p_path) const; @@ -84,6 +85,7 @@ public: void load_extensions(); void reload_extensions(); + bool ensure_extensions_loaded(const HashSet<String> &p_extensions); GDExtensionManager(); ~GDExtensionManager(); diff --git a/core/input/godotcontrollerdb.txt b/core/input/godotcontrollerdb.txt index f5158bfabb..8e8ec4c718 100644 --- a/core/input/godotcontrollerdb.txt +++ b/core/input/godotcontrollerdb.txt @@ -8,7 +8,7 @@ __XINPUT_DEVICE__,XInput Gamepad,a:b12,b:b13,x:b14,y:b15,start:b4,guide:b10,back Default Android Gamepad,Default Controller,leftx:a0,lefty:a1,dpdown:h0.4,rightstick:b8,rightshoulder:b10,rightx:a2,start:b6,righty:a3,dpleft:h0.8,lefttrigger:a4,x:b2,dpup:h0.1,back:b4,leftstick:b7,leftshoulder:b9,y:b3,a:b0,dpright:h0.2,righttrigger:a5,b:b1,platform:Android, # Web -standard,Standard Gamepad Mapping,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,a:b0,b:b1,x:b2,y:b3,leftshoulder:b4,rightshoulder:b5,back:b8,start:b9,leftstick:b10,rightstick:b11,dpup:b12,dpdown:b13,dpleft:b14,dpright:b15,guide:b16,leftstick:b10,rightstick:b11,platform:Web, +standard,Standard Gamepad Mapping,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:+a4,righttrigger:+a5,a:b0,b:b1,x:b2,y:b3,leftshoulder:b4,rightshoulder:b5,back:b8,start:b9,leftstick:b10,rightstick:b11,dpup:b12,dpdown:b13,dpleft:b14,dpright:b15,guide:b16,leftstick:b10,rightstick:b11,platform:Web, Linux24c6581a,PowerA Xbox One Cabled,a:b0,b:b1,y:b3,x:b2,start:b7,guide:b8,back:b6,leftstick:b9,rightstick:b10,leftshoulder:b4,rightshoulder:b5,dpup:-a7,dpleft:-a6,dpdown:+a7,dpright:+a6,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,platform:Web, Linux0e6f0301,Logic 3 Controller (xbox compatible),a:b0,b:b1,y:b3,x:b2,start:b7,guide:b8,back:b6,leftstick:b9,rightstick:b10,leftshoulder:b4,rightshoulder:b5,dpup:-a7,dpleft:-a6,dpdown:+a7,dpright:+a6,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,platform:Web, Linux045e028e,Microsoft X-Box 360 pad,a:b0,b:b1,y:b3,x:b2,start:b7,guide:b8,back:b6,leftstick:b9,rightstick:b10,leftshoulder:b4,rightshoulder:b5,dpup:-a7,dpleft:-a6,dpdown:+a7,dpright:+a6,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,platform:Web, diff --git a/core/input/input.cpp b/core/input/input.cpp index 56f616fac4..eba7ded267 100644 --- a/core/input/input.cpp +++ b/core/input/input.cpp @@ -513,21 +513,49 @@ void Input::joy_connection_changed(int p_idx, bool p_connected, const String &p_ Vector3 Input::get_gravity() const { _THREAD_SAFE_METHOD_ + +#ifdef DEBUG_ENABLED + if (!gravity_enabled) { + WARN_PRINT_ONCE("`input_devices/sensors/enable_gravity` is not enabled in project settings."); + } +#endif + return gravity; } Vector3 Input::get_accelerometer() const { _THREAD_SAFE_METHOD_ + +#ifdef DEBUG_ENABLED + if (!accelerometer_enabled) { + WARN_PRINT_ONCE("`input_devices/sensors/enable_accelerometer` is not enabled in project settings."); + } +#endif + return accelerometer; } Vector3 Input::get_magnetometer() const { _THREAD_SAFE_METHOD_ + +#ifdef DEBUG_ENABLED + if (!magnetometer_enabled) { + WARN_PRINT_ONCE("`input_devices/sensors/enable_magnetometer` is not enabled in project settings."); + } +#endif + return magnetometer; } Vector3 Input::get_gyroscope() const { _THREAD_SAFE_METHOD_ + +#ifdef DEBUG_ENABLED + if (!gyroscope_enabled) { + WARN_PRINT_ONCE("`input_devices/sensors/enable_gyroscope` is not enabled in project settings."); + } +#endif + return gyroscope; } @@ -758,12 +786,13 @@ void Input::_parse_input_event_impl(const Ref<InputEvent> &p_event, bool p_is_em bool was_pressed = action_state.cache.pressed; _update_action_cache(E.key, action_state); + // As input may come in part way through a physics tick, the earliest we can react to it is the next physics tick. if (action_state.cache.pressed && !was_pressed) { - action_state.pressed_physics_frame = Engine::get_singleton()->get_physics_frames(); + action_state.pressed_physics_frame = Engine::get_singleton()->get_physics_frames() + 1; action_state.pressed_process_frame = Engine::get_singleton()->get_process_frames(); } if (!action_state.cache.pressed && was_pressed) { - action_state.released_physics_frame = Engine::get_singleton()->get_physics_frames(); + action_state.released_physics_frame = Engine::get_singleton()->get_physics_frames() + 1; action_state.released_process_frame = Engine::get_singleton()->get_process_frames(); } } @@ -889,8 +918,9 @@ void Input::action_press(const StringName &p_action, float p_strength) { // Create or retrieve existing action. ActionState &action_state = action_states[p_action]; + // As input may come in part way through a physics tick, the earliest we can react to it is the next physics tick. if (!action_state.cache.pressed) { - action_state.pressed_physics_frame = Engine::get_singleton()->get_physics_frames(); + action_state.pressed_physics_frame = Engine::get_singleton()->get_physics_frames() + 1; action_state.pressed_process_frame = Engine::get_singleton()->get_process_frames(); } action_state.exact = true; @@ -907,7 +937,8 @@ void Input::action_release(const StringName &p_action) { action_state.cache.pressed = 0; action_state.cache.strength = 0.0; action_state.cache.raw_strength = 0.0; - action_state.released_physics_frame = Engine::get_singleton()->get_physics_frames(); + // As input may come in part way through a physics tick, the earliest we can react to it is the next physics tick. + action_state.released_physics_frame = Engine::get_singleton()->get_physics_frames() + 1; action_state.released_process_frame = Engine::get_singleton()->get_process_frames(); action_state.device_states.clear(); action_state.exact = true; @@ -1022,7 +1053,7 @@ void Input::parse_input_event(const Ref<InputEvent> &p_event) { if (buffered_events.is_empty() || !buffered_events.back()->get()->accumulate(p_event)) { buffered_events.push_back(p_event); } - } else if (use_input_buffering) { + } else if (agile_input_event_flushing) { buffered_events.push_back(p_event); } else { _parse_input_event_impl(p_event, false); @@ -1053,12 +1084,12 @@ void Input::flush_buffered_events() { } } -bool Input::is_using_input_buffering() { - return use_input_buffering; +bool Input::is_agile_input_event_flushing() { + return agile_input_event_flushing; } -void Input::set_use_input_buffering(bool p_enable) { - use_input_buffering = p_enable; +void Input::set_agile_input_event_flushing(bool p_enable) { + agile_input_event_flushing = p_enable; } void Input::set_use_accumulated_input(bool p_enable) { @@ -1680,6 +1711,11 @@ Input::Input() { // Always use standard behavior in the editor. legacy_just_pressed_behavior = false; } + + accelerometer_enabled = GLOBAL_DEF_RST_BASIC("input_devices/sensors/enable_accelerometer", false); + gravity_enabled = GLOBAL_DEF_RST_BASIC("input_devices/sensors/enable_gravity", false); + gyroscope_enabled = GLOBAL_DEF_RST_BASIC("input_devices/sensors/enable_gyroscope", false); + magnetometer_enabled = GLOBAL_DEF_RST_BASIC("input_devices/sensors/enable_magnetometer", false); } Input::~Input() { diff --git a/core/input/input.h b/core/input/input.h index 4daea0c9e8..95dd623cc0 100644 --- a/core/input/input.h +++ b/core/input/input.h @@ -92,9 +92,13 @@ private: RBSet<JoyButton> joy_buttons_pressed; RBMap<JoyAxis, float> _joy_axis; //RBMap<StringName,int> custom_action_press; + bool gravity_enabled = false; Vector3 gravity; + bool accelerometer_enabled = false; Vector3 accelerometer; + bool magnetometer_enabled = false; Vector3 magnetometer; + bool gyroscope_enabled = false; Vector3 gyroscope; Vector2 mouse_pos; int64_t mouse_window = 0; @@ -128,7 +132,7 @@ private: bool emulate_touch_from_mouse = false; bool emulate_mouse_from_touch = false; - bool use_input_buffering = false; + bool agile_input_event_flushing = false; bool use_accumulated_input = true; int mouse_from_touch_index = -1; @@ -367,8 +371,8 @@ public: void flush_frame_parsed_events(); #endif void flush_buffered_events(); - bool is_using_input_buffering(); - void set_use_input_buffering(bool p_enable); + bool is_agile_input_event_flushing(); + void set_agile_input_event_flushing(bool p_enable); void set_use_accumulated_input(bool p_enable); bool is_using_accumulated_input(); diff --git a/core/input/input_map.cpp b/core/input/input_map.cpp index 178d02b987..ddeee9d765 100644 --- a/core/input/input_map.cpp +++ b/core/input/input_map.cpp @@ -636,6 +636,7 @@ const HashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() { inputs = List<Ref<InputEvent>>(); inputs.push_back(InputEventKey::create_reference(Key::A | KeyModifierMask::CTRL)); inputs.push_back(InputEventKey::create_reference(Key::LEFT | KeyModifierMask::CMD_OR_CTRL)); + inputs.push_back(InputEventKey::create_reference(Key::HOME)); default_builtin_cache.insert("ui_text_caret_line_start.macos", inputs); inputs = List<Ref<InputEvent>>(); @@ -645,6 +646,7 @@ const HashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() { inputs = List<Ref<InputEvent>>(); inputs.push_back(InputEventKey::create_reference(Key::E | KeyModifierMask::CTRL)); inputs.push_back(InputEventKey::create_reference(Key::RIGHT | KeyModifierMask::CMD_OR_CTRL)); + inputs.push_back(InputEventKey::create_reference(Key::END)); default_builtin_cache.insert("ui_text_caret_line_end.macos", inputs); // Text Caret Movement Page Up/Down @@ -665,6 +667,7 @@ const HashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() { inputs = List<Ref<InputEvent>>(); inputs.push_back(InputEventKey::create_reference(Key::UP | KeyModifierMask::CMD_OR_CTRL)); + inputs.push_back(InputEventKey::create_reference(Key::HOME | KeyModifierMask::CMD_OR_CTRL)); default_builtin_cache.insert("ui_text_caret_document_start.macos", inputs); inputs = List<Ref<InputEvent>>(); @@ -673,6 +676,7 @@ const HashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() { inputs = List<Ref<InputEvent>>(); inputs.push_back(InputEventKey::create_reference(Key::DOWN | KeyModifierMask::CMD_OR_CTRL)); + inputs.push_back(InputEventKey::create_reference(Key::END | KeyModifierMask::CMD_OR_CTRL)); default_builtin_cache.insert("ui_text_caret_document_end.macos", inputs); // Text Caret Addition Below/Above diff --git a/core/io/dtls_server.cpp b/core/io/dtls_server.cpp index 07d62d3a8d..7638328dc3 100644 --- a/core/io/dtls_server.cpp +++ b/core/io/dtls_server.cpp @@ -33,12 +33,12 @@ #include "core/config/project_settings.h" #include "core/io/file_access.h" -DTLSServer *(*DTLSServer::_create)() = nullptr; +DTLSServer *(*DTLSServer::_create)(bool p_notify_postinitialize) = nullptr; bool DTLSServer::available = false; -DTLSServer *DTLSServer::create() { +DTLSServer *DTLSServer::create(bool p_notify_postinitialize) { if (_create) { - return _create(); + return _create(p_notify_postinitialize); } return nullptr; } diff --git a/core/io/dtls_server.h b/core/io/dtls_server.h index f3fbde3c15..5ffed1ecc3 100644 --- a/core/io/dtls_server.h +++ b/core/io/dtls_server.h @@ -38,14 +38,14 @@ class DTLSServer : public RefCounted { GDCLASS(DTLSServer, RefCounted); protected: - static DTLSServer *(*_create)(); + static DTLSServer *(*_create)(bool p_notify_postinitialize); static void _bind_methods(); static bool available; public: static bool is_available(); - static DTLSServer *create(); + static DTLSServer *create(bool p_notify_postinitialize = true); virtual Error setup(Ref<TLSOptions> p_options) = 0; virtual void stop() = 0; diff --git a/core/io/file_access.cpp b/core/io/file_access.cpp index 1cf388b33a..d919243e6b 100644 --- a/core/io/file_access.cpp +++ b/core/io/file_access.cpp @@ -59,11 +59,9 @@ bool FileAccess::exists(const String &p_name) { return true; } - Ref<FileAccess> f = open(p_name, READ); - if (f.is_null()) { - return false; - } - return true; + // Using file_exists because it's faster than trying to open the file. + Ref<FileAccess> ret = create_for_path(p_name); + return ret->file_exists(p_name); } void FileAccess::_set_access_type(AccessType p_access) { @@ -225,59 +223,44 @@ String FileAccess::fix_path(const String &p_path) const { } /* these are all implemented for ease of porting, then can later be optimized */ +uint8_t FileAccess::get_8() const { + uint8_t data = 0; + get_buffer(&data, sizeof(uint8_t)); -uint16_t FileAccess::get_16() const { - uint16_t res; - uint8_t a, b; + return data; +} - a = get_8(); - b = get_8(); +uint16_t FileAccess::get_16() const { + uint16_t data = 0; + get_buffer(reinterpret_cast<uint8_t *>(&data), sizeof(uint16_t)); if (big_endian) { - SWAP(a, b); + data = BSWAP16(data); } - res = b; - res <<= 8; - res |= a; - - return res; + return data; } uint32_t FileAccess::get_32() const { - uint32_t res; - uint16_t a, b; - - a = get_16(); - b = get_16(); + uint32_t data = 0; + get_buffer(reinterpret_cast<uint8_t *>(&data), sizeof(uint32_t)); if (big_endian) { - SWAP(a, b); + data = BSWAP32(data); } - res = b; - res <<= 16; - res |= a; - - return res; + return data; } uint64_t FileAccess::get_64() const { - uint64_t res; - uint32_t a, b; - - a = get_32(); - b = get_32(); + uint64_t data = 0; + get_buffer(reinterpret_cast<uint8_t *>(&data), sizeof(uint64_t)); if (big_endian) { - SWAP(a, b); + data = BSWAP64(data); } - res = b; - res <<= 32; - res |= a; - - return res; + return data; } float FileAccess::get_float() const { @@ -467,17 +450,6 @@ String FileAccess::get_as_text(bool p_skip_cr) const { return text; } -uint64_t FileAccess::get_buffer(uint8_t *p_dst, uint64_t p_length) const { - ERR_FAIL_COND_V(!p_dst && p_length > 0, -1); - - uint64_t i = 0; - for (i = 0; i < p_length && !eof_reached(); i++) { - p_dst[i] = get_8(); - } - - return i; -} - Vector<uint8_t> FileAccess::get_buffer(int64_t p_length) const { Vector<uint8_t> data; @@ -490,7 +462,7 @@ Vector<uint8_t> FileAccess::get_buffer(int64_t p_length) const { ERR_FAIL_COND_V_MSG(err != OK, data, "Can't resize data to " + itos(p_length) + " elements."); uint8_t *w = data.ptrw(); - int64_t len = get_buffer(&w[0], p_length); + int64_t len = get_buffer(w, p_length); if (len < p_length) { data.resize(len); @@ -514,46 +486,32 @@ String FileAccess::get_as_utf8_string(bool p_skip_cr) const { return s; } -void FileAccess::store_16(uint16_t p_dest) { - uint8_t a, b; - - a = p_dest & 0xFF; - b = p_dest >> 8; +void FileAccess::store_8(uint8_t p_dest) { + store_buffer(&p_dest, sizeof(uint8_t)); +} +void FileAccess::store_16(uint16_t p_dest) { if (big_endian) { - SWAP(a, b); + p_dest = BSWAP16(p_dest); } - store_8(a); - store_8(b); + store_buffer(reinterpret_cast<uint8_t *>(&p_dest), sizeof(uint16_t)); } void FileAccess::store_32(uint32_t p_dest) { - uint16_t a, b; - - a = p_dest & 0xFFFF; - b = p_dest >> 16; - if (big_endian) { - SWAP(a, b); + p_dest = BSWAP32(p_dest); } - store_16(a); - store_16(b); + store_buffer(reinterpret_cast<uint8_t *>(&p_dest), sizeof(uint32_t)); } void FileAccess::store_64(uint64_t p_dest) { - uint32_t a, b; - - a = p_dest & 0xFFFFFFFF; - b = p_dest >> 32; - if (big_endian) { - SWAP(a, b); + p_dest = BSWAP64(p_dest); } - store_32(a); - store_32(b); + store_buffer(reinterpret_cast<uint8_t *>(&p_dest), sizeof(uint64_t)); } void FileAccess::store_real(real_t p_real) { @@ -710,22 +668,11 @@ void FileAccess::store_csv_line(const Vector<String> &p_values, const String &p_ store_line(line); } -void FileAccess::store_buffer(const uint8_t *p_src, uint64_t p_length) { - ERR_FAIL_COND(!p_src && p_length > 0); - for (uint64_t i = 0; i < p_length; i++) { - store_8(p_src[i]); - } -} - void FileAccess::store_buffer(const Vector<uint8_t> &p_buffer) { uint64_t len = p_buffer.size(); - if (len == 0) { - return; - } - const uint8_t *r = p_buffer.ptr(); - store_buffer(&r[0], len); + store_buffer(r, len); } void FileAccess::store_var(const Variant &p_var, bool p_full_objects) { diff --git a/core/io/file_access.h b/core/io/file_access.h index 2ab84db4b6..2f4d1a8604 100644 --- a/core/io/file_access.h +++ b/core/io/file_access.h @@ -137,7 +137,7 @@ public: virtual bool eof_reached() const = 0; ///< reading passed EOF - virtual uint8_t get_8() const = 0; ///< get a byte + virtual uint8_t get_8() const; ///< get a byte virtual uint16_t get_16() const; ///< get 16 bits uint virtual uint32_t get_32() const; ///< get 32 bits uint virtual uint64_t get_64() const; ///< get 64 bits uint @@ -148,7 +148,7 @@ public: Variant get_var(bool p_allow_objects = false) const; - virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const; ///< get an array of bytes + virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const = 0; ///< get an array of bytes, needs to be overwritten by children. Vector<uint8_t> get_buffer(int64_t p_length) const; virtual String get_line() const; virtual String get_token() const; @@ -168,7 +168,7 @@ public: virtual Error resize(int64_t p_length) = 0; virtual void flush() = 0; - virtual void store_8(uint8_t p_dest) = 0; ///< store a byte + virtual void store_8(uint8_t p_dest); ///< store a byte virtual void store_16(uint16_t p_dest); ///< store 16 bits uint virtual void store_32(uint32_t p_dest); ///< store 32 bits uint virtual void store_64(uint64_t p_dest); ///< store 64 bits uint @@ -184,7 +184,7 @@ public: virtual void store_pascal_string(const String &p_string); virtual String get_pascal_string(); - virtual void store_buffer(const uint8_t *p_src, uint64_t p_length); ///< store an array of bytes + virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) = 0; ///< store an array of bytes, needs to be overwritten by children. void store_buffer(const Vector<uint8_t> &p_buffer); void store_var(const Variant &p_var, bool p_full_objects = false); diff --git a/core/io/file_access_compressed.cpp b/core/io/file_access_compressed.cpp index 0f00bd292c..3602baf8c5 100644 --- a/core/io/file_access_compressed.cpp +++ b/core/io/file_access_compressed.cpp @@ -260,38 +260,6 @@ bool FileAccessCompressed::eof_reached() const { } } -uint8_t FileAccessCompressed::get_8() const { - ERR_FAIL_COND_V_MSG(f.is_null(), 0, "File must be opened before use."); - ERR_FAIL_COND_V_MSG(writing, 0, "File has not been opened in read mode."); - - if (at_end) { - read_eof = true; - return 0; - } - - uint8_t ret = read_ptr[read_pos]; - - read_pos++; - if (read_pos >= read_block_size) { - read_block++; - - if (read_block < read_block_count) { - //read another block of compressed data - f->get_buffer(comp_buffer.ptrw(), read_blocks[read_block].csize); - int total = Compression::decompress(buffer.ptrw(), read_blocks.size() == 1 ? read_total : block_size, comp_buffer.ptr(), read_blocks[read_block].csize, cmode); - ERR_FAIL_COND_V_MSG(total == -1, 0, "Compressed file is corrupt."); - read_block_size = read_block == read_block_count - 1 ? read_total % block_size : block_size; - read_pos = 0; - - } else { - read_block--; - at_end = true; - } - } - - return ret; -} - uint64_t FileAccessCompressed::get_buffer(uint8_t *p_dst, uint64_t p_length) const { ERR_FAIL_COND_V(!p_dst && p_length > 0, -1); ERR_FAIL_COND_V_MSG(f.is_null(), -1, "File must be opened before use."); @@ -341,12 +309,13 @@ void FileAccessCompressed::flush() { // compressed files keep data in memory till close() } -void FileAccessCompressed::store_8(uint8_t p_dest) { +void FileAccessCompressed::store_buffer(const uint8_t *p_src, uint64_t p_length) { ERR_FAIL_COND_MSG(f.is_null(), "File must be opened before use."); ERR_FAIL_COND_MSG(!writing, "File has not been opened in write mode."); - WRITE_FIT(1); - write_ptr[write_pos++] = p_dest; + WRITE_FIT(p_length); + memcpy(write_ptr + write_pos, p_src, p_length); + write_pos += p_length; } bool FileAccessCompressed::file_exists(const String &p_name) { diff --git a/core/io/file_access_compressed.h b/core/io/file_access_compressed.h index f706c82f8e..ea9837dd03 100644 --- a/core/io/file_access_compressed.h +++ b/core/io/file_access_compressed.h @@ -83,14 +83,13 @@ public: virtual bool eof_reached() const override; ///< reading passed EOF - virtual uint8_t get_8() const override; ///< get a byte virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override; virtual Error get_error() const override; ///< get last error virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; } virtual void flush() override; - virtual void store_8(uint8_t p_dest) override; ///< store a byte + virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override; virtual bool file_exists(const String &p_name) override; ///< return true if a file exists diff --git a/core/io/file_access_encrypted.cpp b/core/io/file_access_encrypted.cpp index b689f5b628..13d1e0c8fc 100644 --- a/core/io/file_access_encrypted.cpp +++ b/core/io/file_access_encrypted.cpp @@ -37,7 +37,7 @@ #include <stdio.h> Error FileAccessEncrypted::open_and_parse(Ref<FileAccess> p_base, const Vector<uint8_t> &p_key, Mode p_mode, bool p_with_magic) { - ERR_FAIL_COND_V_MSG(file != nullptr, ERR_ALREADY_IN_USE, "Can't open file while another file from path '" + file->get_path_absolute() + "' is open."); + ERR_FAIL_COND_V_MSG(file.is_valid(), ERR_ALREADY_IN_USE, "Can't open file while another file from path '" + file->get_path_absolute() + "' is open."); ERR_FAIL_COND_V(p_key.size() != 32, ERR_INVALID_PARAMETER); pos = 0; @@ -162,7 +162,7 @@ void FileAccessEncrypted::_close() { } bool FileAccessEncrypted::is_open() const { - return file != nullptr; + return file.is_valid(); } String FileAccessEncrypted::get_path() const { @@ -206,26 +206,13 @@ bool FileAccessEncrypted::eof_reached() const { return eofed; } -uint8_t FileAccessEncrypted::get_8() const { - ERR_FAIL_COND_V_MSG(writing, 0, "File has not been opened in read mode."); - if (pos >= get_length()) { - eofed = true; - return 0; - } - - uint8_t b = data[pos]; - pos++; - return b; -} - uint64_t FileAccessEncrypted::get_buffer(uint8_t *p_dst, uint64_t p_length) const { ERR_FAIL_COND_V(!p_dst && p_length > 0, -1); ERR_FAIL_COND_V_MSG(writing, -1, "File has not been opened in read mode."); uint64_t to_copy = MIN(p_length, get_length() - pos); - for (uint64_t i = 0; i < to_copy; i++) { - p_dst[i] = data[pos++]; - } + memcpy(p_dst, data.ptr() + pos, to_copy); + pos += to_copy; if (to_copy < p_length) { eofed = true; @@ -242,17 +229,12 @@ void FileAccessEncrypted::store_buffer(const uint8_t *p_src, uint64_t p_length) ERR_FAIL_COND_MSG(!writing, "File has not been opened in write mode."); ERR_FAIL_COND(!p_src && p_length > 0); - if (pos < get_length()) { - for (uint64_t i = 0; i < p_length; i++) { - store_8(p_src[i]); - } - } else if (pos == get_length()) { + if (pos + p_length >= get_length()) { data.resize(pos + p_length); - for (uint64_t i = 0; i < p_length; i++) { - data.write[pos + i] = p_src[i]; - } - pos += p_length; } + + memcpy(data.ptrw() + pos, p_src, p_length); + pos += p_length; } void FileAccessEncrypted::flush() { @@ -261,18 +243,6 @@ void FileAccessEncrypted::flush() { // encrypted files keep data in memory till close() } -void FileAccessEncrypted::store_8(uint8_t p_dest) { - ERR_FAIL_COND_MSG(!writing, "File has not been opened in write mode."); - - if (pos < get_length()) { - data.write[pos] = p_dest; - pos++; - } else if (pos == get_length()) { - data.push_back(p_dest); - pos++; - } -} - bool FileAccessEncrypted::file_exists(const String &p_name) { Ref<FileAccess> fa = FileAccess::open(p_name, FileAccess::READ); if (fa.is_null()) { diff --git a/core/io/file_access_encrypted.h b/core/io/file_access_encrypted.h index 42afe49a5e..5f8c803d60 100644 --- a/core/io/file_access_encrypted.h +++ b/core/io/file_access_encrypted.h @@ -73,14 +73,12 @@ public: virtual bool eof_reached() const override; ///< reading passed EOF - virtual uint8_t get_8() const override; ///< get a byte virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override; virtual Error get_error() const override; ///< get last error virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; } virtual void flush() override; - virtual void store_8(uint8_t p_dest) override; ///< store a byte virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override; ///< store an array of bytes virtual bool file_exists(const String &p_name) override; ///< return true if a file exists diff --git a/core/io/file_access_memory.cpp b/core/io/file_access_memory.cpp index 9521a4f666..1541a5ed4a 100644 --- a/core/io/file_access_memory.cpp +++ b/core/io/file_access_memory.cpp @@ -122,16 +122,6 @@ bool FileAccessMemory::eof_reached() const { return pos >= length; } -uint8_t FileAccessMemory::get_8() const { - uint8_t ret = 0; - if (pos < length) { - ret = data[pos]; - } - ++pos; - - return ret; -} - uint64_t FileAccessMemory::get_buffer(uint8_t *p_dst, uint64_t p_length) const { ERR_FAIL_COND_V(!p_dst && p_length > 0, -1); ERR_FAIL_NULL_V(data, -1); @@ -157,16 +147,12 @@ void FileAccessMemory::flush() { ERR_FAIL_NULL(data); } -void FileAccessMemory::store_8(uint8_t p_byte) { - ERR_FAIL_NULL(data); - ERR_FAIL_COND(pos >= length); - data[pos++] = p_byte; -} - void FileAccessMemory::store_buffer(const uint8_t *p_src, uint64_t p_length) { ERR_FAIL_COND(!p_src && p_length > 0); + uint64_t left = length - pos; uint64_t write = MIN(p_length, left); + if (write < p_length) { WARN_PRINT("Writing less data than requested"); } diff --git a/core/io/file_access_memory.h b/core/io/file_access_memory.h index e9fbc26d75..39e1528d97 100644 --- a/core/io/file_access_memory.h +++ b/core/io/file_access_memory.h @@ -55,15 +55,12 @@ public: virtual bool eof_reached() const override; ///< reading passed EOF - virtual uint8_t get_8() const override; ///< get a byte - virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override; ///< get an array of bytes virtual Error get_error() const override; ///< get last error virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; } virtual void flush() override; - virtual void store_8(uint8_t p_byte) override; ///< store a byte virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override; ///< store an array of bytes virtual bool file_exists(const String &p_name) override; ///< return true if a file exists diff --git a/core/io/file_access_pack.cpp b/core/io/file_access_pack.cpp index 991b94db38..eec27ce0aa 100644 --- a/core/io/file_access_pack.cpp +++ b/core/io/file_access_pack.cpp @@ -119,6 +119,10 @@ void PackedData::_free_packed_dirs(PackedDir *p_dir) { } PackedData::~PackedData() { + if (singleton == this) { + singleton = nullptr; + } + for (int i = 0; i < sources.size(); i++) { memdelete(sources[i]); } @@ -309,17 +313,6 @@ bool FileAccessPack::eof_reached() const { return eof; } -uint8_t FileAccessPack::get_8() const { - ERR_FAIL_COND_V_MSG(f.is_null(), 0, "File must be opened before use."); - if (pos >= pf.size) { - eof = true; - return 0; - } - - pos++; - return f->get_8(); -} - uint64_t FileAccessPack::get_buffer(uint8_t *p_dst, uint64_t p_length) const { ERR_FAIL_COND_V_MSG(f.is_null(), -1, "File must be opened before use."); ERR_FAIL_COND_V(!p_dst && p_length > 0, -1); @@ -362,10 +355,6 @@ void FileAccessPack::flush() { ERR_FAIL(); } -void FileAccessPack::store_8(uint8_t p_dest) { - ERR_FAIL(); -} - void FileAccessPack::store_buffer(const uint8_t *p_src, uint64_t p_length) { ERR_FAIL(); } diff --git a/core/io/file_access_pack.h b/core/io/file_access_pack.h index 594ac8f089..595a36bca4 100644 --- a/core/io/file_access_pack.h +++ b/core/io/file_access_pack.h @@ -169,8 +169,6 @@ public: virtual bool eof_reached() const override; - virtual uint8_t get_8() const override; - virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override; virtual void set_big_endian(bool p_big_endian) override; @@ -179,8 +177,6 @@ public: virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; } virtual void flush() override; - virtual void store_8(uint8_t p_dest) override; - virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override; virtual bool file_exists(const String &p_name) override; diff --git a/core/io/file_access_zip.cpp b/core/io/file_access_zip.cpp index c0d1afc8e1..b33b7b35c3 100644 --- a/core/io/file_access_zip.cpp +++ b/core/io/file_access_zip.cpp @@ -291,12 +291,6 @@ bool FileAccessZip::eof_reached() const { return at_eof; } -uint8_t FileAccessZip::get_8() const { - uint8_t ret = 0; - get_buffer(&ret, 1); - return ret; -} - uint64_t FileAccessZip::get_buffer(uint8_t *p_dst, uint64_t p_length) const { ERR_FAIL_COND_V(!p_dst && p_length > 0, -1); ERR_FAIL_NULL_V(zfile, -1); @@ -328,7 +322,7 @@ void FileAccessZip::flush() { ERR_FAIL(); } -void FileAccessZip::store_8(uint8_t p_dest) { +void FileAccessZip::store_buffer(const uint8_t *p_src, uint64_t p_length) { ERR_FAIL(); } diff --git a/core/io/file_access_zip.h b/core/io/file_access_zip.h index 88b63e93e2..1e11e050df 100644 --- a/core/io/file_access_zip.h +++ b/core/io/file_access_zip.h @@ -95,14 +95,13 @@ public: virtual bool eof_reached() const override; ///< reading passed EOF - virtual uint8_t get_8() const override; ///< get a byte virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override; virtual Error get_error() const override; ///< get last error virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; } virtual void flush() override; - virtual void store_8(uint8_t p_dest) override; ///< store a byte + virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override; virtual bool file_exists(const String &p_name) override; ///< return true if a file exists diff --git a/core/io/http_client.cpp b/core/io/http_client.cpp index 833fd1adc3..fc91341bed 100644 --- a/core/io/http_client.cpp +++ b/core/io/http_client.cpp @@ -42,9 +42,9 @@ const char *HTTPClient::_methods[METHOD_MAX] = { "PATCH" }; -HTTPClient *HTTPClient::create() { +HTTPClient *HTTPClient::create(bool p_notify_postinitialize) { if (_create) { - return _create(); + return _create(p_notify_postinitialize); } return nullptr; } diff --git a/core/io/http_client.h b/core/io/http_client.h index 9e018182e3..5945291122 100644 --- a/core/io/http_client.h +++ b/core/io/http_client.h @@ -158,12 +158,12 @@ protected: Error _request_raw(Method p_method, const String &p_url, const Vector<String> &p_headers, const Vector<uint8_t> &p_body); Error _request(Method p_method, const String &p_url, const Vector<String> &p_headers, const String &p_body = String()); - static HTTPClient *(*_create)(); + static HTTPClient *(*_create)(bool p_notify_postinitialize); static void _bind_methods(); public: - static HTTPClient *create(); + static HTTPClient *create(bool p_notify_postinitialize = true); String query_string_from_dict(const Dictionary &p_dict); Error verify_headers(const Vector<String> &p_headers); diff --git a/core/io/http_client_tcp.cpp b/core/io/http_client_tcp.cpp index 2f45238951..70fcad543a 100644 --- a/core/io/http_client_tcp.cpp +++ b/core/io/http_client_tcp.cpp @@ -35,8 +35,8 @@ #include "core/io/stream_peer_tls.h" #include "core/version.h" -HTTPClient *HTTPClientTCP::_create_func() { - return memnew(HTTPClientTCP); +HTTPClient *HTTPClientTCP::_create_func(bool p_notify_postinitialize) { + return static_cast<HTTPClient *>(ClassDB::creator<HTTPClientTCP>(p_notify_postinitialize)); } Error HTTPClientTCP::connect_to_host(const String &p_host, int p_port, Ref<TLSOptions> p_options) { @@ -792,6 +792,6 @@ HTTPClientTCP::HTTPClientTCP() { request_buffer.instantiate(); } -HTTPClient *(*HTTPClient::_create)() = HTTPClientTCP::_create_func; +HTTPClient *(*HTTPClient::_create)(bool p_notify_postinitialize) = HTTPClientTCP::_create_func; #endif // WEB_ENABLED diff --git a/core/io/http_client_tcp.h b/core/io/http_client_tcp.h index 6060c975bc..dd6cc6b84f 100644 --- a/core/io/http_client_tcp.h +++ b/core/io/http_client_tcp.h @@ -76,7 +76,7 @@ private: Error _get_http_data(uint8_t *p_buffer, int p_bytes, int &r_received); public: - static HTTPClient *_create_func(); + static HTTPClient *_create_func(bool p_notify_postinitialize); Error request(Method p_method, const String &p_url, const Vector<String> &p_headers, const uint8_t *p_body, int p_body_size) override; diff --git a/core/io/image.cpp b/core/io/image.cpp index 4b1188ad47..fcbe483e38 100644 --- a/core/io/image.cpp +++ b/core/io/image.cpp @@ -30,6 +30,7 @@ #include "image.h" +#include "core/config/project_settings.h" #include "core/error/error_list.h" #include "core/error/error_macros.h" #include "core/io/image_loader.h" @@ -300,10 +301,10 @@ int Image::get_format_block_size(Format p_format) { return 1; } -void Image::_get_mipmap_offset_and_size(int p_mipmap, int &r_offset, int &r_width, int &r_height) const { +void Image::_get_mipmap_offset_and_size(int p_mipmap, int64_t &r_offset, int &r_width, int &r_height) const { int w = width; int h = height; - int ofs = 0; + int64_t ofs = 0; int pixel_size = get_format_pixel_size(format); int pixel_rshift = get_format_pixel_rshift(format); @@ -315,7 +316,7 @@ void Image::_get_mipmap_offset_and_size(int p_mipmap, int &r_offset, int &r_widt int bw = w % block != 0 ? w + (block - w % block) : w; int bh = h % block != 0 ? h + (block - h % block) : h; - int s = bw * bh; + int64_t s = bw * bh; s *= pixel_size; s >>= pixel_rshift; @@ -329,37 +330,30 @@ void Image::_get_mipmap_offset_and_size(int p_mipmap, int &r_offset, int &r_widt r_height = h; } -int Image::get_mipmap_offset(int p_mipmap) const { +int64_t Image::get_mipmap_offset(int p_mipmap) const { ERR_FAIL_INDEX_V(p_mipmap, get_mipmap_count() + 1, -1); - int ofs, w, h; + int64_t ofs; + int w, h; _get_mipmap_offset_and_size(p_mipmap, ofs, w, h); return ofs; } -int Image::get_mipmap_byte_size(int p_mipmap) const { - ERR_FAIL_INDEX_V(p_mipmap, get_mipmap_count() + 1, -1); - - int ofs, w, h; - _get_mipmap_offset_and_size(p_mipmap, ofs, w, h); - int ofs2; - _get_mipmap_offset_and_size(p_mipmap + 1, ofs2, w, h); - return ofs2 - ofs; -} - -void Image::get_mipmap_offset_and_size(int p_mipmap, int &r_ofs, int &r_size) const { - int ofs, w, h; +void Image::get_mipmap_offset_and_size(int p_mipmap, int64_t &r_ofs, int64_t &r_size) const { + int64_t ofs; + int w, h; _get_mipmap_offset_and_size(p_mipmap, ofs, w, h); - int ofs2; + int64_t ofs2; _get_mipmap_offset_and_size(p_mipmap + 1, ofs2, w, h); r_ofs = ofs; r_size = ofs2 - ofs; } -void Image::get_mipmap_offset_size_and_dimensions(int p_mipmap, int &r_ofs, int &r_size, int &w, int &h) const { - int ofs; +void Image::get_mipmap_offset_size_and_dimensions(int p_mipmap, int64_t &r_ofs, int64_t &r_size, int &w, int &h) const { + int64_t ofs; _get_mipmap_offset_and_size(p_mipmap, ofs, w, h); - int ofs2, w2, h2; + int64_t ofs2; + int w2, h2; _get_mipmap_offset_and_size(p_mipmap + 1, ofs2, w2, h2); r_ofs = ofs; r_size = ofs2 - ofs; @@ -508,6 +502,38 @@ static void _convert(int p_width, int p_height, const uint8_t *p_src, uint8_t *p } } +template <typename T, uint32_t read_channels, uint32_t write_channels, T def_zero, T def_one> +static void _convert_fast(int p_width, int p_height, const T *p_src, T *p_dst) { + uint32_t dst_count = 0; + uint32_t src_count = 0; + + const int resolution = p_width * p_height; + + for (int i = 0; i < resolution; i++) { + memcpy(p_dst + dst_count, p_src + src_count, MIN(read_channels, write_channels) * sizeof(T)); + + if constexpr (write_channels > read_channels) { + const T def_value[4] = { def_zero, def_zero, def_zero, def_one }; + memcpy(p_dst + dst_count + read_channels, &def_value[read_channels], (write_channels - read_channels) * sizeof(T)); + } + + dst_count += write_channels; + src_count += read_channels; + } +} + +static bool _are_formats_compatible(Image::Format p_format0, Image::Format p_format1) { + if (p_format0 <= Image::FORMAT_RGBA8 && p_format1 <= Image::FORMAT_RGBA8) { + return true; + } else if (p_format0 <= Image::FORMAT_RGBAH && p_format0 >= Image::FORMAT_RH && p_format1 <= Image::FORMAT_RGBAH && p_format1 >= Image::FORMAT_RH) { + return true; + } else if (p_format0 <= Image::FORMAT_RGBAF && p_format0 >= Image::FORMAT_RF && p_format1 <= Image::FORMAT_RGBAF && p_format1 >= Image::FORMAT_RF) { + return true; + } + + return false; +} + void Image::convert(Format p_new_format) { ERR_FAIL_INDEX_MSG(p_new_format, FORMAT_MAX, "The Image format specified (" + itos(p_new_format) + ") is out of range. See Image's Format enum."); if (data.size() == 0) { @@ -524,7 +550,7 @@ void Image::convert(Format p_new_format) { if (Image::is_format_compressed(format) || Image::is_format_compressed(p_new_format)) { ERR_FAIL_MSG("Cannot convert to <-> from compressed formats. Use compress() and decompress() instead."); - } else if (format > FORMAT_RGBA8 || p_new_format > FORMAT_RGBA8) { + } else if (!_are_formats_compatible(format, p_new_format)) { //use put/set pixel which is slower but works with non byte formats Image new_img(width, height, mipmaps, p_new_format); @@ -538,8 +564,8 @@ void Image::convert(Format p_new_format) { } } - int mip_offset = 0; - int mip_size = 0; + int64_t mip_offset = 0; + int64_t mip_size = 0; new_img.get_mipmap_offset_and_size(mip, mip_offset, mip_size); memcpy(new_img.data.ptrw() + mip_offset, new_mip->data.ptr(), mip_size); @@ -555,8 +581,8 @@ void Image::convert(Format p_new_format) { int conversion_type = format | p_new_format << 8; for (int mip = 0; mip < mipmap_count; mip++) { - int mip_offset = 0; - int mip_size = 0; + int64_t mip_offset = 0; + int64_t mip_size = 0; int mip_width = 0; int mip_height = 0; get_mipmap_offset_size_and_dimensions(mip, mip_offset, mip_size, mip_width, mip_height); @@ -655,6 +681,78 @@ void Image::convert(Format p_new_format) { case FORMAT_RGBA8 | (FORMAT_RGB8 << 8): _convert<3, true, 3, false, false, false>(mip_width, mip_height, rptr, wptr); break; + case FORMAT_RH | (FORMAT_RGH << 8): + _convert_fast<uint16_t, 1, 2, 0x0000, 0x3C00>(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr); + break; + case FORMAT_RH | (FORMAT_RGBH << 8): + _convert_fast<uint16_t, 1, 3, 0x0000, 0x3C00>(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr); + break; + case FORMAT_RH | (FORMAT_RGBAH << 8): + _convert_fast<uint16_t, 1, 4, 0x0000, 0x3C00>(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr); + break; + case FORMAT_RGH | (FORMAT_RH << 8): + _convert_fast<uint16_t, 2, 1, 0x0000, 0x3C00>(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr); + break; + case FORMAT_RGH | (FORMAT_RGBH << 8): + _convert_fast<uint16_t, 2, 3, 0x0000, 0x3C00>(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr); + break; + case FORMAT_RGH | (FORMAT_RGBAH << 8): + _convert_fast<uint16_t, 2, 4, 0x0000, 0x3C00>(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr); + break; + case FORMAT_RGBH | (FORMAT_RH << 8): + _convert_fast<uint16_t, 3, 1, 0x0000, 0x3C00>(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr); + break; + case FORMAT_RGBH | (FORMAT_RGH << 8): + _convert_fast<uint16_t, 3, 2, 0x0000, 0x3C00>(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr); + break; + case FORMAT_RGBH | (FORMAT_RGBAH << 8): + _convert_fast<uint16_t, 3, 4, 0x0000, 0x3C00>(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr); + break; + case FORMAT_RGBAH | (FORMAT_RH << 8): + _convert_fast<uint16_t, 4, 1, 0x0000, 0x3C00>(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr); + break; + case FORMAT_RGBAH | (FORMAT_RGH << 8): + _convert_fast<uint16_t, 4, 2, 0x0000, 0x3C00>(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr); + break; + case FORMAT_RGBAH | (FORMAT_RGBH << 8): + _convert_fast<uint16_t, 4, 3, 0x0000, 0x3C00>(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr); + break; + case FORMAT_RF | (FORMAT_RGF << 8): + _convert_fast<uint32_t, 1, 2, 0x00000000, 0x3F800000>(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr); + break; + case FORMAT_RF | (FORMAT_RGBF << 8): + _convert_fast<uint32_t, 1, 3, 0x00000000, 0x3F800000>(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr); + break; + case FORMAT_RF | (FORMAT_RGBAF << 8): + _convert_fast<uint32_t, 1, 4, 0x00000000, 0x3F800000>(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr); + break; + case FORMAT_RGF | (FORMAT_RF << 8): + _convert_fast<uint32_t, 2, 1, 0x00000000, 0x3F800000>(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr); + break; + case FORMAT_RGF | (FORMAT_RGBF << 8): + _convert_fast<uint32_t, 2, 3, 0x00000000, 0x3F800000>(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr); + break; + case FORMAT_RGF | (FORMAT_RGBAF << 8): + _convert_fast<uint32_t, 2, 4, 0x00000000, 0x3F800000>(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr); + break; + case FORMAT_RGBF | (FORMAT_RF << 8): + _convert_fast<uint32_t, 3, 1, 0x00000000, 0x3F800000>(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr); + break; + case FORMAT_RGBF | (FORMAT_RGF << 8): + _convert_fast<uint32_t, 3, 2, 0x00000000, 0x3F800000>(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr); + break; + case FORMAT_RGBF | (FORMAT_RGBAF << 8): + _convert_fast<uint32_t, 3, 4, 0x00000000, 0x3F800000>(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr); + break; + case FORMAT_RGBAF | (FORMAT_RF << 8): + _convert_fast<uint32_t, 4, 1, 0x00000000, 0x3F800000>(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr); + break; + case FORMAT_RGBAF | (FORMAT_RGF << 8): + _convert_fast<uint32_t, 4, 2, 0x00000000, 0x3F800000>(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr); + break; + case FORMAT_RGBAF | (FORMAT_RGBF << 8): + _convert_fast<uint32_t, 4, 3, 0x00000000, 0x3F800000>(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr); + break; } } @@ -1151,7 +1249,7 @@ void Image::resize(int p_width, int p_height, Interpolation p_interpolation) { if (i == 0) { // Read from the first mipmap that will be interpolated // (if both levels are the same, we will not interpolate, but at least we'll sample from the right level) - int offs; + int64_t offs; _get_mipmap_offset_and_size(mip1, offs, src_width, src_height); src_ptr = r_ptr + offs; } else if (!interpolate_mipmaps) { @@ -1159,7 +1257,7 @@ void Image::resize(int p_width, int p_height, Interpolation p_interpolation) { break; } else { // Switch to read from the second mipmap that will be interpolated - int offs; + int64_t offs; _get_mipmap_offset_and_size(mip2, offs, src_width, src_height); src_ptr = r_ptr + offs; // Switch to write to the second destination image @@ -1599,9 +1697,9 @@ void Image::flip_x() { } /// Get mipmap size and offset. -int Image::_get_dst_image_size(int p_width, int p_height, Format p_format, int &r_mipmaps, int p_mipmaps, int *r_mm_width, int *r_mm_height) { +int64_t Image::_get_dst_image_size(int p_width, int p_height, Format p_format, int &r_mipmaps, int p_mipmaps, int *r_mm_width, int *r_mm_height) { // Data offset in mipmaps (including the original texture). - int size = 0; + int64_t size = 0; int w = p_width; int h = p_height; @@ -1623,7 +1721,7 @@ int Image::_get_dst_image_size(int p_width, int p_height, Format p_format, int & int bw = w % block != 0 ? w + (block - w % block) : w; int bh = h % block != 0 ? h + (block - h % block) : h; - int s = bw * bh; + int64_t s = bw * bh; s *= pixsize; s >>= pixshift; @@ -1837,7 +1935,8 @@ Error Image::generate_mipmaps(bool p_renormalize) { int prev_w = width; for (int i = 1; i <= mmcount; i++) { - int ofs, w, h; + int64_t ofs; + int w, h; _get_mipmap_offset_and_size(i, ofs, w, h); switch (format) { @@ -1993,7 +2092,8 @@ Error Image::generate_mipmap_roughness(RoughnessChannel p_roughness_channel, con uint8_t *base_ptr = data.ptrw(); for (int i = 1; i <= mmcount; i++) { - int ofs, w, h; + int64_t ofs; + int w, h; _get_mipmap_offset_and_size(i, ofs, w, h); uint8_t *ptr = &base_ptr[ofs]; @@ -2102,21 +2202,6 @@ Error Image::generate_mipmap_roughness(RoughnessChannel p_roughness_channel, con _set_color_at_ofs(ptr, pixel_ofs, c); } } -#if 0 - { - int size = get_mipmap_byte_size(i); - print_line("size for mimpap " + itos(i) + ": " + itos(size)); - Vector<uint8_t> imgdata; - imgdata.resize(size); - - - uint8_t* wr = imgdata.ptrw(); - memcpy(wr.ptr(), ptr, size); - wr = uint8_t*(); - Ref<Image> im = Image::create_from_data(w, h, false, format, imgdata); - im->save_png("res://mipmap_" + itos(i) + ".png"); - } -#endif } return OK; @@ -2131,7 +2216,8 @@ void Image::clear_mipmaps() { return; } - int ofs, w, h; + int64_t ofs; + int w, h; _get_mipmap_offset_and_size(1, ofs, w, h); data.resize(ofs); @@ -2176,7 +2262,7 @@ void Image::initialize_data(int p_width, int p_height, bool p_use_mipmaps, Forma ERR_FAIL_INDEX_MSG(p_format, FORMAT_MAX, "The Image format specified (" + itos(p_format) + ") is out of range. See Image's Format enum."); int mm = 0; - int size = _get_dst_image_size(p_width, p_height, p_format, mm, p_use_mipmaps ? -1 : 0); + int64_t size = _get_dst_image_size(p_width, p_height, p_format, mm, p_use_mipmaps ? -1 : 0); data.resize(size); { @@ -2202,7 +2288,7 @@ void Image::initialize_data(int p_width, int p_height, bool p_use_mipmaps, Forma ERR_FAIL_INDEX_MSG(p_format, FORMAT_MAX, "The Image format specified (" + itos(p_format) + ") is out of range. See Image's Format enum."); int mm; - int size = _get_dst_image_size(p_width, p_height, p_format, mm, p_use_mipmaps ? -1 : 0); + int64_t size = _get_dst_image_size(p_width, p_height, p_format, mm, p_use_mipmaps ? -1 : 0); if (unlikely(p_data.size() != size)) { String description_mipmaps = get_format_name(p_format) + " "; @@ -2405,7 +2491,7 @@ bool Image::is_invisible() const { return false; } - int len = data.size(); + int64_t len = data.size(); if (len == 0) { return true; @@ -2445,7 +2531,7 @@ bool Image::is_invisible() const { } Image::AlphaMode Image::detect_alpha() const { - int len = data.size(); + int64_t len = data.size(); if (len == 0) { return ALPHA_NONE; @@ -2579,7 +2665,7 @@ Vector<uint8_t> Image::save_webp_to_buffer(const bool p_lossy, const float p_qua return save_webp_buffer_func(Ref<Image>((Image *)this), p_lossy, p_quality); } -int Image::get_image_data_size(int p_width, int p_height, Format p_format, bool p_mipmaps) { +int64_t Image::get_image_data_size(int p_width, int p_height, Format p_format, bool p_mipmaps) { int mm; return _get_dst_image_size(p_width, p_height, p_format, mm, p_mipmaps ? -1 : 0); } @@ -2597,7 +2683,7 @@ Size2i Image::get_image_mipmap_size(int p_width, int p_height, Format p_format, return ret; } -int Image::get_image_mipmap_offset(int p_width, int p_height, Format p_format, int p_mipmap) { +int64_t Image::get_image_mipmap_offset(int p_width, int p_height, Format p_format, int p_mipmap) { if (p_mipmap <= 0) { return 0; } @@ -2605,7 +2691,7 @@ int Image::get_image_mipmap_offset(int p_width, int p_height, Format p_format, i return _get_dst_image_size(p_width, p_height, p_format, mm, p_mipmap - 1); } -int Image::get_image_mipmap_offset_and_dimensions(int p_width, int p_height, Format p_format, int p_mipmap, int &r_w, int &r_h) { +int64_t Image::get_image_mipmap_offset_and_dimensions(int p_width, int p_height, Format p_format, int p_mipmap, int &r_w, int &r_h) { if (p_mipmap <= 0) { r_w = p_width; r_h = p_height; @@ -2649,6 +2735,27 @@ Error Image::compress(CompressMode p_mode, CompressSource p_source, ASTCFormat p Error Image::compress_from_channels(CompressMode p_mode, UsedChannels p_channels, ASTCFormat p_astc_format) { ERR_FAIL_COND_V(data.is_empty(), ERR_INVALID_DATA); + // RenderingDevice only. + if (GLOBAL_GET("rendering/textures/vram_compression/compress_with_gpu")) { + switch (p_mode) { + case COMPRESS_BPTC: { + // BC7 is unsupported currently. + if ((format >= FORMAT_RF && format <= FORMAT_RGBE9995) && _image_compress_bptc_rd_func) { + Error result = _image_compress_bptc_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: { + } + } + } + switch (p_mode) { case COMPRESS_S3TC: { ERR_FAIL_NULL_V(_image_compress_bc_func, ERR_UNAVAILABLE); @@ -3030,6 +3137,7 @@ void (*Image::_image_compress_bptc_func)(Image *, Image::UsedChannels) = nullptr 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; void (*Image::_image_decompress_bc)(Image *) = nullptr; void (*Image::_image_decompress_bptc)(Image *) = nullptr; void (*Image::_image_decompress_etc1)(Image *) = nullptr; @@ -3479,6 +3587,7 @@ void Image::_bind_methods() { ClassDB::bind_method(D_METHOD("fix_alpha_edges"), &Image::fix_alpha_edges); ClassDB::bind_method(D_METHOD("premultiply_alpha"), &Image::premultiply_alpha); ClassDB::bind_method(D_METHOD("srgb_to_linear"), &Image::srgb_to_linear); + ClassDB::bind_method(D_METHOD("linear_to_srgb"), &Image::linear_to_srgb); ClassDB::bind_method(D_METHOD("normal_map_to_xy"), &Image::normal_map_to_xy); ClassDB::bind_method(D_METHOD("rgbe_to_srgb"), &Image::rgbe_to_srgb); ClassDB::bind_method(D_METHOD("bump_map_to_normal_map", "bump_scale"), &Image::bump_map_to_normal_map, DEFVAL(1.0)); @@ -3642,9 +3751,10 @@ Ref<Image> Image::rgbe_to_srgb() { return new_image; } -Ref<Image> Image::get_image_from_mipmap(int p_mipamp) const { - int ofs, size, w, h; - get_mipmap_offset_size_and_dimensions(p_mipamp, ofs, size, w, h); +Ref<Image> Image::get_image_from_mipmap(int p_mipmap) const { + int64_t ofs, size; + int w, h; + get_mipmap_offset_size_and_dimensions(p_mipmap, ofs, size, w, h); Vector<uint8_t> new_data; new_data.resize(size); @@ -3714,6 +3824,33 @@ void Image::bump_map_to_normal_map(float bump_scale) { data = result_image; } +bool Image::detect_signed(bool p_include_mips) const { + ERR_FAIL_COND_V(is_compressed(), false); + + if (format >= Image::FORMAT_RH && format <= Image::FORMAT_RGBAH) { + const uint16_t *img_data = reinterpret_cast<const uint16_t *>(data.ptr()); + const uint64_t img_size = p_include_mips ? (data.size() / 2) : (width * height * get_format_pixel_size(format) / 2); + + for (uint64_t i = 0; i < img_size; i++) { + if ((img_data[i] & 0x8000) != 0 && (img_data[i] & 0x7fff) != 0) { + return true; + } + } + + } else if (format >= Image::FORMAT_RF && format <= Image::FORMAT_RGBAF) { + const uint32_t *img_data = reinterpret_cast<const uint32_t *>(data.ptr()); + const uint64_t img_size = p_include_mips ? (data.size() / 4) : (width * height * get_format_pixel_size(format) / 4); + + for (uint64_t i = 0; i < img_size; i++) { + if ((img_data[i] & 0x80000000) != 0 && (img_data[i] & 0x7fffffff) != 0) { + return true; + } + } + } + + return false; +} + void Image::srgb_to_linear() { if (data.size() == 0) { return; @@ -3745,6 +3882,37 @@ void Image::srgb_to_linear() { } } +void Image::linear_to_srgb() { + if (data.size() == 0) { + return; + } + + static const uint8_t lin2srgb[256] = { 0, 12, 21, 28, 33, 38, 42, 46, 49, 52, 55, 58, 61, 63, 66, 68, 70, 73, 75, 77, 79, 81, 82, 84, 86, 88, 89, 91, 93, 94, 96, 97, 99, 100, 102, 103, 104, 106, 107, 109, 110, 111, 112, 114, 115, 116, 117, 118, 120, 121, 122, 123, 124, 125, 126, 127, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 151, 152, 153, 154, 155, 156, 157, 157, 158, 159, 160, 161, 161, 162, 163, 164, 165, 165, 166, 167, 168, 168, 169, 170, 171, 171, 172, 173, 174, 174, 175, 176, 176, 177, 178, 179, 179, 180, 181, 181, 182, 183, 183, 184, 185, 185, 186, 187, 187, 188, 189, 189, 190, 191, 191, 192, 193, 193, 194, 194, 195, 196, 196, 197, 197, 198, 199, 199, 200, 201, 201, 202, 202, 203, 204, 204, 205, 205, 206, 206, 207, 208, 208, 209, 209, 210, 210, 211, 212, 212, 213, 213, 214, 214, 215, 215, 216, 217, 217, 218, 218, 219, 219, 220, 220, 221, 221, 222, 222, 223, 223, 224, 224, 225, 226, 226, 227, 227, 228, 228, 229, 229, 230, 230, 231, 231, 232, 232, 233, 233, 234, 234, 235, 235, 236, 236, 237, 237, 237, 238, 238, 239, 239, 240, 240, 241, 241, 242, 242, 243, 243, 244, 244, 245, 245, 245, 246, 246, 247, 247, 248, 248, 249, 249, 250, 250, 251, 251, 251, 252, 252, 253, 253, 254, 254, 255 }; + + ERR_FAIL_COND(format != FORMAT_RGB8 && format != FORMAT_RGBA8); + + if (format == FORMAT_RGBA8) { + int len = data.size() / 4; + uint8_t *data_ptr = data.ptrw(); + + for (int i = 0; i < len; i++) { + data_ptr[(i << 2) + 0] = lin2srgb[data_ptr[(i << 2) + 0]]; + data_ptr[(i << 2) + 1] = lin2srgb[data_ptr[(i << 2) + 1]]; + data_ptr[(i << 2) + 2] = lin2srgb[data_ptr[(i << 2) + 2]]; + } + + } else if (format == FORMAT_RGB8) { + int len = data.size() / 3; + uint8_t *data_ptr = data.ptrw(); + + for (int i = 0; i < len; i++) { + data_ptr[(i * 3) + 0] = lin2srgb[data_ptr[(i * 3) + 0]]; + data_ptr[(i * 3) + 1] = lin2srgb[data_ptr[(i * 3) + 1]]; + data_ptr[(i * 3) + 2] = lin2srgb[data_ptr[(i * 3) + 2]]; + } + } +} + void Image::premultiply_alpha() { if (data.size() == 0) { return; @@ -4057,7 +4225,7 @@ Dictionary Image::compute_image_metrics(const Ref<Image> p_compared_image, bool result["root_mean_squared"] = INFINITY; result["peak_snr"] = 0.0f; - ERR_FAIL_NULL_V(p_compared_image, result); + ERR_FAIL_COND_V(p_compared_image.is_null(), result); Error err = OK; Ref<Image> compared_image = duplicate(true); if (compared_image->is_compressed()) { diff --git a/core/io/image.h b/core/io/image.h index d3ae99954f..4461ae71a6 100644 --- a/core/io/image.h +++ b/core/io/image.h @@ -159,6 +159,8 @@ public: static void (*_image_compress_etc2_func)(Image *, UsedChannels p_channels); static void (*_image_compress_astc_func)(Image *, ASTCFormat p_format); + static Error (*_image_compress_bptc_rd_func)(Image *, UsedChannels p_channels); + static void (*_image_decompress_bc)(Image *); static void (*_image_decompress_bptc)(Image *); static void (*_image_decompress_etc1)(Image *); @@ -195,9 +197,9 @@ private: data = p_image.data; } - _FORCE_INLINE_ void _get_mipmap_offset_and_size(int p_mipmap, int &r_offset, int &r_width, int &r_height) const; //get where the mipmap begins in data + _FORCE_INLINE_ void _get_mipmap_offset_and_size(int p_mipmap, int64_t &r_offset, int &r_width, int &r_height) const; //get where the mipmap begins in data - static int _get_dst_image_size(int p_width, int p_height, Format p_format, int &r_mipmaps, int p_mipmaps = -1, int *r_mm_width = nullptr, int *r_mm_height = nullptr); + static int64_t _get_dst_image_size(int p_width, int p_height, Format p_format, int &r_mipmaps, int p_mipmaps = -1, int *r_mm_width = nullptr, int *r_mm_height = nullptr); bool _can_modify(Format p_format) const; _FORCE_INLINE_ void _get_clipped_src_and_dest_rects(const Ref<Image> &p_src, const Rect2i &p_src_rect, const Point2i &p_dest, Rect2i &r_clipped_src_rect, Rect2i &r_clipped_dest_rect) const; @@ -238,10 +240,12 @@ public: */ Format get_format() const; - int get_mipmap_byte_size(int p_mipmap) const; //get where the mipmap begins in data - int get_mipmap_offset(int p_mipmap) const; //get where the mipmap begins in data - void get_mipmap_offset_and_size(int p_mipmap, int &r_ofs, int &r_size) const; //get where the mipmap begins in data - void get_mipmap_offset_size_and_dimensions(int p_mipmap, int &r_ofs, int &r_size, int &w, int &h) const; //get where the mipmap begins in data + /** + * Get where the mipmap begins in data. + */ + int64_t get_mipmap_offset(int p_mipmap) const; + void get_mipmap_offset_and_size(int p_mipmap, int64_t &r_ofs, int64_t &r_size) const; + void get_mipmap_offset_size_and_dimensions(int p_mipmap, int64_t &r_ofs, int64_t &r_size, int &w, int &h) const; enum Image3DValidateError { VALIDATE_3D_OK, @@ -351,11 +355,11 @@ public: static int get_format_block_size(Format p_format); static void get_format_min_pixel_size(Format p_format, int &r_w, int &r_h); - static int get_image_data_size(int p_width, int p_height, Format p_format, bool p_mipmaps = false); + static int64_t get_image_data_size(int p_width, int p_height, Format p_format, bool p_mipmaps = false); static int get_image_required_mipmaps(int p_width, int p_height, Format p_format); static Size2i get_image_mipmap_size(int p_width, int p_height, Format p_format, int p_mipmap); - static int get_image_mipmap_offset(int p_width, int p_height, Format p_format, int p_mipmap); - static int get_image_mipmap_offset_and_dimensions(int p_width, int p_height, Format p_format, int p_mipmap, int &r_w, int &r_h); + static int64_t get_image_mipmap_offset(int p_width, int p_height, Format p_format, int p_mipmap); + static int64_t get_image_mipmap_offset_and_dimensions(int p_width, int p_height, Format p_format, int p_mipmap, int &r_w, int &r_h); enum CompressMode { COMPRESS_S3TC, @@ -381,11 +385,14 @@ public: void fix_alpha_edges(); void premultiply_alpha(); void srgb_to_linear(); + void linear_to_srgb(); void normal_map_to_xy(); Ref<Image> rgbe_to_srgb(); - Ref<Image> get_image_from_mipmap(int p_mipamp) const; + Ref<Image> get_image_from_mipmap(int p_mipmap) const; void bump_map_to_normal_map(float bump_scale = 1.0); + bool detect_signed(bool p_include_mips = true) const; + void blit_rect(const Ref<Image> &p_src, const Rect2i &p_src_rect, const Point2i &p_dest); void blit_rect_mask(const Ref<Image> &p_src, const Ref<Image> &p_mask, const Rect2i &p_src_rect, const Point2i &p_dest); void blend_rect(const Ref<Image> &p_src, const Rect2i &p_src_rect, const Point2i &p_dest); diff --git a/core/io/ip.cpp b/core/io/ip.cpp index f20d65bef9..38c71b19fa 100644 --- a/core/io/ip.cpp +++ b/core/io/ip.cpp @@ -81,17 +81,17 @@ struct _IP_ResolverPrivate { continue; } - mutex.lock(); + MutexLock lock(mutex); List<IPAddress> response; String hostname = queue[i].hostname; IP::Type type = queue[i].type; - mutex.unlock(); + lock.temp_unlock(); // We should not lock while resolving the hostname, // only when modifying the queue. IP::get_singleton()->_resolve_hostname(response, hostname, type); - MutexLock lock(mutex); + lock.temp_relock(); // Could have been completed by another function, or deleted. if (queue[i].status.get() != IP::RESOLVER_STATUS_WAITING) { continue; @@ -131,21 +131,22 @@ PackedStringArray IP::resolve_hostname_addresses(const String &p_hostname, Type List<IPAddress> res; String key = _IP_ResolverPrivate::get_cache_key(p_hostname, p_type); - resolver->mutex.lock(); - if (resolver->cache.has(key)) { - res = resolver->cache[key]; - } else { - // This should be run unlocked so the resolver thread can keep resolving - // other requests. - resolver->mutex.unlock(); - _resolve_hostname(res, p_hostname, p_type); - resolver->mutex.lock(); - // We might be overriding another result, but we don't care as long as the result is valid. - if (res.size()) { - resolver->cache[key] = res; + { + MutexLock lock(resolver->mutex); + if (resolver->cache.has(key)) { + res = resolver->cache[key]; + } else { + // This should be run unlocked so the resolver thread can keep resolving + // other requests. + lock.temp_unlock(); + _resolve_hostname(res, p_hostname, p_type); + lock.temp_relock(); + // We might be overriding another result, but we don't care as long as the result is valid. + if (res.size()) { + resolver->cache[key] = res; + } } } - resolver->mutex.unlock(); PackedStringArray result; for (const IPAddress &E : res) { diff --git a/core/io/json.cpp b/core/io/json.cpp index 61051727c1..664ff7857b 100644 --- a/core/io/json.cpp +++ b/core/io/json.cpp @@ -588,10 +588,756 @@ void JSON::_bind_methods() { ClassDB::bind_method(D_METHOD("get_error_line"), &JSON::get_error_line); ClassDB::bind_method(D_METHOD("get_error_message"), &JSON::get_error_message); + ClassDB::bind_static_method("JSON", D_METHOD("to_native", "json", "allow_classes", "allow_scripts"), &JSON::to_native, DEFVAL(false), DEFVAL(false)); + ClassDB::bind_static_method("JSON", D_METHOD("from_native", "variant", "allow_classes", "allow_scripts"), &JSON::from_native, DEFVAL(false), DEFVAL(false)); + ADD_PROPERTY(PropertyInfo(Variant::NIL, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT), "set_data", "get_data"); // Ensures that it can be serialized as binary. } -//// +#define GDTYPE "__gdtype" +#define VALUES "values" +#define PASS_ARG p_allow_classes, p_allow_scripts + +Variant JSON::from_native(const Variant &p_variant, bool p_allow_classes, bool p_allow_scripts) { + switch (p_variant.get_type()) { + case Variant::NIL: { + Dictionary nil; + nil[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return nil; + } break; + case Variant::BOOL: { + return p_variant; + } break; + case Variant::INT: { + return p_variant; + } break; + case Variant::FLOAT: { + return p_variant; + } break; + case Variant::STRING: { + return p_variant; + } break; + case Variant::VECTOR2: { + Dictionary d; + Vector2 v = p_variant; + Array values; + values.push_back(v.x); + values.push_back(v.y); + d[VALUES] = values; + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::VECTOR2I: { + Dictionary d; + Vector2i v = p_variant; + Array values; + values.push_back(v.x); + values.push_back(v.y); + d[VALUES] = values; + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::RECT2: { + Dictionary d; + Rect2 r = p_variant; + d["position"] = from_native(r.position); + d["size"] = from_native(r.size); + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::RECT2I: { + Dictionary d; + Rect2i r = p_variant; + d["position"] = from_native(r.position); + d["size"] = from_native(r.size); + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::VECTOR3: { + Dictionary d; + Vector3 v = p_variant; + Array values; + values.push_back(v.x); + values.push_back(v.y); + values.push_back(v.z); + d[VALUES] = values; + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::VECTOR3I: { + Dictionary d; + Vector3i v = p_variant; + Array values; + values.push_back(v.x); + values.push_back(v.y); + values.push_back(v.z); + d[VALUES] = values; + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::TRANSFORM2D: { + Dictionary d; + Transform2D t = p_variant; + d["x"] = from_native(t[0]); + d["y"] = from_native(t[1]); + d["origin"] = from_native(t[2]); + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::VECTOR4: { + Dictionary d; + Vector4 v = p_variant; + Array values; + values.push_back(v.x); + values.push_back(v.y); + values.push_back(v.z); + values.push_back(v.w); + d[VALUES] = values; + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::VECTOR4I: { + Dictionary d; + Vector4i v = p_variant; + Array values; + values.push_back(v.x); + values.push_back(v.y); + values.push_back(v.z); + values.push_back(v.w); + d[VALUES] = values; + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::PLANE: { + Dictionary d; + Plane p = p_variant; + d["normal"] = from_native(p.normal); + d["d"] = p.d; + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::QUATERNION: { + Dictionary d; + Quaternion q = p_variant; + Array values; + values.push_back(q.x); + values.push_back(q.y); + values.push_back(q.z); + values.push_back(q.w); + d[VALUES] = values; + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::AABB: { + Dictionary d; + AABB aabb = p_variant; + d["position"] = from_native(aabb.position); + d["size"] = from_native(aabb.size); + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::BASIS: { + Dictionary d; + Basis t = p_variant; + d["x"] = from_native(t.get_column(0)); + d["y"] = from_native(t.get_column(1)); + d["z"] = from_native(t.get_column(2)); + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::TRANSFORM3D: { + Dictionary d; + Transform3D t = p_variant; + d["basis"] = from_native(t.basis); + d["origin"] = from_native(t.origin); + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::PROJECTION: { + Dictionary d; + Projection t = p_variant; + d["x"] = from_native(t[0]); + d["y"] = from_native(t[1]); + d["z"] = from_native(t[2]); + d["w"] = from_native(t[3]); + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::COLOR: { + Dictionary d; + Color c = p_variant; + Array values; + values.push_back(c.r); + values.push_back(c.g); + values.push_back(c.b); + values.push_back(c.a); + d[VALUES] = values; + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::STRING_NAME: { + Dictionary d; + d["name"] = String(p_variant); + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::NODE_PATH: { + Dictionary d; + d["path"] = String(p_variant); + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::RID: { + Dictionary d; + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::OBJECT: { + Object *obj = p_variant.get_validated_object(); + + if (p_allow_classes && obj) { + Dictionary d; + List<PropertyInfo> property_list; + obj->get_property_list(&property_list); + + d["type"] = obj->get_class(); + Dictionary p; + for (const PropertyInfo &P : property_list) { + if (P.usage & PROPERTY_USAGE_STORAGE) { + if (P.name == "script" && !p_allow_scripts) { + continue; + } + p[P.name] = from_native(obj->get(P.name), PASS_ARG); + } + } + d["properties"] = p; + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } else { + Dictionary nil; + nil[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return nil; + } + } break; + case Variant::CALLABLE: + case Variant::SIGNAL: { + Dictionary nil; + nil[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return nil; + } break; + case Variant::DICTIONARY: { + Dictionary d = p_variant; + List<Variant> keys; + d.get_key_list(&keys); + bool all_strings = true; + for (const Variant &K : keys) { + if (K.get_type() != Variant::STRING) { + all_strings = false; + break; + } + } + + if (all_strings) { + Dictionary ret_dict; + for (const Variant &K : keys) { + ret_dict[K] = from_native(d[K], PASS_ARG); + } + return ret_dict; + } else { + Dictionary ret; + Array pairs; + for (const Variant &K : keys) { + Dictionary pair; + pair["key"] = from_native(K, PASS_ARG); + pair["value"] = from_native(d[K], PASS_ARG); + pairs.push_back(pair); + } + ret["pairs"] = pairs; + ret[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return ret; + } + } break; + case Variant::ARRAY: { + Array arr = p_variant; + Array ret; + for (int i = 0; i < arr.size(); i++) { + ret.push_back(from_native(arr[i], PASS_ARG)); + } + return ret; + } break; + case Variant::PACKED_BYTE_ARRAY: { + Dictionary d; + PackedByteArray arr = p_variant; + Array values; + for (int i = 0; i < arr.size(); i++) { + values.push_back(arr[i]); + } + d[VALUES] = values; + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::PACKED_INT32_ARRAY: { + Dictionary d; + PackedInt32Array arr = p_variant; + Array values; + for (int i = 0; i < arr.size(); i++) { + values.push_back(arr[i]); + } + d[VALUES] = values; + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + + } break; + case Variant::PACKED_INT64_ARRAY: { + Dictionary d; + PackedInt64Array arr = p_variant; + Array values; + for (int i = 0; i < arr.size(); i++) { + values.push_back(arr[i]); + } + d[VALUES] = values; + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::PACKED_FLOAT32_ARRAY: { + Dictionary d; + PackedFloat32Array arr = p_variant; + Array values; + for (int i = 0; i < arr.size(); i++) { + values.push_back(arr[i]); + } + d[VALUES] = values; + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::PACKED_FLOAT64_ARRAY: { + Dictionary d; + PackedFloat64Array arr = p_variant; + Array values; + for (int i = 0; i < arr.size(); i++) { + values.push_back(arr[i]); + } + d[VALUES] = values; + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::PACKED_STRING_ARRAY: { + Dictionary d; + PackedStringArray arr = p_variant; + Array values; + for (int i = 0; i < arr.size(); i++) { + values.push_back(arr[i]); + } + d[VALUES] = values; + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::PACKED_VECTOR2_ARRAY: { + Dictionary d; + PackedVector2Array arr = p_variant; + Array values; + for (int i = 0; i < arr.size(); i++) { + Vector2 v = arr[i]; + values.push_back(v.x); + values.push_back(v.y); + } + d[VALUES] = values; + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::PACKED_VECTOR3_ARRAY: { + Dictionary d; + PackedVector3Array arr = p_variant; + Array values; + for (int i = 0; i < arr.size(); i++) { + Vector3 v = arr[i]; + values.push_back(v.x); + values.push_back(v.y); + values.push_back(v.z); + } + d[VALUES] = values; + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::PACKED_COLOR_ARRAY: { + Dictionary d; + PackedColorArray arr = p_variant; + Array values; + for (int i = 0; i < arr.size(); i++) { + Color v = arr[i]; + values.push_back(v.r); + values.push_back(v.g); + values.push_back(v.b); + values.push_back(v.a); + } + d[VALUES] = values; + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::PACKED_VECTOR4_ARRAY: { + Dictionary d; + PackedVector4Array arr = p_variant; + Array values; + for (int i = 0; i < arr.size(); i++) { + Vector4 v = arr[i]; + values.push_back(v.x); + values.push_back(v.y); + values.push_back(v.z); + values.push_back(v.w); + } + d[VALUES] = values; + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + default: { + ERR_PRINT(vformat("Unhandled conversion from native Variant type '%s' to JSON.", Variant::get_type_name(p_variant.get_type()))); + } break; + } + + Dictionary nil; + nil[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return nil; +} + +Variant JSON::to_native(const Variant &p_json, bool p_allow_classes, bool p_allow_scripts) { + switch (p_json.get_type()) { + case Variant::BOOL: { + return p_json; + } break; + case Variant::INT: { + return p_json; + } break; + case Variant::FLOAT: { + return p_json; + } break; + case Variant::STRING: { + return p_json; + } break; + case Variant::STRING_NAME: { + return p_json; + } break; + case Variant::CALLABLE: { + return p_json; + } break; + case Variant::DICTIONARY: { + Dictionary d = p_json; + if (d.has(GDTYPE)) { + // Specific Godot Variant types serialized to JSON. + String type = d[GDTYPE]; + if (type == Variant::get_type_name(Variant::VECTOR2)) { + ERR_FAIL_COND_V(!d.has(VALUES), Variant()); + Array values = d[VALUES]; + ERR_FAIL_COND_V(values.size() != 2, Variant()); + Vector2 v; + v.x = values[0]; + v.y = values[1]; + return v; + } else if (type == Variant::get_type_name(Variant::VECTOR2I)) { + ERR_FAIL_COND_V(!d.has(VALUES), Variant()); + Array values = d[VALUES]; + ERR_FAIL_COND_V(values.size() != 2, Variant()); + Vector2i v; + v.x = values[0]; + v.y = values[1]; + return v; + } else if (type == Variant::get_type_name(Variant::RECT2)) { + ERR_FAIL_COND_V(!d.has("position"), Variant()); + ERR_FAIL_COND_V(!d.has("size"), Variant()); + Rect2 r; + r.position = to_native(d["position"]); + r.size = to_native(d["size"]); + return r; + } else if (type == Variant::get_type_name(Variant::RECT2I)) { + ERR_FAIL_COND_V(!d.has("position"), Variant()); + ERR_FAIL_COND_V(!d.has("size"), Variant()); + Rect2i r; + r.position = to_native(d["position"]); + r.size = to_native(d["size"]); + return r; + } else if (type == Variant::get_type_name(Variant::VECTOR3)) { + ERR_FAIL_COND_V(!d.has(VALUES), Variant()); + Array values = d[VALUES]; + ERR_FAIL_COND_V(values.size() != 3, Variant()); + Vector3 v; + v.x = values[0]; + v.y = values[1]; + v.z = values[2]; + return v; + } else if (type == Variant::get_type_name(Variant::VECTOR3I)) { + ERR_FAIL_COND_V(!d.has(VALUES), Variant()); + Array values = d[VALUES]; + ERR_FAIL_COND_V(values.size() != 3, Variant()); + Vector3i v; + v.x = values[0]; + v.y = values[1]; + v.z = values[2]; + return v; + } else if (type == Variant::get_type_name(Variant::TRANSFORM2D)) { + ERR_FAIL_COND_V(!d.has("x"), Variant()); + ERR_FAIL_COND_V(!d.has("y"), Variant()); + ERR_FAIL_COND_V(!d.has("origin"), Variant()); + Transform2D t; + t[0] = to_native(d["x"]); + t[1] = to_native(d["y"]); + t[2] = to_native(d["origin"]); + return t; + } else if (type == Variant::get_type_name(Variant::VECTOR4)) { + ERR_FAIL_COND_V(!d.has(VALUES), Variant()); + Array values = d[VALUES]; + ERR_FAIL_COND_V(values.size() != 4, Variant()); + Vector4 v; + v.x = values[0]; + v.y = values[1]; + v.z = values[2]; + v.w = values[3]; + return v; + } else if (type == Variant::get_type_name(Variant::VECTOR4I)) { + ERR_FAIL_COND_V(!d.has(VALUES), Variant()); + Array values = d[VALUES]; + ERR_FAIL_COND_V(values.size() != 4, Variant()); + Vector4i v; + v.x = values[0]; + v.y = values[1]; + v.z = values[2]; + v.w = values[3]; + return v; + } else if (type == Variant::get_type_name(Variant::PLANE)) { + ERR_FAIL_COND_V(!d.has("normal"), Variant()); + ERR_FAIL_COND_V(!d.has("d"), Variant()); + Plane p; + p.normal = to_native(d["normal"]); + p.d = d["d"]; + return p; + } else if (type == Variant::get_type_name(Variant::QUATERNION)) { + ERR_FAIL_COND_V(!d.has(VALUES), Variant()); + Array values = d[VALUES]; + ERR_FAIL_COND_V(values.size() != 4, Variant()); + Quaternion v; + v.x = values[0]; + v.y = values[1]; + v.z = values[2]; + v.w = values[3]; + return v; + } else if (type == Variant::get_type_name(Variant::AABB)) { + ERR_FAIL_COND_V(!d.has("position"), Variant()); + ERR_FAIL_COND_V(!d.has("size"), Variant()); + AABB r; + r.position = to_native(d["position"]); + r.size = to_native(d["size"]); + return r; + } else if (type == Variant::get_type_name(Variant::BASIS)) { + ERR_FAIL_COND_V(!d.has("x"), Variant()); + ERR_FAIL_COND_V(!d.has("y"), Variant()); + ERR_FAIL_COND_V(!d.has("z"), Variant()); + Basis b; + b.set_column(0, to_native(d["x"])); + b.set_column(1, to_native(d["y"])); + b.set_column(2, to_native(d["z"])); + return b; + } else if (type == Variant::get_type_name(Variant::TRANSFORM3D)) { + ERR_FAIL_COND_V(!d.has("basis"), Variant()); + ERR_FAIL_COND_V(!d.has("origin"), Variant()); + Transform3D t; + t.basis = to_native(d["basis"]); + t.origin = to_native(d["origin"]); + return t; + } else if (type == Variant::get_type_name(Variant::PROJECTION)) { + ERR_FAIL_COND_V(!d.has("x"), Variant()); + ERR_FAIL_COND_V(!d.has("y"), Variant()); + ERR_FAIL_COND_V(!d.has("z"), Variant()); + ERR_FAIL_COND_V(!d.has("w"), Variant()); + Projection p; + p[0] = to_native(d["x"]); + p[1] = to_native(d["y"]); + p[2] = to_native(d["z"]); + p[3] = to_native(d["w"]); + return p; + } else if (type == Variant::get_type_name(Variant::COLOR)) { + ERR_FAIL_COND_V(!d.has(VALUES), Variant()); + Array values = d[VALUES]; + ERR_FAIL_COND_V(values.size() != 4, Variant()); + Color c; + c.r = values[0]; + c.g = values[1]; + c.b = values[2]; + c.a = values[3]; + return c; + } else if (type == Variant::get_type_name(Variant::NODE_PATH)) { + ERR_FAIL_COND_V(!d.has("path"), Variant()); + NodePath np = d["path"]; + return np; + } else if (type == Variant::get_type_name(Variant::STRING_NAME)) { + ERR_FAIL_COND_V(!d.has("name"), Variant()); + StringName s = d["name"]; + return s; + } else if (type == Variant::get_type_name(Variant::OBJECT)) { + ERR_FAIL_COND_V(!d.has("type"), Variant()); + ERR_FAIL_COND_V(!d.has("properties"), Variant()); + + ERR_FAIL_COND_V(!p_allow_classes, Variant()); + + String obj_type = d["type"]; + bool is_script = obj_type == "Script" || ClassDB::is_parent_class(obj_type, "Script"); + ERR_FAIL_COND_V(!p_allow_scripts && is_script, Variant()); + Object *obj = ClassDB::instantiate(obj_type); + ERR_FAIL_NULL_V(obj, Variant()); + + Dictionary p = d["properties"]; + + List<Variant> keys; + p.get_key_list(&keys); + + for (const Variant &K : keys) { + String property = K; + Variant value = to_native(p[K], PASS_ARG); + obj->set(property, value); + } + + Variant v(obj); + + return v; + } else if (type == Variant::get_type_name(Variant::DICTIONARY)) { + ERR_FAIL_COND_V(!d.has("pairs"), Variant()); + Array pairs = d["pairs"]; + Dictionary r; + for (int i = 0; i < pairs.size(); i++) { + Dictionary p = pairs[i]; + ERR_CONTINUE(!p.has("key")); + ERR_CONTINUE(!p.has("value")); + r[to_native(p["key"], PASS_ARG)] = to_native(p["value"]); + } + return r; + } else if (type == Variant::get_type_name(Variant::ARRAY)) { + ERR_PRINT(vformat("Unexpected Array with '%s' key. Arrays are supported natively.", GDTYPE)); + } else if (type == Variant::get_type_name(Variant::PACKED_BYTE_ARRAY)) { + ERR_FAIL_COND_V(!d.has(VALUES), Variant()); + Array values = d[VALUES]; + PackedByteArray pbarr; + pbarr.resize(values.size()); + for (int i = 0; i < pbarr.size(); i++) { + pbarr.write[i] = values[i]; + } + return pbarr; + } else if (type == Variant::get_type_name(Variant::PACKED_INT32_ARRAY)) { + ERR_FAIL_COND_V(!d.has(VALUES), Variant()); + Array values = d[VALUES]; + PackedInt32Array arr; + arr.resize(values.size()); + for (int i = 0; i < arr.size(); i++) { + arr.write[i] = values[i]; + } + return arr; + } else if (type == Variant::get_type_name(Variant::PACKED_INT64_ARRAY)) { + ERR_FAIL_COND_V(!d.has(VALUES), Variant()); + Array values = d[VALUES]; + PackedInt64Array arr; + arr.resize(values.size()); + for (int i = 0; i < arr.size(); i++) { + arr.write[i] = values[i]; + } + return arr; + } else if (type == Variant::get_type_name(Variant::PACKED_FLOAT32_ARRAY)) { + ERR_FAIL_COND_V(!d.has(VALUES), Variant()); + Array values = d[VALUES]; + PackedFloat32Array arr; + arr.resize(values.size()); + for (int i = 0; i < arr.size(); i++) { + arr.write[i] = values[i]; + } + return arr; + } else if (type == Variant::get_type_name(Variant::PACKED_FLOAT64_ARRAY)) { + ERR_FAIL_COND_V(!d.has(VALUES), Variant()); + Array values = d[VALUES]; + PackedFloat64Array arr; + arr.resize(values.size()); + for (int i = 0; i < arr.size(); i++) { + arr.write[i] = values[i]; + } + return arr; + } else if (type == Variant::get_type_name(Variant::PACKED_STRING_ARRAY)) { + ERR_FAIL_COND_V(!d.has(VALUES), Variant()); + Array values = d[VALUES]; + PackedStringArray arr; + arr.resize(values.size()); + for (int i = 0; i < arr.size(); i++) { + arr.write[i] = values[i]; + } + return arr; + } else if (type == Variant::get_type_name(Variant::PACKED_VECTOR2_ARRAY)) { + ERR_FAIL_COND_V(!d.has(VALUES), Variant()); + Array values = d[VALUES]; + ERR_FAIL_COND_V(values.size() % 2 != 0, Variant()); + PackedVector2Array arr; + arr.resize(values.size() / 2); + for (int i = 0; i < arr.size(); i++) { + arr.write[i] = Vector2(values[i * 2 + 0], values[i * 2 + 1]); + } + return arr; + } else if (type == Variant::get_type_name(Variant::PACKED_VECTOR3_ARRAY)) { + ERR_FAIL_COND_V(!d.has(VALUES), Variant()); + Array values = d[VALUES]; + ERR_FAIL_COND_V(values.size() % 3 != 0, Variant()); + PackedVector3Array arr; + arr.resize(values.size() / 3); + for (int i = 0; i < arr.size(); i++) { + arr.write[i] = Vector3(values[i * 3 + 0], values[i * 3 + 1], values[i * 3 + 2]); + } + return arr; + } else if (type == Variant::get_type_name(Variant::PACKED_COLOR_ARRAY)) { + ERR_FAIL_COND_V(!d.has(VALUES), Variant()); + Array values = d[VALUES]; + ERR_FAIL_COND_V(values.size() % 4 != 0, Variant()); + PackedColorArray arr; + arr.resize(values.size() / 4); + for (int i = 0; i < arr.size(); i++) { + arr.write[i] = Color(values[i * 4 + 0], values[i * 4 + 1], values[i * 4 + 2], values[i * 4 + 3]); + } + return arr; + } else if (type == Variant::get_type_name(Variant::PACKED_VECTOR4_ARRAY)) { + ERR_FAIL_COND_V(!d.has(VALUES), Variant()); + Array values = d[VALUES]; + ERR_FAIL_COND_V(values.size() % 4 != 0, Variant()); + PackedVector4Array arr; + arr.resize(values.size() / 4); + for (int i = 0; i < arr.size(); i++) { + arr.write[i] = Vector4(values[i * 4 + 0], values[i * 4 + 1], values[i * 4 + 2], values[i * 4 + 3]); + } + return arr; + } else { + return Variant(); + } + } else { + // Regular dictionary with string keys. + List<Variant> keys; + d.get_key_list(&keys); + Dictionary r; + for (const Variant &K : keys) { + r[K] = to_native(d[K], PASS_ARG); + } + return r; + } + } break; + case Variant::ARRAY: { + Array arr = p_json; + Array ret; + ret.resize(arr.size()); + for (int i = 0; i < arr.size(); i++) { + ret[i] = to_native(arr[i], PASS_ARG); + } + return ret; + } break; + default: { + ERR_PRINT(vformat("Unhandled conversion from JSON type '%s' to native Variant type.", Variant::get_type_name(p_json.get_type()))); + return Variant(); + } + } + + return Variant(); +} + +#undef GDTYPE +#undef VALUES +#undef PASS_ARG //////////// diff --git a/core/io/json.h b/core/io/json.h index 801fa29b4b..67b5e09afa 100644 --- a/core/io/json.h +++ b/core/io/json.h @@ -94,6 +94,9 @@ public: void set_data(const Variant &p_data); inline int get_error_line() const { return err_line; } inline String get_error_message() const { return err_str; } + + static Variant from_native(const Variant &p_variant, bool p_allow_classes = false, bool p_allow_scripts = false); + static Variant to_native(const Variant &p_json, bool p_allow_classes = false, bool p_allow_scripts = false); }; class ResourceFormatLoaderJSON : public ResourceFormatLoader { diff --git a/core/io/logger.cpp b/core/io/logger.cpp index 1476b8ccac..26b60f6738 100644 --- a/core/io/logger.cpp +++ b/core/io/logger.cpp @@ -84,11 +84,7 @@ void Logger::log_error(const char *p_function, const char *p_file, int p_line, c err_details = p_code; } - if (p_editor_notify) { - logf_error("%s: %s\n", err_type, err_details); - } else { - logf_error("USER %s: %s\n", err_type, err_details); - } + logf_error("%s: %s\n", err_type, err_details); logf_error(" at: %s (%s:%i)\n", p_function, p_file, p_line); } @@ -212,7 +208,7 @@ void RotatedFileLogger::logv(const char *p_format, va_list p_list, bool p_err) { // Strip ANSI escape codes (such as those inserted by `print_rich()`) // before writing to file, as text editors cannot display those // correctly. - file->store_string(strip_ansi_regex->sub(String(buf), "", true)); + file->store_string(strip_ansi_regex->sub(String::utf8(buf), "", true)); #else file->store_buffer((uint8_t *)buf, len); #endif // MODULE_REGEX_ENABLED diff --git a/core/io/marshalls.cpp b/core/io/marshalls.cpp index c0d18d0120..67469de5cc 100644 --- a/core/io/marshalls.cpp +++ b/core/io/marshalls.cpp @@ -1315,10 +1315,12 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo if (array.is_typed()) { Ref<Script> script = array.get_typed_script(); if (script.is_valid()) { - header |= HEADER_DATA_FIELD_TYPED_ARRAY_SCRIPT; + header |= p_full_objects ? HEADER_DATA_FIELD_TYPED_ARRAY_SCRIPT : HEADER_DATA_FIELD_TYPED_ARRAY_CLASS_NAME; } else if (array.get_typed_class_name() != StringName()) { header |= HEADER_DATA_FIELD_TYPED_ARRAY_CLASS_NAME; } else { + // No need to check `p_full_objects` since for `Variant::OBJECT` + // `array.get_typed_class_name()` should be non-empty. header |= HEADER_DATA_FIELD_TYPED_ARRAY_BUILTIN; } } @@ -1783,12 +1785,18 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo Variant variant = array.get_typed_script(); Ref<Script> script = variant; if (script.is_valid()) { - String path = script->get_path(); - ERR_FAIL_COND_V_MSG(path.is_empty() || !path.begins_with("res://"), ERR_UNAVAILABLE, "Failed to encode a path to a custom script for an array type."); - _encode_string(path, buf, r_len); + if (p_full_objects) { + String path = script->get_path(); + ERR_FAIL_COND_V_MSG(path.is_empty() || !path.begins_with("res://"), ERR_UNAVAILABLE, "Failed to encode a path to a custom script for an array type."); + _encode_string(path, buf, r_len); + } else { + _encode_string(EncodedObjectAsID::get_class_static(), buf, r_len); + } } else if (array.get_typed_class_name() != StringName()) { - _encode_string(array.get_typed_class_name(), buf, r_len); + _encode_string(p_full_objects ? array.get_typed_class_name().operator String() : EncodedObjectAsID::get_class_static(), buf, r_len); } else { + // No need to check `p_full_objects` since for `Variant::OBJECT` + // `array.get_typed_class_name()` should be non-empty. if (buf) { encode_uint32(array.get_typed_builtin(), buf); buf += 4; diff --git a/core/io/packed_data_container.h b/core/io/packed_data_container.h index cc9996101e..f4ffa09022 100644 --- a/core/io/packed_data_container.h +++ b/core/io/packed_data_container.h @@ -36,7 +36,7 @@ class PackedDataContainer : public Resource { GDCLASS(PackedDataContainer, Resource); - enum { + enum : uint32_t { TYPE_DICT = 0xFFFFFFFF, TYPE_ARRAY = 0xFFFFFFFE, }; diff --git a/core/io/packet_peer_dtls.cpp b/core/io/packet_peer_dtls.cpp index 18bef3ff3c..231c48d887 100644 --- a/core/io/packet_peer_dtls.cpp +++ b/core/io/packet_peer_dtls.cpp @@ -32,12 +32,12 @@ #include "core/config/project_settings.h" #include "core/io/file_access.h" -PacketPeerDTLS *(*PacketPeerDTLS::_create)() = nullptr; +PacketPeerDTLS *(*PacketPeerDTLS::_create)(bool p_notify_postinitialize) = nullptr; bool PacketPeerDTLS::available = false; -PacketPeerDTLS *PacketPeerDTLS::create() { +PacketPeerDTLS *PacketPeerDTLS::create(bool p_notify_postinitialize) { if (_create) { - return _create(); + return _create(p_notify_postinitialize); } return nullptr; } diff --git a/core/io/packet_peer_dtls.h b/core/io/packet_peer_dtls.h index 3990a851f7..03d97a5903 100644 --- a/core/io/packet_peer_dtls.h +++ b/core/io/packet_peer_dtls.h @@ -38,7 +38,7 @@ class PacketPeerDTLS : public PacketPeer { GDCLASS(PacketPeerDTLS, PacketPeer); protected: - static PacketPeerDTLS *(*_create)(); + static PacketPeerDTLS *(*_create)(bool p_notify_postinitialize); static void _bind_methods(); static bool available; @@ -57,7 +57,7 @@ public: virtual void disconnect_from_peer() = 0; virtual Status get_status() const = 0; - static PacketPeerDTLS *create(); + static PacketPeerDTLS *create(bool p_notify_postinitialize = true); static bool is_available(); PacketPeerDTLS() {} diff --git a/core/io/packet_peer_udp.cpp b/core/io/packet_peer_udp.cpp index 32030146bb..fae3de2a98 100644 --- a/core/io/packet_peer_udp.cpp +++ b/core/io/packet_peer_udp.cpp @@ -106,7 +106,7 @@ Error PacketPeerUDP::get_packet(const uint8_t **r_buffer, int &r_buffer_size) { } uint32_t size = 0; - uint8_t ipv6[16]; + uint8_t ipv6[16] = {}; rb.read(ipv6, 16, true); packet_ip.set_ipv6(ipv6); rb.read((uint8_t *)&packet_port, 4, true); diff --git a/core/io/plist.cpp b/core/io/plist.cpp index 86737609bf..8d91e6dec2 100644 --- a/core/io/plist.cpp +++ b/core/io/plist.cpp @@ -814,7 +814,7 @@ bool PList::load_string(const String &p_string, String &r_err_out) { } PackedByteArray PList::save_asn1() const { - if (root == nullptr) { + if (root.is_null()) { ERR_FAIL_V_MSG(PackedByteArray(), "PList: Invalid PList, no root node."); } size_t size = root->get_asn1_size(1); @@ -848,7 +848,7 @@ PackedByteArray PList::save_asn1() const { } String PList::save_text() const { - if (root == nullptr) { + if (root.is_null()) { ERR_FAIL_V_MSG(String(), "PList: Invalid PList, no root node."); } diff --git a/core/io/resource.cpp b/core/io/resource.cpp index c045c0fc74..6177cba6a4 100644 --- a/core/io/resource.cpp +++ b/core/io/resource.cpp @@ -40,6 +40,11 @@ #include <stdio.h> void Resource::emit_changed() { + if (ResourceLoader::is_within_load() && !Thread::is_main_thread()) { + ResourceLoader::resource_changed_emit(this); + return; + } + emit_signal(CoreStringName(changed)); } @@ -55,32 +60,32 @@ void Resource::set_path(const String &p_path, bool p_take_over) { p_take_over = false; // Can't take over an empty path } - ResourceCache::lock.lock(); + { + MutexLock lock(ResourceCache::lock); - if (!path_cache.is_empty()) { - ResourceCache::resources.erase(path_cache); - } + if (!path_cache.is_empty()) { + ResourceCache::resources.erase(path_cache); + } - path_cache = ""; + path_cache = ""; - Ref<Resource> existing = ResourceCache::get_ref(p_path); + Ref<Resource> existing = ResourceCache::get_ref(p_path); - if (existing.is_valid()) { - if (p_take_over) { - existing->path_cache = String(); - ResourceCache::resources.erase(p_path); - } else { - ResourceCache::lock.unlock(); - ERR_FAIL_MSG("Another resource is loaded from path '" + p_path + "' (possible cyclic resource inclusion)."); + if (existing.is_valid()) { + if (p_take_over) { + existing->path_cache = String(); + ResourceCache::resources.erase(p_path); + } else { + ERR_FAIL_MSG("Another resource is loaded from path '" + p_path + "' (possible cyclic resource inclusion)."); + } } - } - path_cache = p_path; + path_cache = p_path; - if (!path_cache.is_empty()) { - ResourceCache::resources[path_cache] = this; + if (!path_cache.is_empty()) { + ResourceCache::resources[path_cache] = this; + } } - ResourceCache::lock.unlock(); _resource_path_changed(); } @@ -161,12 +166,22 @@ bool Resource::editor_can_reload_from_file() { } void Resource::connect_changed(const Callable &p_callable, uint32_t p_flags) { + if (ResourceLoader::is_within_load() && !Thread::is_main_thread()) { + ResourceLoader::resource_changed_connect(this, p_callable, p_flags); + return; + } + if (!is_connected(CoreStringName(changed), p_callable) || p_flags & CONNECT_REFERENCE_COUNTED) { connect(CoreStringName(changed), p_callable, p_flags); } } void Resource::disconnect_changed(const Callable &p_callable) { + if (ResourceLoader::is_within_load() && !Thread::is_main_thread()) { + ResourceLoader::resource_changed_disconnect(this, p_callable); + return; + } + if (is_connected(CoreStringName(changed), p_callable)) { disconnect(CoreStringName(changed), p_callable); } @@ -401,21 +416,15 @@ void Resource::_take_over_path(const String &p_path) { } RID Resource::get_rid() const { - if (get_script_instance()) { - Callable::CallError ce; - RID ret = get_script_instance()->callp(SNAME("_get_rid"), nullptr, 0, ce); - if (ce.error == Callable::CallError::CALL_OK && ret.is_valid()) { - return ret; - } - } - if (_get_extension() && _get_extension()->get_rid) { - RID ret = RID::from_uint64(_get_extension()->get_rid(_get_extension_instance())); - if (ret.is_valid()) { - return ret; + RID ret; + if (!GDVIRTUAL_CALL(_get_rid, ret)) { +#ifndef DISABLE_DEPRECATED + if (_get_extension() && _get_extension()->get_rid) { + ret = RID::from_uint64(_get_extension()->get_rid(_get_extension_instance())); } +#endif } - - return RID(); + return ret; } #ifdef TOOLS_ENABLED @@ -477,15 +486,13 @@ void Resource::set_as_translation_remapped(bool p_remapped) { return; } - ResourceCache::lock.lock(); + MutexLock lock(ResourceCache::lock); if (p_remapped) { ResourceLoader::remapped_list.add(&remapped_list); } else { ResourceLoader::remapped_list.remove(&remapped_list); } - - ResourceCache::lock.unlock(); } #ifdef TOOLS_ENABLED @@ -543,11 +550,8 @@ void Resource::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::STRING, "resource_name"), "set_name", "get_name"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "resource_scene_unique_id", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_scene_unique_id", "get_scene_unique_id"); - MethodInfo get_rid_bind("_get_rid"); - get_rid_bind.return_val.type = Variant::RID; - - ::ClassDB::add_virtual_method(get_class_static(), get_rid_bind, true, Vector<String>(), true); GDVIRTUAL_BIND(_setup_local_to_scene); + GDVIRTUAL_BIND(_get_rid); } Resource::Resource() : @@ -558,14 +562,13 @@ Resource::~Resource() { return; } - ResourceCache::lock.lock(); + MutexLock lock(ResourceCache::lock); // Only unregister from the cache if this is the actual resource listed there. // (Other resources can have the same value in `path_cache` if loaded with `CACHE_IGNORE`.) HashMap<String, Resource *>::Iterator E = ResourceCache::resources.find(path_cache); if (likely(E && E->value == this)) { ResourceCache::resources.remove(E); } - ResourceCache::lock.unlock(); } HashMap<String, Resource *> ResourceCache::resources; @@ -594,18 +597,20 @@ void ResourceCache::clear() { } bool ResourceCache::has(const String &p_path) { - lock.lock(); + Resource **res = nullptr; - Resource **res = resources.getptr(p_path); + { + MutexLock mutex_lock(lock); - if (res && (*res)->get_reference_count() == 0) { - // This resource is in the process of being deleted, ignore its existence. - (*res)->path_cache = String(); - resources.erase(p_path); - res = nullptr; - } + res = resources.getptr(p_path); - lock.unlock(); + if (res && (*res)->get_reference_count() == 0) { + // This resource is in the process of being deleted, ignore its existence. + (*res)->path_cache = String(); + resources.erase(p_path); + res = nullptr; + } + } if (!res) { return false; @@ -616,28 +621,27 @@ bool ResourceCache::has(const String &p_path) { Ref<Resource> ResourceCache::get_ref(const String &p_path) { Ref<Resource> ref; - lock.lock(); + { + MutexLock mutex_lock(lock); + Resource **res = resources.getptr(p_path); - Resource **res = resources.getptr(p_path); - - if (res) { - ref = Ref<Resource>(*res); - } + if (res) { + ref = Ref<Resource>(*res); + } - if (res && !ref.is_valid()) { - // This resource is in the process of being deleted, ignore its existence - (*res)->path_cache = String(); - resources.erase(p_path); - res = nullptr; + if (res && !ref.is_valid()) { + // This resource is in the process of being deleted, ignore its existence + (*res)->path_cache = String(); + resources.erase(p_path); + res = nullptr; + } } - lock.unlock(); - return ref; } void ResourceCache::get_cached_resources(List<Ref<Resource>> *p_resources) { - lock.lock(); + MutexLock mutex_lock(lock); LocalVector<String> to_remove; @@ -657,14 +661,9 @@ void ResourceCache::get_cached_resources(List<Ref<Resource>> *p_resources) { for (const String &E : to_remove) { resources.erase(E); } - - lock.unlock(); } int ResourceCache::get_cached_resource_count() { - lock.lock(); - int rc = resources.size(); - lock.unlock(); - - return rc; + MutexLock mutex_lock(lock); + return resources.size(); } diff --git a/core/io/resource.h b/core/io/resource.h index cc8a0d4387..2c1a431255 100644 --- a/core/io/resource.h +++ b/core/io/resource.h @@ -87,6 +87,8 @@ protected: virtual void reset_local_to_scene(); GDVIRTUAL0(_setup_local_to_scene); + GDVIRTUAL0RC(RID, _get_rid); + public: static Node *(*_get_local_scene_func)(); //used by editor static void (*_update_configuration_warning)(); //used by editor diff --git a/core/io/resource_format_binary.cpp b/core/io/resource_format_binary.cpp index f71257fa76..b4826c356e 100644 --- a/core/io/resource_format_binary.cpp +++ b/core/io/resource_format_binary.cpp @@ -845,14 +845,29 @@ Error ResourceLoaderBinary::load() { } } - if (value.get_type() == Variant::ARRAY) { - Array set_array = value; - bool is_get_valid = false; - Variant get_value = res->get(name, &is_get_valid); - if (is_get_valid && get_value.get_type() == Variant::ARRAY) { - Array get_array = get_value; - if (!set_array.is_same_typed(get_array)) { - value = Array(set_array, get_array.get_typed_builtin(), get_array.get_typed_class_name(), get_array.get_typed_script()); + if (ClassDB::has_property(res->get_class_name(), name)) { + if (value.get_type() == Variant::ARRAY) { + Array set_array = value; + bool is_get_valid = false; + Variant get_value = res->get(name, &is_get_valid); + if (is_get_valid && get_value.get_type() == Variant::ARRAY) { + Array get_array = get_value; + if (!set_array.is_same_typed(get_array)) { + value = Array(set_array, get_array.get_typed_builtin(), get_array.get_typed_class_name(), get_array.get_typed_script()); + } + } + } + + if (value.get_type() == Variant::DICTIONARY) { + Dictionary set_dict = value; + bool is_get_valid = false; + Variant get_value = res->get(name, &is_get_valid); + if (is_get_valid && get_value.get_type() == Variant::DICTIONARY) { + Dictionary get_dict = get_value; + if (!set_dict.is_same_typed(get_dict)) { + value = Dictionary(set_dict, get_dict.get_typed_key_builtin(), get_dict.get_typed_key_class_name(), get_dict.get_typed_key_script(), + get_dict.get_typed_value_builtin(), get_dict.get_typed_value_class_name(), get_dict.get_typed_value_script()); + } } } } @@ -2064,6 +2079,8 @@ void ResourceFormatSaverBinaryInstance::_find_resources(const Variant &p_variant case Variant::DICTIONARY: { Dictionary d = p_variant; + _find_resources(d.get_typed_key_script()); + _find_resources(d.get_typed_value_script()); List<Variant> keys; d.get_key_list(&keys); for (const Variant &E : keys) { diff --git a/core/io/resource_importer.cpp b/core/io/resource_importer.cpp index 9e6f3ba314..a572dd562e 100644 --- a/core/io/resource_importer.cpp +++ b/core/io/resource_importer.cpp @@ -35,6 +35,8 @@ #include "core/os/os.h" #include "core/variant/variant_parser.h" +ResourceFormatImporterLoadOnStartup ResourceImporter::load_on_startup = nullptr; + bool ResourceFormatImporter::SortImporterByName::operator()(const Ref<ResourceImporter> &p_a, const Ref<ResourceImporter> &p_b) const { return p_a->get_importer_name() < p_b->get_importer_name(); } @@ -137,6 +139,20 @@ Error ResourceFormatImporter::_get_path_and_type(const String &p_path, PathAndTy } Ref<Resource> ResourceFormatImporter::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) { +#ifdef TOOLS_ENABLED + // When loading a resource on startup, we use the load_on_startup callback, + // which executes the loading in the EditorFileSystem. It can reimport + // the resource and retry the load, allowing the resource to be loaded + // even if it is not yet imported. + if (ResourceImporter::load_on_startup != nullptr) { + return ResourceImporter::load_on_startup(this, p_path, r_error, p_use_sub_threads, r_progress, p_cache_mode); + } +#endif + + return load_internal(p_path, r_error, p_use_sub_threads, r_progress, p_cache_mode, false); +} + +Ref<Resource> ResourceFormatImporter::load_internal(const String &p_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode, bool p_silence_errors) { PathAndType pat; Error err = _get_path_and_type(p_path, pat); @@ -148,6 +164,13 @@ Ref<Resource> ResourceFormatImporter::load(const String &p_path, const String &p return Ref<Resource>(); } + if (p_silence_errors) { + // Note: Some importers do not create files in the .godot folder, so we need to check if the path is empty. + if (!pat.path.is_empty() && !FileAccess::exists(pat.path)) { + return Ref<Resource>(); + } + } + Ref<Resource> res = ResourceLoader::_load(pat.path, p_path, pat.type, p_cache_mode, r_error, p_use_sub_threads, r_progress); #ifdef TOOLS_ENABLED @@ -364,6 +387,23 @@ ResourceUID::ID ResourceFormatImporter::get_resource_uid(const String &p_path) c return pat.uid; } +Error ResourceFormatImporter::get_resource_import_info(const String &p_path, StringName &r_type, ResourceUID::ID &r_uid, String &r_import_group_file) const { + PathAndType pat; + Error err = _get_path_and_type(p_path, pat); + + if (err == OK) { + r_type = pat.type; + r_uid = pat.uid; + r_import_group_file = pat.group_file; + } else { + r_type = ""; + r_uid = ResourceUID::INVALID_ID; + r_import_group_file = ""; + } + + return err; +} + Variant ResourceFormatImporter::get_resource_metadata(const String &p_path) const { PathAndType pat; Error err = _get_path_and_type(p_path, pat); diff --git a/core/io/resource_importer.h b/core/io/resource_importer.h index dbd9e70d16..6ea5d0972a 100644 --- a/core/io/resource_importer.h +++ b/core/io/resource_importer.h @@ -35,6 +35,9 @@ #include "core/io/resource_saver.h" class ResourceImporter; +class ResourceFormatImporter; + +typedef Ref<Resource> (*ResourceFormatImporterLoadOnStartup)(ResourceFormatImporter *p_importer, const String &p_path, Error *r_error, bool p_use_sub_threads, float *r_progress, ResourceFormatLoader::CacheMode p_cache_mode); class ResourceFormatImporter : public ResourceFormatLoader { struct PathAndType { @@ -60,6 +63,7 @@ class ResourceFormatImporter : public ResourceFormatLoader { public: static ResourceFormatImporter *get_singleton() { return singleton; } virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE) override; + Ref<Resource> load_internal(const String &p_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode, bool p_silence_errors); virtual void get_recognized_extensions(List<String> *p_extensions) const override; virtual void get_recognized_extensions_for_type(const String &p_type, List<String> *p_extensions) const override; virtual bool recognize_path(const String &p_path, const String &p_for_type = String()) const override; @@ -93,6 +97,8 @@ public: String get_import_settings_hash() const; String get_import_base_path(const String &p_for_file) const; + Error get_resource_import_info(const String &p_path, StringName &r_type, ResourceUID::ID &r_uid, String &r_import_group_file) const; + ResourceFormatImporter(); }; @@ -103,6 +109,8 @@ protected: static void _bind_methods(); public: + static ResourceFormatImporterLoadOnStartup load_on_startup; + virtual String get_importer_name() const = 0; virtual String get_visible_name() const = 0; virtual void get_recognized_extensions(List<String> *p_extensions) const = 0; diff --git a/core/io/resource_loader.cpp b/core/io/resource_loader.cpp index 58ad61b621..f29f9eef98 100644 --- a/core/io/resource_loader.cpp +++ b/core/io/resource_loader.cpp @@ -31,6 +31,7 @@ #include "resource_loader.h" #include "core/config/project_settings.h" +#include "core/core_bind.h" #include "core/io/file_access.h" #include "core/io/resource_importer.h" #include "core/object/script_language.h" @@ -38,7 +39,7 @@ #include "core/os/os.h" #include "core/os/safe_binary_mutex.h" #include "core/string/print_string.h" -#include "core/string/translation.h" +#include "core/string/translation_server.h" #include "core/variant/variant_parser.h" #include "servers/rendering_server.h" @@ -207,34 +208,57 @@ void ResourceFormatLoader::_bind_methods() { /////////////////////////////////// +// These are used before and after a wait for a WorkerThreadPool task +// because that can lead to another load started in the same thread, +// something we must treat as a different stack for the purposes +// of tracking nesting. + +#define PREPARE_FOR_WTP_WAIT \ + int load_nesting_backup = ResourceLoader::load_nesting; \ + Vector<String> load_paths_stack_backup = ResourceLoader::load_paths_stack; \ + ResourceLoader::load_nesting = 0; \ + ResourceLoader::load_paths_stack.clear(); + +#define RESTORE_AFTER_WTP_WAIT \ + DEV_ASSERT(ResourceLoader::load_nesting == 0); \ + DEV_ASSERT(ResourceLoader::load_paths_stack.is_empty()); \ + ResourceLoader::load_nesting = load_nesting_backup; \ + ResourceLoader::load_paths_stack = load_paths_stack_backup; \ + load_paths_stack_backup.clear(); + // This should be robust enough to be called redundantly without issues. void ResourceLoader::LoadToken::clear() { - thread_load_mutex.lock(); - WorkerThreadPool::TaskID task_to_await = 0; - if (!local_path.is_empty()) { // Empty is used for the special case where the load task is not registered. - DEV_ASSERT(thread_load_tasks.has(local_path)); - ThreadLoadTask &load_task = thread_load_tasks[local_path]; - if (!load_task.awaited) { - task_to_await = load_task.task_id; - load_task.awaited = true; - } - thread_load_tasks.erase(local_path); - local_path.clear(); - } + { + MutexLock thread_load_lock(thread_load_mutex); + // User-facing tokens shouldn't be deleted until completely claimed. + DEV_ASSERT(user_rc == 0 && user_path.is_empty()); - if (!user_path.is_empty()) { - DEV_ASSERT(user_load_tokens.has(user_path)); - user_load_tokens.erase(user_path); - user_path.clear(); + if (!local_path.is_empty()) { + if (task_if_unregistered) { + memdelete(task_if_unregistered); + task_if_unregistered = nullptr; + } else { + DEV_ASSERT(thread_load_tasks.has(local_path)); + ThreadLoadTask &load_task = thread_load_tasks[local_path]; + if (load_task.task_id && !load_task.awaited) { + task_to_await = load_task.task_id; + } + // Removing a task which is still in progress would be catastrophic. + // Tokens must be alive until the task thread function is done. + DEV_ASSERT(load_task.status == THREAD_LOAD_FAILED || load_task.status == THREAD_LOAD_LOADED); + thread_load_tasks.erase(local_path); + } + local_path.clear(); // Mark as already cleared. + } } - thread_load_mutex.unlock(); - // If task is unused, await it here, locally, now the token data is consistent. if (task_to_await) { + PREPARE_FOR_WTP_WAIT WorkerThreadPool::get_singleton()->wait_for_task_completion(task_to_await); + RESTORE_AFTER_WTP_WAIT } } @@ -245,18 +269,17 @@ ResourceLoader::LoadToken::~LoadToken() { Ref<Resource> ResourceLoader::_load(const String &p_path, const String &p_original_path, const String &p_type_hint, ResourceFormatLoader::CacheMode p_cache_mode, Error *r_error, bool p_use_sub_threads, float *r_progress) { const String &original_path = p_original_path.is_empty() ? p_path : p_original_path; load_nesting++; - if (load_paths_stack->size()) { - thread_load_mutex.lock(); - const String &parent_task_path = load_paths_stack->get(load_paths_stack->size() - 1); + if (load_paths_stack.size()) { + MutexLock thread_load_lock(thread_load_mutex); + const String &parent_task_path = load_paths_stack.get(load_paths_stack.size() - 1); HashMap<String, ThreadLoadTask>::Iterator E = thread_load_tasks.find(parent_task_path); // Avoid double-tracking, for progress reporting, resources that boil down to a remapped path containing the real payload (e.g., imported resources). bool is_remapped_load = original_path == parent_task_path; if (E && !is_remapped_load) { E->value.sub_tasks.insert(p_original_path); } - thread_load_mutex.unlock(); } - load_paths_stack->push_back(original_path); + load_paths_stack.push_back(original_path); // Try all loaders and pick the first match for the type hint bool found = false; @@ -272,7 +295,7 @@ Ref<Resource> ResourceLoader::_load(const String &p_path, const String &p_origin } } - load_paths_stack->resize(load_paths_stack->size() - 1); + load_paths_stack.resize(load_paths_stack.size() - 1); res_ref_overrides.erase(load_nesting); load_nesting--; @@ -280,6 +303,10 @@ 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)); @@ -291,92 +318,114 @@ Ref<Resource> ResourceLoader::_load(const String &p_path, const String &p_origin ERR_FAIL_V_MSG(Ref<Resource>(), vformat("No loader found for resource: %s (expected type: %s)", p_path, p_type_hint)); } -void ResourceLoader::_thread_load_function(void *p_userdata) { +// This implementation must allow re-entrancy for a task that started awaiting in a deeper stack frame. +// The load task token must be manually re-referenced before this is called, which includes threaded runs. +void ResourceLoader::_run_load_task(void *p_userdata) { ThreadLoadTask &load_task = *(ThreadLoadTask *)p_userdata; - thread_load_mutex.lock(); - caller_task_id = load_task.task_id; - if (cleaning_tasks) { - load_task.status = THREAD_LOAD_FAILED; - thread_load_mutex.unlock(); - return; + { + MutexLock thread_load_lock(thread_load_mutex); + if (cleaning_tasks) { + load_task.status = THREAD_LOAD_FAILED; + return; + } } - thread_load_mutex.unlock(); + + ThreadLoadTask *curr_load_task_backup = curr_load_task; + curr_load_task = &load_task; // Thread-safe either if it's the current thread or a brand new one. - bool mq_override_present = false; CallQueue *own_mq_override = nullptr; if (load_nesting == 0) { - load_paths_stack = memnew(Vector<String>); - - if (!load_task.dependent_path.is_empty()) { - load_paths_stack->push_back(load_task.dependent_path); - } + DEV_ASSERT(load_paths_stack.is_empty()); if (!Thread::is_main_thread()) { // Let the caller thread use its own, for added flexibility. Provide one otherwise. if (MessageQueue::get_singleton() == MessageQueue::get_main_singleton()) { own_mq_override = memnew(CallQueue); MessageQueue::set_thread_singleton_override(own_mq_override); } - mq_override_present = true; set_current_thread_safe_for_nodes(true); } - } else { - DEV_ASSERT(load_task.dependent_path.is_empty()); } // -- - if (!Thread::is_main_thread()) { - set_current_thread_safe_for_nodes(true); - } + bool xl_remapped = false; + const String &remapped_path = _path_remap(load_task.local_path, &xl_remapped); - Ref<Resource> res = _load(load_task.remapped_path, load_task.remapped_path != load_task.local_path ? load_task.local_path : String(), load_task.type_hint, load_task.cache_mode, &load_task.error, load_task.use_sub_threads, &load_task.progress); - if (mq_override_present) { + print_verbose("Loading resource: " + remapped_path); + + Error load_err = OK; + Ref<Resource> res = _load(remapped_path, remapped_path != load_task.local_path ? load_task.local_path : String(), load_task.type_hint, load_task.cache_mode, &load_err, load_task.use_sub_threads, &load_task.progress); + if (MessageQueue::get_singleton() != MessageQueue::get_main_singleton()) { MessageQueue::get_singleton()->flush(); } + if (res.is_null()) { + print_verbose("Failed loading resource: " + remapped_path); + } + thread_load_mutex.lock(); load_task.resource = res; - load_task.progress = 1.0; //it was fully loaded at this point, so force progress to 1.0 + load_task.progress = 1.0; // It was fully loaded at this point, so force progress to 1.0. + load_task.error = load_err; if (load_task.error != OK) { load_task.status = THREAD_LOAD_FAILED; } else { load_task.status = THREAD_LOAD_LOADED; } - if (load_task.cond_var) { + if (load_task.cond_var && load_task.need_wait) { load_task.cond_var->notify_all(); - memdelete(load_task.cond_var); - load_task.cond_var = nullptr; } + load_task.need_wait = false; bool ignoring = load_task.cache_mode == ResourceFormatLoader::CACHE_MODE_IGNORE || load_task.cache_mode == ResourceFormatLoader::CACHE_MODE_IGNORE_DEEP; bool replacing = load_task.cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE || load_task.cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE_DEEP; + bool unlock_pending = true; if (load_task.resource.is_valid()) { + // From now on, no critical section needed as no one will write to the task anymore. + // Moreover, the mutex being unlocked is a requirement if some of the calls below + // that set the resource up invoke code that in turn requests resource loading. + thread_load_mutex.unlock(); + unlock_pending = false; + if (!ignoring) { - if (replacing) { - Ref<Resource> old_res = ResourceCache::get_ref(load_task.local_path); - if (old_res.is_valid() && old_res != load_task.resource) { - // If resource is already loaded, only replace its data, to avoid existing invalidating instances. - old_res->copy_from(load_task.resource); + ResourceCache::lock.lock(); // Check and operations must happen atomically. + bool pending_unlock = true; + Ref<Resource> old_res = ResourceCache::get_ref(load_task.local_path); + if (old_res.is_valid()) { + if (old_res != load_task.resource) { + // Resource can already exists at this point for two reasons: + // a) The load uses replace mode. + // b) There were more than one load in flight for the same path because of deadlock prevention. + // Either case, we want to keep the resource that was already there. + ResourceCache::lock.unlock(); + pending_unlock = false; + if (replacing) { + old_res->copy_from(load_task.resource); + } load_task.resource = old_res; } + } else { + load_task.resource->set_path(load_task.local_path); + } + if (pending_unlock) { + ResourceCache::lock.unlock(); } - load_task.resource->set_path(load_task.local_path, replacing); } else { load_task.resource->set_path_cache(load_task.local_path); } - if (load_task.xl_remapped) { + if (xl_remapped) { load_task.resource->set_as_translation_remapped(true); } #ifdef TOOLS_ENABLED load_task.resource->set_edited(false); if (timestamp_on_load) { - uint64_t mt = FileAccess::get_modified_time(load_task.remapped_path); + uint64_t mt = FileAccess::get_modified_time(remapped_path); //printf("mt %s: %lli\n",remapped_path.utf8().get_data(),mt); load_task.resource->set_last_modified_time(mt); } @@ -392,21 +441,31 @@ void ResourceLoader::_thread_load_function(void *p_userdata) { load_task.status = THREAD_LOAD_LOADED; load_task.progress = 1.0; + thread_load_mutex.unlock(); + unlock_pending = false; + if (_loaded_callback) { _loaded_callback(load_task.resource, load_task.local_path); } } } - thread_load_mutex.unlock(); + // It's safe now to let the task go in case no one else was grabbing the token. + load_task.load_token->unreference(); + + if (unlock_pending) { + thread_load_mutex.unlock(); + } if (load_nesting == 0) { if (own_mq_override) { MessageQueue::set_thread_singleton_override(nullptr); memdelete(own_mq_override); } - memdelete(load_paths_stack); + DEV_ASSERT(load_paths_stack.is_empty()); } + + curr_load_task = curr_load_task_backup; } static String _validate_local_path(const String &p_path) { @@ -421,36 +480,44 @@ static String _validate_local_path(const String &p_path) { } Error ResourceLoader::load_threaded_request(const String &p_path, const String &p_type_hint, bool p_use_sub_threads, ResourceFormatLoader::CacheMode p_cache_mode) { - thread_load_mutex.lock(); - if (user_load_tokens.has(p_path)) { + Ref<ResourceLoader::LoadToken> token = _load_start(p_path, p_type_hint, p_use_sub_threads ? LOAD_THREAD_DISTRIBUTE : LOAD_THREAD_SPAWN_SINGLE, p_cache_mode, true); + return token.is_valid() ? OK : FAILED; +} + +ResourceLoader::LoadToken *ResourceLoader::_load_threaded_request_reuse_user_token(const String &p_path) { + HashMap<String, LoadToken *>::Iterator E = user_load_tokens.find(p_path); + if (E) { print_verbose("load_threaded_request(): Another threaded load for resource path '" + p_path + "' has been initiated. Not an error."); - user_load_tokens[p_path]->reference(); // Additional request. - thread_load_mutex.unlock(); - return OK; - } - user_load_tokens[p_path] = nullptr; - thread_load_mutex.unlock(); - - Ref<ResourceLoader::LoadToken> token = _load_start(p_path, p_type_hint, p_use_sub_threads ? LOAD_THREAD_DISTRIBUTE : LOAD_THREAD_SPAWN_SINGLE, p_cache_mode); - if (token.is_valid()) { - thread_load_mutex.lock(); - token->user_path = p_path; - token->reference(); // First request. - user_load_tokens[p_path] = token.ptr(); - print_lt("REQUEST: user load tokens: " + itos(user_load_tokens.size())); - thread_load_mutex.unlock(); - return OK; + LoadToken *token = E->value; + token->user_rc++; + return token; } else { - return FAILED; + return nullptr; } } +void ResourceLoader::_load_threaded_request_setup_user_token(LoadToken *p_token, const String &p_path) { + p_token->user_path = p_path; + p_token->reference(); // Extra RC until all user requests have been gotten. + p_token->user_rc = 1; + user_load_tokens[p_path] = p_token; + print_lt("REQUEST: user load tokens: " + itos(user_load_tokens.size())); +} + Ref<Resource> ResourceLoader::load(const String &p_path, const String &p_type_hint, ResourceFormatLoader::CacheMode p_cache_mode, Error *r_error) { if (r_error) { *r_error = OK; } - Ref<LoadToken> load_token = _load_start(p_path, p_type_hint, LOAD_THREAD_FROM_CURRENT, p_cache_mode); + LoadThreadMode thread_mode = LOAD_THREAD_FROM_CURRENT; + if (WorkerThreadPool::get_singleton()->get_caller_task_id() != WorkerThreadPool::INVALID_TASK_ID) { + // If user is initiating a single-threaded load from a WorkerThreadPool task, + // we instead spawn a new task so there's a precondition that a load in a pool task + // is always initiated by the engine. That makes certain aspects simpler, such as + // cyclic load detection and awaiting. + thread_mode = LOAD_THREAD_SPAWN_SINGLE; + } + Ref<LoadToken> load_token = _load_start(p_path, p_type_hint, thread_mode, p_cache_mode); if (!load_token.is_valid()) { if (r_error) { *r_error = FAILED; @@ -462,36 +529,50 @@ Ref<Resource> ResourceLoader::load(const String &p_path, const String &p_type_hi return res; } -Ref<ResourceLoader::LoadToken> ResourceLoader::_load_start(const String &p_path, const String &p_type_hint, LoadThreadMode p_thread_mode, ResourceFormatLoader::CacheMode p_cache_mode) { +Ref<ResourceLoader::LoadToken> ResourceLoader::_load_start(const String &p_path, const String &p_type_hint, LoadThreadMode p_thread_mode, ResourceFormatLoader::CacheMode p_cache_mode, bool p_for_user) { String local_path = _validate_local_path(p_path); bool ignoring_cache = p_cache_mode == ResourceFormatLoader::CACHE_MODE_IGNORE || p_cache_mode == ResourceFormatLoader::CACHE_MODE_IGNORE_DEEP; Ref<LoadToken> load_token; - ThreadLoadTask unregistered_load_task; // Once set, must be valid up to the call to do the load. + bool must_not_register = false; ThreadLoadTask *load_task_ptr = nullptr; - bool run_on_current_thread = false; { MutexLock thread_load_lock(thread_load_mutex); + if (p_for_user) { + LoadToken *existing_token = _load_threaded_request_reuse_user_token(p_path); + if (existing_token) { + return Ref<LoadToken>(existing_token); + } + } + if (!ignoring_cache && thread_load_tasks.has(local_path)) { load_token = Ref<LoadToken>(thread_load_tasks[local_path].load_token); - if (!load_token.is_valid()) { + if (load_token.is_valid()) { + if (p_for_user) { + // Load task exists, with no user tokens at the moment. + // Let's "attach" to it. + _load_threaded_request_setup_user_token(load_token.ptr(), p_path); + } + return load_token; + } else { // The token is dying (reached 0 on another thread). // Ensure it's killed now so the path can be safely reused right away. thread_load_tasks[local_path].load_token->clear(); } - return load_token; } load_token.instantiate(); load_token->local_path = local_path; + if (p_for_user) { + _load_threaded_request_setup_user_token(load_token.ptr(), p_path); + } //create load task { ThreadLoadTask load_task; - load_task.remapped_path = _path_remap(local_path, &load_task.xl_remapped); load_task.load_token = load_token.ptr(); load_task.local_path = local_path; load_task.type_hint = p_type_hint; @@ -504,16 +585,17 @@ Ref<ResourceLoader::LoadToken> ResourceLoader::_load_start(const String &p_path, load_task.resource = existing; load_task.status = THREAD_LOAD_LOADED; load_task.progress = 1.0; + DEV_ASSERT(!thread_load_tasks.has(local_path)); thread_load_tasks[local_path] = load_task; return load_token; } } - // Cache-ignoring tasks aren't registered in the map and so must finish within scope. - if (ignoring_cache) { - load_token->local_path.clear(); - unregistered_load_task = load_task; - load_task_ptr = &unregistered_load_task; + // If we want to ignore cache, but there's another task loading it, we can't add this one to the map. + must_not_register = ignoring_cache && thread_load_tasks.has(local_path); + if (must_not_register) { + load_token->task_if_unregistered = memnew(ThreadLoadTask(load_task)); + load_task_ptr = load_token->task_if_unregistered; } else { DEV_ASSERT(!thread_load_tasks.has(local_path)); HashMap<String, ResourceLoader::ThreadLoadTask>::Iterator E = thread_load_tasks.insert(local_path, load_task); @@ -521,20 +603,26 @@ Ref<ResourceLoader::LoadToken> ResourceLoader::_load_start(const String &p_path, } } - run_on_current_thread = ignoring_cache || p_thread_mode == LOAD_THREAD_FROM_CURRENT; + // It's important to keep the token alive because until the load completes, + // which includes before the thread start, it may happen that no one is grabbing + // the token anymore so it's released. + load_task_ptr->load_token->reference(); - if (run_on_current_thread) { - load_task_ptr->thread_id = Thread::get_caller_id(); + if (p_thread_mode == LOAD_THREAD_FROM_CURRENT) { + // The current thread may happen to be a thread from the pool. + WorkerThreadPool::TaskID tid = WorkerThreadPool::get_singleton()->get_caller_task_id(); + if (tid != WorkerThreadPool::INVALID_TASK_ID) { + load_task_ptr->task_id = tid; + } else { + load_task_ptr->thread_id = Thread::get_caller_id(); + } } else { - load_task_ptr->task_id = WorkerThreadPool::get_singleton()->add_native_task(&ResourceLoader::_thread_load_function, load_task_ptr); + load_task_ptr->task_id = WorkerThreadPool::get_singleton()->add_native_task(&ResourceLoader::_run_load_task, load_task_ptr); } - } + } // MutexLock(thread_load_mutex). - if (run_on_current_thread) { - _thread_load_function(load_task_ptr); - if (ignoring_cache) { - load_token->res_if_unregistered = load_task_ptr->resource; - } + if (p_thread_mode == LOAD_THREAD_FROM_CURRENT) { + _run_load_task(load_task_ptr); } return load_token; @@ -563,39 +651,40 @@ float ResourceLoader::_dependency_get_progress(const String &p_path) { } ResourceLoader::ThreadLoadStatus ResourceLoader::load_threaded_get_status(const String &p_path, float *r_progress) { - MutexLock thread_load_lock(thread_load_mutex); + bool ensure_progress = false; + ThreadLoadStatus status = THREAD_LOAD_IN_PROGRESS; + { + MutexLock thread_load_lock(thread_load_mutex); - if (!user_load_tokens.has(p_path)) { - print_verbose("load_threaded_get_status(): No threaded load for resource path '" + p_path + "' has been initiated or its result has already been collected."); - return THREAD_LOAD_INVALID_RESOURCE; - } + if (!user_load_tokens.has(p_path)) { + print_verbose("load_threaded_get_status(): No threaded load for resource path '" + p_path + "' has been initiated or its result has already been collected."); + return THREAD_LOAD_INVALID_RESOURCE; + } - String local_path = _validate_local_path(p_path); - if (!thread_load_tasks.has(local_path)) { -#ifdef DEV_ENABLED - CRASH_NOW(); -#endif - // On non-dev, be defensive and at least avoid crashing (at this point at least). - return THREAD_LOAD_INVALID_RESOURCE; - } + String local_path = _validate_local_path(p_path); + ERR_FAIL_COND_V_MSG(!thread_load_tasks.has(local_path), THREAD_LOAD_INVALID_RESOURCE, "Bug in ResourceLoader logic, please report."); - ThreadLoadTask &load_task = thread_load_tasks[local_path]; - ThreadLoadStatus status; - status = load_task.status; - if (r_progress) { - *r_progress = _dependency_get_progress(local_path); - } + ThreadLoadTask &load_task = thread_load_tasks[local_path]; + status = load_task.status; + if (r_progress) { + *r_progress = _dependency_get_progress(local_path); + } - // Support userland polling in a loop on the main thread. - if (Thread::is_main_thread() && status == THREAD_LOAD_IN_PROGRESS) { - uint64_t frame = Engine::get_singleton()->get_process_frames(); - if (frame == load_task.last_progress_check_main_thread_frame) { - _ensure_load_progress(); - } else { - load_task.last_progress_check_main_thread_frame = frame; + // Support userland polling in a loop on the main thread. + if (Thread::is_main_thread() && status == THREAD_LOAD_IN_PROGRESS) { + uint64_t frame = Engine::get_singleton()->get_process_frames(); + if (frame == load_task.last_progress_check_main_thread_frame) { + ensure_progress = true; + } else { + load_task.last_progress_check_main_thread_frame = frame; + } } } + if (ensure_progress) { + _ensure_load_progress(); + } + return status; } @@ -617,31 +706,32 @@ Ref<Resource> ResourceLoader::load_threaded_get(const String &p_path, Error *r_e } LoadToken *load_token = user_load_tokens[p_path]; - if (!load_token) { - // This happens if requested from one thread and rapidly querying from another. - if (r_error) { - *r_error = ERR_BUSY; - } - return Ref<Resource>(); - } + DEV_ASSERT(load_token->user_rc >= 1); // Support userland requesting on the main thread before the load is reported to be complete. if (Thread::is_main_thread() && !load_token->local_path.is_empty()) { const ThreadLoadTask &load_task = thread_load_tasks[load_token->local_path]; while (load_task.status == THREAD_LOAD_IN_PROGRESS) { - if (!_ensure_load_progress()) { - // This local poll loop is not needed. + thread_load_lock.temp_unlock(); + bool exit = !_ensure_load_progress(); + OS::get_singleton()->delay_usec(1000); + thread_load_lock.temp_relock(); + if (exit) { break; } - thread_load_lock.~MutexLock(); - OS::get_singleton()->delay_usec(1000); - new (&thread_load_lock) MutexLock(thread_load_mutex); } } res = _load_complete_inner(*load_token, r_error, thread_load_lock); - if (load_token->unreference()) { - memdelete(load_token); + + load_token->user_rc--; + if (load_token->user_rc == 0) { + load_token->user_path.clear(); + user_load_tokens.erase(p_path); + if (load_token->unreference()) { + memdelete(load_token); + load_token = nullptr; + } } } @@ -660,16 +750,15 @@ Ref<Resource> ResourceLoader::_load_complete_inner(LoadToken &p_load_token, Erro *r_error = OK; } - if (!p_load_token.local_path.is_empty()) { + ThreadLoadTask *load_task_ptr = nullptr; + if (p_load_token.task_if_unregistered) { + load_task_ptr = p_load_token.task_if_unregistered; + } else { if (!thread_load_tasks.has(p_load_token.local_path)) { -#ifdef DEV_ENABLED - CRASH_NOW(); -#endif - // On non-dev, be defensive and at least avoid crashing (at this point at least). if (r_error) { *r_error = ERR_BUG; } - return Ref<Resource>(); + ERR_FAIL_V_MSG(Ref<Resource>(), "Bug in ResourceLoader logic, please report."); } ThreadLoadTask &load_task = thread_load_tasks[p_load_token.local_path]; @@ -677,7 +766,7 @@ Ref<Resource> ResourceLoader::_load_complete_inner(LoadToken &p_load_token, Erro if (load_task.status == THREAD_LOAD_IN_PROGRESS) { DEV_ASSERT((load_task.task_id == 0) != (load_task.thread_id == 0)); - if ((load_task.task_id != 0 && load_task.task_id == caller_task_id) || + if ((load_task.task_id != 0 && load_task.task_id == WorkerThreadPool::get_singleton()->get_caller_task_id()) || (load_task.thread_id != 0 && load_task.thread_id == Thread::get_caller_id())) { // Load is in progress, but it's precisely this thread the one in charge. // That means this is a cyclic load. @@ -688,49 +777,46 @@ Ref<Resource> ResourceLoader::_load_complete_inner(LoadToken &p_load_token, Erro } bool loader_is_wtp = load_task.task_id != 0; - Error wtp_task_err = FAILED; if (loader_is_wtp) { // Loading thread is in the worker pool. - thread_load_mutex.unlock(); - wtp_task_err = WorkerThreadPool::get_singleton()->wait_for_task_completion(load_task.task_id); - } + p_thread_load_lock.temp_unlock(); + + PREPARE_FOR_WTP_WAIT + Error wait_err = WorkerThreadPool::get_singleton()->wait_for_task_completion(load_task.task_id); + RESTORE_AFTER_WTP_WAIT + + DEV_ASSERT(!wait_err || wait_err == ERR_BUSY); + if (wait_err == ERR_BUSY) { + // The WorkerThreadPool has reported that the current task wants to await on an older one. + // That't not allowed for safety, to avoid deadlocks. Fortunately, though, in the context of + // resource loading that means that the task to wait for can be restarted here to break the + // cycle, with as much recursion into this process as needed. + // When the stack is eventually unrolled, the original load will have been notified to go on. + load_task.load_token->reference(); + _run_load_task(&load_task); + } - if (load_task.status == THREAD_LOAD_IN_PROGRESS) { // If early errored, awaiting would deadlock. - if (loader_is_wtp) { - if (wtp_task_err == ERR_BUSY) { - // The WorkerThreadPool has reported that the current task wants to await on an older one. - // That't not allowed for safety, to avoid deadlocks. Fortunately, though, in the context of - // resource loading that means that the task to wait for can be restarted here to break the - // cycle, with as much recursion into this process as needed. - // When the stack is eventually unrolled, the original load will have been notified to go on. - // CACHE_MODE_IGNORE is needed because, otherwise, the new request would just see there's - // an ongoing load for that resource and wait for it again. This value forces a new load. - Ref<ResourceLoader::LoadToken> token = _load_start(load_task.local_path, load_task.type_hint, LOAD_THREAD_DISTRIBUTE, ResourceFormatLoader::CACHE_MODE_IGNORE); - Ref<Resource> resource = _load_complete(*token.ptr(), &wtp_task_err); - if (r_error) { - *r_error = wtp_task_err; - } - thread_load_mutex.lock(); - return resource; - } else { - DEV_ASSERT(wtp_task_err == OK); - thread_load_mutex.lock(); - load_task.awaited = true; - } - } else { - // Loading thread is main or user thread. - if (!load_task.cond_var) { - load_task.cond_var = memnew(ConditionVariable); - } - do { - load_task.cond_var->wait(p_thread_load_lock); - DEV_ASSERT(thread_load_tasks.has(p_load_token.local_path) && p_load_token.get_reference_count()); - } while (load_task.cond_var); + p_thread_load_lock.temp_relock(); + load_task.awaited = true; + + DEV_ASSERT(load_task.status == THREAD_LOAD_FAILED || load_task.status == THREAD_LOAD_LOADED); + } else if (load_task.need_wait) { + // Loading thread is main or user thread. + if (!load_task.cond_var) { + load_task.cond_var = memnew(ConditionVariable); } - } else { - if (loader_is_wtp) { - thread_load_mutex.lock(); + load_task.awaiters_count++; + do { + load_task.cond_var->wait(p_thread_load_lock); + DEV_ASSERT(thread_load_tasks.has(p_load_token.local_path) && p_load_token.get_reference_count()); + } while (load_task.need_wait); + load_task.awaiters_count--; + if (load_task.awaiters_count == 0) { + memdelete(load_task.cond_var); + load_task.cond_var = nullptr; } + + DEV_ASSERT(load_task.status == THREAD_LOAD_FAILED || load_task.status == THREAD_LOAD_LOADED); } } @@ -739,22 +825,51 @@ Ref<Resource> ResourceLoader::_load_complete_inner(LoadToken &p_load_token, Erro load_task.error = FAILED; } - Ref<Resource> resource = load_task.resource; - if (r_error) { - *r_error = load_task.error; - } - return resource; - } else { - // Special case of an unregistered task. - // The resource should have been loaded by now. - Ref<Resource> resource = p_load_token.res_if_unregistered; - if (!resource.is_valid()) { - if (r_error) { - *r_error = FAILED; + load_task_ptr = &load_task; + } + + p_thread_load_lock.temp_unlock(); + + Ref<Resource> resource = load_task_ptr->resource; + if (r_error) { + *r_error = load_task_ptr->error; + } + + if (resource.is_valid()) { + if (curr_load_task) { + // A task awaiting another => Let the awaiter accumulate the resource changed connections. + DEV_ASSERT(curr_load_task != load_task_ptr); + for (const ThreadLoadTask::ResourceChangedConnection &rcc : load_task_ptr->resource_changed_connections) { + curr_load_task->resource_changed_connections.push_back(rcc); + } + } else { + // A leaf task being awaited => Propagate the resource changed connections. + if (Thread::is_main_thread()) { + // On the main thread it's safe to migrate the connections to the standard signal mechanism. + for (const ThreadLoadTask::ResourceChangedConnection &rcc : load_task_ptr->resource_changed_connections) { + if (rcc.callable.is_valid()) { + rcc.source->connect_changed(rcc.callable, rcc.flags); + } + } + } else { + // On non-main threads, we have to queue and call it done when processed. + if (!load_task_ptr->resource_changed_connections.is_empty()) { + for (const ThreadLoadTask::ResourceChangedConnection &rcc : load_task_ptr->resource_changed_connections) { + if (rcc.callable.is_valid()) { + MessageQueue::get_main_singleton()->push_callable(callable_mp(rcc.source, &Resource::connect_changed).bind(rcc.callable, rcc.flags)); + } + } + core_bind::Semaphore done; + MessageQueue::get_main_singleton()->push_callable(callable_mp(&done, &core_bind::Semaphore::post)); + done.wait(); + } } } - return resource; } + + p_thread_load_lock.temp_relock(); + + return resource; } bool ResourceLoader::_ensure_load_progress() { @@ -768,6 +883,50 @@ bool ResourceLoader::_ensure_load_progress() { return true; } +void ResourceLoader::resource_changed_connect(Resource *p_source, const Callable &p_callable, uint32_t p_flags) { + print_lt(vformat("%d\t%ud:%s\t" FUNCTION_STR "\t%d", Thread::get_caller_id(), p_source->get_instance_id(), p_source->get_class(), p_callable.get_object_id())); + + MutexLock lock(thread_load_mutex); + + for (const ThreadLoadTask::ResourceChangedConnection &rcc : curr_load_task->resource_changed_connections) { + if (unlikely(rcc.source == p_source && rcc.callable == p_callable)) { + return; + } + } + + ThreadLoadTask::ResourceChangedConnection rcc; + rcc.source = p_source; + rcc.callable = p_callable; + rcc.flags = p_flags; + curr_load_task->resource_changed_connections.push_back(rcc); +} + +void ResourceLoader::resource_changed_disconnect(Resource *p_source, const Callable &p_callable) { + print_lt(vformat("%d\t%ud:%s\t" FUNCTION_STR "t%d", Thread::get_caller_id(), p_source->get_instance_id(), p_source->get_class(), p_callable.get_object_id())); + + MutexLock lock(thread_load_mutex); + + for (uint32_t i = 0; i < curr_load_task->resource_changed_connections.size(); ++i) { + const ThreadLoadTask::ResourceChangedConnection &rcc = curr_load_task->resource_changed_connections[i]; + if (unlikely(rcc.source == p_source && rcc.callable == p_callable)) { + curr_load_task->resource_changed_connections.remove_at_unordered(i); + return; + } + } +} + +void ResourceLoader::resource_changed_emit(Resource *p_source) { + print_lt(vformat("%d\t%ud:%s\t" FUNCTION_STR, Thread::get_caller_id(), p_source->get_instance_id(), p_source->get_class())); + + MutexLock lock(thread_load_mutex); + + for (const ThreadLoadTask::ResourceChangedConnection &rcc : curr_load_task->resource_changed_connections) { + if (unlikely(rcc.source == p_source)) { + rcc.callable.call(); + } + } +} + Ref<Resource> ResourceLoader::ensure_resource_ref_override_for_outer_load(const String &p_path, const String &p_res_type) { ERR_FAIL_COND_V(load_nesting == 0, Ref<Resource>()); // It makes no sense to use this from nesting level 0. const String &local_path = _validate_local_path(p_path); @@ -1044,36 +1203,39 @@ String ResourceLoader::_path_remap(const String &p_path, bool *r_translation_rem new_path = path_remaps[new_path]; } else { // Try file remap. - Error err; - Ref<FileAccess> f = FileAccess::open(new_path + ".remap", FileAccess::READ, &err); - if (f.is_valid()) { - VariantParser::StreamFile stream; - stream.f = f; - - String assign; - Variant value; - VariantParser::Tag next_tag; - - int lines = 0; - String error_text; - while (true) { - assign = Variant(); - next_tag.fields.clear(); - next_tag.name = String(); - - err = VariantParser::parse_tag_assign_eof(&stream, lines, error_text, next_tag, assign, value, nullptr, true); - if (err == ERR_FILE_EOF) { - break; - } else if (err != OK) { - ERR_PRINT("Parse error: " + p_path + ".remap:" + itos(lines) + " error: " + error_text + "."); - break; - } + // Usually, there's no remap file and FileAccess::exists() is faster than FileAccess::open(). + if (FileAccess::exists(new_path + ".remap")) { + Error err; + Ref<FileAccess> f = FileAccess::open(new_path + ".remap", FileAccess::READ, &err); + if (f.is_valid()) { + VariantParser::StreamFile stream; + stream.f = f; + + String assign; + Variant value; + VariantParser::Tag next_tag; + + int lines = 0; + String error_text; + while (true) { + assign = Variant(); + next_tag.fields.clear(); + next_tag.name = String(); + + err = VariantParser::parse_tag_assign_eof(&stream, lines, error_text, next_tag, assign, value, nullptr, true); + if (err == ERR_FILE_EOF) { + break; + } else if (err != OK) { + ERR_PRINT("Parse error: " + p_path + ".remap:" + itos(lines) + " error: " + error_text + "."); + break; + } - if (assign == "path") { - new_path = value; - break; - } else if (next_tag.name != "remap") { - break; + if (assign == "path") { + new_path = value; + break; + } else if (next_tag.name != "remap") { + break; + } } } } @@ -1095,17 +1257,17 @@ String ResourceLoader::path_remap(const String &p_path) { } void ResourceLoader::reload_translation_remaps() { - ResourceCache::lock.lock(); - List<Resource *> to_reload; - SelfList<Resource> *E = remapped_list.first(); - while (E) { - to_reload.push_back(E->self()); - E = E->next(); - } + { + MutexLock lock(ResourceCache::lock); + SelfList<Resource> *E = remapped_list.first(); - ResourceCache::lock.unlock(); + while (E) { + to_reload.push_back(E->self()); + E = E->next(); + } + } //now just make sure to not delete any of these resources while changing locale.. while (to_reload.front()) { @@ -1145,7 +1307,7 @@ void ResourceLoader::clear_translation_remaps() { void ResourceLoader::clear_thread_load_tasks() { // Bring the thing down as quickly as possible without causing deadlocks or leaks. - thread_load_mutex.lock(); + MutexLock thread_load_lock(thread_load_mutex); cleaning_tasks = true; while (true) { @@ -1153,11 +1315,10 @@ void ResourceLoader::clear_thread_load_tasks() { if (thread_load_tasks.size()) { for (KeyValue<String, ResourceLoader::ThreadLoadTask> &E : thread_load_tasks) { if (E.value.status == THREAD_LOAD_IN_PROGRESS) { - if (E.value.cond_var) { + if (E.value.cond_var && E.value.need_wait) { E.value.cond_var->notify_all(); - memdelete(E.value.cond_var); - E.value.cond_var = nullptr; } + E.value.need_wait = false; none_running = false; } } @@ -1165,21 +1326,23 @@ void ResourceLoader::clear_thread_load_tasks() { if (none_running) { break; } - thread_load_mutex.unlock(); + thread_load_lock.temp_unlock(); OS::get_singleton()->delay_usec(1000); - thread_load_mutex.lock(); + thread_load_lock.temp_relock(); } while (user_load_tokens.begin()) { - // User load tokens remove themselves from the map on destruction. - memdelete(user_load_tokens.begin()->value); + LoadToken *user_token = user_load_tokens.begin()->value; + user_load_tokens.remove(user_load_tokens.begin()); + DEV_ASSERT(user_token->user_rc > 0 && !user_token->user_path.is_empty()); + user_token->user_path.clear(); + user_token->user_rc = 0; + user_token->unreference(); } - user_load_tokens.clear(); thread_load_tasks.clear(); cleaning_tasks = false; - thread_load_mutex.unlock(); } void ResourceLoader::load_path_remaps() { @@ -1292,12 +1455,16 @@ bool ResourceLoader::abort_on_missing_resource = true; bool ResourceLoader::timestamp_on_load = false; thread_local int ResourceLoader::load_nesting = 0; -thread_local WorkerThreadPool::TaskID ResourceLoader::caller_task_id = 0; -thread_local Vector<String> *ResourceLoader::load_paths_stack; +thread_local Vector<String> ResourceLoader::load_paths_stack; thread_local HashMap<int, HashMap<String, Ref<Resource>>> ResourceLoader::res_ref_overrides; +thread_local ResourceLoader::ThreadLoadTask *ResourceLoader::curr_load_task = nullptr; + +SafeBinaryMutex<ResourceLoader::BINARY_MUTEX_TAG> &_get_res_loader_mutex() { + return ResourceLoader::thread_load_mutex; +} template <> -thread_local uint32_t SafeBinaryMutex<ResourceLoader::BINARY_MUTEX_TAG>::count = 0; +thread_local SafeBinaryMutex<ResourceLoader::BINARY_MUTEX_TAG>::TLSData SafeBinaryMutex<ResourceLoader::BINARY_MUTEX_TAG>::tls_data(_get_res_loader_mutex()); SafeBinaryMutex<ResourceLoader::BINARY_MUTEX_TAG> ResourceLoader::thread_load_mutex; HashMap<String, ResourceLoader::ThreadLoadTask> ResourceLoader::thread_load_tasks; bool ResourceLoader::cleaning_tasks = false; diff --git a/core/io/resource_loader.h b/core/io/resource_loader.h index 46df79ea22..caaf9f8f45 100644 --- a/core/io/resource_loader.h +++ b/core/io/resource_loader.h @@ -100,10 +100,14 @@ typedef Error (*ResourceLoaderImport)(const String &p_path); typedef void (*ResourceLoadedCallback)(Ref<Resource> p_resource, const String &p_path); class ResourceLoader { + friend class LoadToken; + enum { MAX_LOADERS = 64 }; + struct ThreadLoadTask; + public: enum ThreadLoadStatus { THREAD_LOAD_INVALID_RESOURCE, @@ -121,7 +125,8 @@ public: struct LoadToken : public RefCounted { String local_path; String user_path; - Ref<Resource> res_if_unregistered; + uint32_t user_rc = 0; // Having user RC implies regular RC incremented in one, until the user RC reaches zero. + ThreadLoadTask *task_if_unregistered = nullptr; void clear(); @@ -130,10 +135,13 @@ public: static const int BINARY_MUTEX_TAG = 1; - static Ref<LoadToken> _load_start(const String &p_path, const String &p_type_hint, LoadThreadMode p_thread_mode, ResourceFormatLoader::CacheMode p_cache_mode); + static Ref<LoadToken> _load_start(const String &p_path, const String &p_type_hint, LoadThreadMode p_thread_mode, ResourceFormatLoader::CacheMode p_cache_mode, bool p_for_user = false); static Ref<Resource> _load_complete(LoadToken &p_load_token, Error *r_error); private: + static LoadToken *_load_threaded_request_reuse_user_token(const String &p_path); + static void _load_threaded_request_setup_user_token(LoadToken *p_token, const String &p_path); + static Ref<Resource> _load_complete_inner(LoadToken &p_load_token, Error *r_error, MutexLock<SafeBinaryMutex<BINARY_MUTEX_TAG>> &p_thread_load_lock); static Ref<ResourceFormatLoader> loader[MAX_LOADERS]; @@ -167,10 +175,10 @@ private: Thread::ID thread_id = 0; // Used if running on an user thread (e.g., simple non-threaded load). bool awaited = false; // If it's in the pool, this helps not awaiting from more than one dependent thread. ConditionVariable *cond_var = nullptr; // In not in the worker pool or already awaiting, this is used as a secondary awaiting mechanism. + uint32_t awaiters_count = 0; + bool need_wait = true; LoadToken *load_token = nullptr; String local_path; - String remapped_path; - String dependent_path; String type_hint; float progress = 0.0f; float max_reported_progress = 0.0f; @@ -179,18 +187,27 @@ private: ResourceFormatLoader::CacheMode cache_mode = ResourceFormatLoader::CACHE_MODE_REUSE; Error error = OK; Ref<Resource> resource; - bool xl_remapped = false; bool use_sub_threads = false; HashSet<String> sub_tasks; + + struct ResourceChangedConnection { + Resource *source = nullptr; + Callable callable; + uint32_t flags = 0; + }; + LocalVector<ResourceChangedConnection> resource_changed_connections; }; - static void _thread_load_function(void *p_userdata); + static void _run_load_task(void *p_userdata); static thread_local int load_nesting; - static thread_local WorkerThreadPool::TaskID caller_task_id; static thread_local HashMap<int, HashMap<String, Ref<Resource>>> res_ref_overrides; // Outermost key is nesting level. - static thread_local Vector<String> *load_paths_stack; // A pointer to avoid broken TLS implementations from double-running the destructor. + static thread_local Vector<String> load_paths_stack; + static thread_local ThreadLoadTask *curr_load_task; + static SafeBinaryMutex<BINARY_MUTEX_TAG> thread_load_mutex; + friend SafeBinaryMutex<BINARY_MUTEX_TAG> &_get_res_loader_mutex(); + static HashMap<String, ThreadLoadTask> thread_load_tasks; static bool cleaning_tasks; @@ -207,6 +224,10 @@ public: static bool is_within_load() { return load_nesting > 0; }; + static void resource_changed_connect(Resource *p_source, const Callable &p_callable, uint32_t p_flags); + static void resource_changed_disconnect(Resource *p_source, const Callable &p_callable); + static void resource_changed_emit(Resource *p_source); + static Ref<Resource> load(const String &p_path, const String &p_type_hint = "", ResourceFormatLoader::CacheMode p_cache_mode = ResourceFormatLoader::CACHE_MODE_REUSE, Error *r_error = nullptr); static bool exists(const String &p_path, const String &p_type_hint = ""); diff --git a/core/io/stream_peer_tls.cpp b/core/io/stream_peer_tls.cpp index 69877974e6..f04e217a26 100644 --- a/core/io/stream_peer_tls.cpp +++ b/core/io/stream_peer_tls.cpp @@ -32,11 +32,11 @@ #include "core/config/engine.h" -StreamPeerTLS *(*StreamPeerTLS::_create)() = nullptr; +StreamPeerTLS *(*StreamPeerTLS::_create)(bool p_notify_postinitialize) = nullptr; -StreamPeerTLS *StreamPeerTLS::create() { +StreamPeerTLS *StreamPeerTLS::create(bool p_notify_postinitialize) { if (_create) { - return _create(); + return _create(p_notify_postinitialize); } return nullptr; } diff --git a/core/io/stream_peer_tls.h b/core/io/stream_peer_tls.h index 5894abb7a4..3e03e25a2d 100644 --- a/core/io/stream_peer_tls.h +++ b/core/io/stream_peer_tls.h @@ -38,7 +38,7 @@ class StreamPeerTLS : public StreamPeer { GDCLASS(StreamPeerTLS, StreamPeer); protected: - static StreamPeerTLS *(*_create)(); + static StreamPeerTLS *(*_create)(bool p_notify_postinitialize); static void _bind_methods(); public: @@ -58,7 +58,7 @@ public: virtual void disconnect_from_stream() = 0; - static StreamPeerTLS *create(); + static StreamPeerTLS *create(bool p_notify_postinitialize = true); static bool is_available(); diff --git a/core/math/a_star.cpp b/core/math/a_star.cpp index 4497604947..c53fd3d330 100644 --- a/core/math/a_star.cpp +++ b/core/math/a_star.cpp @@ -391,9 +391,9 @@ bool AStar3D::_solve(Point *begin_point, Point *end_point) { return found_route; } -real_t AStar3D::_estimate_cost(int64_t p_from_id, int64_t p_to_id) { +real_t AStar3D::_estimate_cost(int64_t p_from_id, int64_t p_end_id) { real_t scost; - if (GDVIRTUAL_CALL(_estimate_cost, p_from_id, p_to_id, scost)) { + if (GDVIRTUAL_CALL(_estimate_cost, p_from_id, p_end_id, scost)) { return scost; } @@ -401,11 +401,11 @@ real_t AStar3D::_estimate_cost(int64_t p_from_id, int64_t p_to_id) { bool from_exists = points.lookup(p_from_id, from_point); ERR_FAIL_COND_V_MSG(!from_exists, 0, vformat("Can't estimate cost. Point with id: %d doesn't exist.", p_from_id)); - Point *to_point = nullptr; - bool to_exists = points.lookup(p_to_id, to_point); - ERR_FAIL_COND_V_MSG(!to_exists, 0, vformat("Can't estimate cost. Point with id: %d doesn't exist.", p_to_id)); + Point *end_point = nullptr; + bool end_exists = points.lookup(p_end_id, end_point); + ERR_FAIL_COND_V_MSG(!end_exists, 0, vformat("Can't estimate cost. Point with id: %d doesn't exist.", p_end_id)); - return from_point->pos.distance_to(to_point->pos); + return from_point->pos.distance_to(end_point->pos); } real_t AStar3D::_compute_cost(int64_t p_from_id, int64_t p_to_id) { @@ -579,7 +579,7 @@ void AStar3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_point_path", "from_id", "to_id", "allow_partial_path"), &AStar3D::get_point_path, DEFVAL(false)); ClassDB::bind_method(D_METHOD("get_id_path", "from_id", "to_id", "allow_partial_path"), &AStar3D::get_id_path, DEFVAL(false)); - GDVIRTUAL_BIND(_estimate_cost, "from_id", "to_id") + GDVIRTUAL_BIND(_estimate_cost, "from_id", "end_id") GDVIRTUAL_BIND(_compute_cost, "from_id", "to_id") } @@ -675,9 +675,9 @@ Vector2 AStar2D::get_closest_position_in_segment(const Vector2 &p_point) const { return Vector2(p.x, p.y); } -real_t AStar2D::_estimate_cost(int64_t p_from_id, int64_t p_to_id) { +real_t AStar2D::_estimate_cost(int64_t p_from_id, int64_t p_end_id) { real_t scost; - if (GDVIRTUAL_CALL(_estimate_cost, p_from_id, p_to_id, scost)) { + if (GDVIRTUAL_CALL(_estimate_cost, p_from_id, p_end_id, scost)) { return scost; } @@ -685,11 +685,11 @@ real_t AStar2D::_estimate_cost(int64_t p_from_id, int64_t p_to_id) { bool from_exists = astar.points.lookup(p_from_id, from_point); ERR_FAIL_COND_V_MSG(!from_exists, 0, vformat("Can't estimate cost. Point with id: %d doesn't exist.", p_from_id)); - AStar3D::Point *to_point = nullptr; - bool to_exists = astar.points.lookup(p_to_id, to_point); - ERR_FAIL_COND_V_MSG(!to_exists, 0, vformat("Can't estimate cost. Point with id: %d doesn't exist.", p_to_id)); + AStar3D::Point *end_point = nullptr; + bool to_exists = astar.points.lookup(p_end_id, end_point); + ERR_FAIL_COND_V_MSG(!to_exists, 0, vformat("Can't estimate cost. Point with id: %d doesn't exist.", p_end_id)); - return from_point->pos.distance_to(to_point->pos); + return from_point->pos.distance_to(end_point->pos); } real_t AStar2D::_compute_cost(int64_t p_from_id, int64_t p_to_id) { @@ -918,6 +918,6 @@ void AStar2D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_point_path", "from_id", "to_id", "allow_partial_path"), &AStar2D::get_point_path, DEFVAL(false)); ClassDB::bind_method(D_METHOD("get_id_path", "from_id", "to_id", "allow_partial_path"), &AStar2D::get_id_path, DEFVAL(false)); - GDVIRTUAL_BIND(_estimate_cost, "from_id", "to_id") + GDVIRTUAL_BIND(_estimate_cost, "from_id", "end_id") GDVIRTUAL_BIND(_compute_cost, "from_id", "to_id") } diff --git a/core/math/a_star.h b/core/math/a_star.h index 8e054c4789..143a3bec61 100644 --- a/core/math/a_star.h +++ b/core/math/a_star.h @@ -120,7 +120,7 @@ class AStar3D : public RefCounted { protected: static void _bind_methods(); - virtual real_t _estimate_cost(int64_t p_from_id, int64_t p_to_id); + virtual real_t _estimate_cost(int64_t p_from_id, int64_t p_end_id); virtual real_t _compute_cost(int64_t p_from_id, int64_t p_to_id); GDVIRTUAL2RC(real_t, _estimate_cost, int64_t, int64_t) @@ -176,7 +176,7 @@ class AStar2D : public RefCounted { protected: static void _bind_methods(); - virtual real_t _estimate_cost(int64_t p_from_id, int64_t p_to_id); + virtual real_t _estimate_cost(int64_t p_from_id, int64_t p_end_id); virtual real_t _compute_cost(int64_t p_from_id, int64_t p_to_id); GDVIRTUAL2RC(real_t, _estimate_cost, int64_t, int64_t) diff --git a/core/math/a_star_grid_2d.cpp b/core/math/a_star_grid_2d.cpp index f272407869..c40ee5b4d7 100644 --- a/core/math/a_star_grid_2d.cpp +++ b/core/math/a_star_grid_2d.cpp @@ -122,14 +122,23 @@ AStarGrid2D::CellShape AStarGrid2D::get_cell_shape() const { } void AStarGrid2D::update() { + if (!dirty) { + return; + } + points.clear(); + solid_mask.clear(); const int32_t end_x = region.get_end().x; const int32_t end_y = region.get_end().y; const Vector2 half_cell_size = cell_size / 2; + for (int32_t x = region.position.x; x < end_x + 2; x++) { + solid_mask.push_back(true); + } for (int32_t y = region.position.y; y < end_y; y++) { LocalVector<Point> line; + solid_mask.push_back(true); for (int32_t x = region.position.x; x < end_x; x++) { Vector2 v = offset; switch (cell_shape) { @@ -146,10 +155,16 @@ void AStarGrid2D::update() { break; } line.push_back(Point(Vector2i(x, y), v)); + solid_mask.push_back(false); } + solid_mask.push_back(true); points.push_back(line); } + for (int32_t x = region.position.x; x < end_x + 2; x++) { + solid_mask.push_back(true); + } + dirty = false; } @@ -203,13 +218,13 @@ AStarGrid2D::Heuristic AStarGrid2D::get_default_estimate_heuristic() const { void AStarGrid2D::set_point_solid(const Vector2i &p_id, bool p_solid) { ERR_FAIL_COND_MSG(dirty, "Grid is not initialized. Call the update method."); ERR_FAIL_COND_MSG(!is_in_boundsv(p_id), vformat("Can't set if point is disabled. Point %s out of bounds %s.", p_id, region)); - _get_point_unchecked(p_id)->solid = p_solid; + _set_solid_unchecked(p_id, p_solid); } bool AStarGrid2D::is_point_solid(const Vector2i &p_id) const { ERR_FAIL_COND_V_MSG(dirty, false, "Grid is not initialized. Call the update method."); ERR_FAIL_COND_V_MSG(!is_in_boundsv(p_id), false, vformat("Can't get if point is disabled. Point %s out of bounds %s.", p_id, region)); - return _get_point_unchecked(p_id)->solid; + return _get_solid_unchecked(p_id); } void AStarGrid2D::set_point_weight_scale(const Vector2i &p_id, real_t p_weight_scale) { @@ -234,7 +249,7 @@ void AStarGrid2D::fill_solid_region(const Rect2i &p_region, bool p_solid) { for (int32_t y = safe_region.position.y; y < end_y; y++) { for (int32_t x = safe_region.position.x; x < end_x; x++) { - _get_point_unchecked(x, y)->solid = p_solid; + _set_solid_unchecked(x, y, p_solid); } } } @@ -255,13 +270,6 @@ void AStarGrid2D::fill_weight_scale_region(const Rect2i &p_region, real_t p_weig } AStarGrid2D::Point *AStarGrid2D::_jump(Point *p_from, Point *p_to) { - if (!p_to || p_to->solid) { - return nullptr; - } - if (p_to == end) { - return p_to; - } - int32_t from_x = p_from->id.x; int32_t from_y = p_from->id.y; @@ -272,72 +280,109 @@ AStarGrid2D::Point *AStarGrid2D::_jump(Point *p_from, Point *p_to) { int32_t dy = to_y - from_y; if (diagonal_mode == DIAGONAL_MODE_ALWAYS || diagonal_mode == DIAGONAL_MODE_AT_LEAST_ONE_WALKABLE) { - if (dx != 0 && dy != 0) { - if ((_is_walkable(to_x - dx, to_y + dy) && !_is_walkable(to_x - dx, to_y)) || (_is_walkable(to_x + dx, to_y - dy) && !_is_walkable(to_x, to_y - dy))) { - return p_to; - } - if (_jump(p_to, _get_point(to_x + dx, to_y)) != nullptr) { - return p_to; + if (dx == 0 || dy == 0) { + return _forced_successor(to_x, to_y, dx, dy); + } + + while (_is_walkable(to_x, to_y) && (diagonal_mode == DIAGONAL_MODE_ALWAYS || _is_walkable(to_x, to_y - dy) || _is_walkable(to_x - dx, to_y))) { + if (end->id.x == to_x && end->id.y == to_y) { + return end; } - if (_jump(p_to, _get_point(to_x, to_y + dy)) != nullptr) { - return p_to; + + if ((_is_walkable(to_x - dx, to_y + dy) && !_is_walkable(to_x - dx, to_y)) || (_is_walkable(to_x + dx, to_y - dy) && !_is_walkable(to_x, to_y - dy))) { + return _get_point_unchecked(to_x, to_y); } - } else { - if (dx != 0) { - if ((_is_walkable(to_x + dx, to_y + 1) && !_is_walkable(to_x, to_y + 1)) || (_is_walkable(to_x + dx, to_y - 1) && !_is_walkable(to_x, to_y - 1))) { - return p_to; - } - } else { - if ((_is_walkable(to_x + 1, to_y + dy) && !_is_walkable(to_x + 1, to_y)) || (_is_walkable(to_x - 1, to_y + dy) && !_is_walkable(to_x - 1, to_y))) { - return p_to; - } + + if (_forced_successor(to_x + dx, to_y, dx, 0) != nullptr || _forced_successor(to_x, to_y + dy, 0, dy) != nullptr) { + return _get_point_unchecked(to_x, to_y); } + + to_x += dx; + to_y += dy; } - if (_is_walkable(to_x + dx, to_y + dy) && (diagonal_mode == DIAGONAL_MODE_ALWAYS || (_is_walkable(to_x + dx, to_y) || _is_walkable(to_x, to_y + dy)))) { - return _jump(p_to, _get_point(to_x + dx, to_y + dy)); - } + } else if (diagonal_mode == DIAGONAL_MODE_ONLY_IF_NO_OBSTACLES) { - if (dx != 0 && dy != 0) { - if ((_is_walkable(to_x + dx, to_y + dy) && !_is_walkable(to_x, to_y + dy)) || !_is_walkable(to_x + dx, to_y)) { - return p_to; - } - if (_jump(p_to, _get_point(to_x + dx, to_y)) != nullptr) { - return p_to; + if (dx == 0 || dy == 0) { + return _forced_successor(from_x, from_y, dx, dy, true); + } + + while (_is_walkable(to_x, to_y) && _is_walkable(to_x, to_y - dy) && _is_walkable(to_x - dx, to_y)) { + if (end->id.x == to_x && end->id.y == to_y) { + return end; } - if (_jump(p_to, _get_point(to_x, to_y + dy)) != nullptr) { - return p_to; + + if ((_is_walkable(to_x + dx, to_y + dy) && !_is_walkable(to_x, to_y + dy)) || !_is_walkable(to_x + dx, to_y)) { + return _get_point_unchecked(to_x, to_y); } - } else { - if (dx != 0) { - if ((_is_walkable(to_x, to_y + 1) && !_is_walkable(to_x - dx, to_y + 1)) || (_is_walkable(to_x, to_y - 1) && !_is_walkable(to_x - dx, to_y - 1))) { - return p_to; - } - } else { - if ((_is_walkable(to_x + 1, to_y) && !_is_walkable(to_x + 1, to_y - dy)) || (_is_walkable(to_x - 1, to_y) && !_is_walkable(to_x - 1, to_y - dy))) { - return p_to; - } + + if (_forced_successor(to_x, to_y, dx, 0) != nullptr || _forced_successor(to_x, to_y, 0, dy) != nullptr) { + return _get_point_unchecked(to_x, to_y); } + + to_x += dx; + to_y += dy; } - if (_is_walkable(to_x + dx, to_y + dy) && _is_walkable(to_x + dx, to_y) && _is_walkable(to_x, to_y + dy)) { - return _jump(p_to, _get_point(to_x + dx, to_y + dy)); - } + } else { // DIAGONAL_MODE_NEVER - if (dx != 0) { - if ((_is_walkable(to_x, to_y - 1) && !_is_walkable(to_x - dx, to_y - 1)) || (_is_walkable(to_x, to_y + 1) && !_is_walkable(to_x - dx, to_y + 1))) { - return p_to; + if (dy == 0) { + return _forced_successor(from_x, from_y, dx, 0, true); + } + + while (_is_walkable(to_x, to_y)) { + if (end->id.x == to_x && end->id.y == to_y) { + return end; } - } else if (dy != 0) { + if ((_is_walkable(to_x - 1, to_y) && !_is_walkable(to_x - 1, to_y - dy)) || (_is_walkable(to_x + 1, to_y) && !_is_walkable(to_x + 1, to_y - dy))) { - return p_to; + return _get_point_unchecked(to_x, to_y); } - if (_jump(p_to, _get_point(to_x + 1, to_y)) != nullptr) { - return p_to; - } - if (_jump(p_to, _get_point(to_x - 1, to_y)) != nullptr) { - return p_to; + + if (_forced_successor(to_x, to_y, 1, 0, true) != nullptr || _forced_successor(to_x, to_y, -1, 0, true) != nullptr) { + return _get_point_unchecked(to_x, to_y); } + + to_y += dy; } - return _jump(p_to, _get_point(to_x + dx, to_y + dy)); + } + + return nullptr; +} + +AStarGrid2D::Point *AStarGrid2D::_forced_successor(int32_t p_x, int32_t p_y, int32_t p_dx, int32_t p_dy, bool p_inclusive) { + // Remembering previous results can improve performance. + bool l_prev = false, r_prev = false, l = false, r = false; + + int32_t o_x = p_x, o_y = p_y; + if (p_inclusive) { + o_x += p_dx; + o_y += p_dy; + } + + int32_t l_x = p_x - p_dy, l_y = p_y - p_dx; + int32_t r_x = p_x + p_dy, r_y = p_y + p_dx; + + while (_is_walkable(o_x, o_y)) { + if (end->id.x == o_x && end->id.y == o_y) { + return end; + } + + l_prev = l || _is_walkable(l_x, l_y); + r_prev = r || _is_walkable(r_x, r_y); + + l_x += p_dx; + l_y += p_dy; + r_x += p_dx; + r_y += p_dy; + + l = _is_walkable(l_x, l_y); + r = _is_walkable(r_x, r_y); + + if ((l && !l_prev) || (r && !r_prev)) { + return _get_point_unchecked(o_x, o_y); + } + + o_x += p_dx; + o_y += p_dy; } return nullptr; } @@ -390,19 +435,19 @@ void AStarGrid2D::_get_nbors(Point *p_point, LocalVector<Point *> &r_nbors) { } } - if (top && !top->solid) { + if (top && !_get_solid_unchecked(top->id)) { r_nbors.push_back(top); ts0 = true; } - if (right && !right->solid) { + if (right && !_get_solid_unchecked(right->id)) { r_nbors.push_back(right); ts1 = true; } - if (bottom && !bottom->solid) { + if (bottom && !_get_solid_unchecked(bottom->id)) { r_nbors.push_back(bottom); ts2 = true; } - if (left && !left->solid) { + if (left && !_get_solid_unchecked(left->id)) { r_nbors.push_back(left); ts3 = true; } @@ -432,16 +477,16 @@ void AStarGrid2D::_get_nbors(Point *p_point, LocalVector<Point *> &r_nbors) { break; } - if (td0 && (top_left && !top_left->solid)) { + if (td0 && (top_left && !_get_solid_unchecked(top_left->id))) { r_nbors.push_back(top_left); } - if (td1 && (top_right && !top_right->solid)) { + if (td1 && (top_right && !_get_solid_unchecked(top_right->id))) { r_nbors.push_back(top_right); } - if (td2 && (bottom_right && !bottom_right->solid)) { + if (td2 && (bottom_right && !_get_solid_unchecked(bottom_right->id))) { r_nbors.push_back(bottom_right); } - if (td3 && (bottom_left && !bottom_left->solid)) { + if (td3 && (bottom_left && !_get_solid_unchecked(bottom_left->id))) { r_nbors.push_back(bottom_left); } } @@ -450,7 +495,7 @@ bool AStarGrid2D::_solve(Point *p_begin_point, Point *p_end_point) { last_closest_point = nullptr; pass++; - if (p_end_point->solid) { + if (_get_solid_unchecked(p_end_point->id)) { return false; } @@ -496,7 +541,7 @@ bool AStarGrid2D::_solve(Point *p_begin_point, Point *p_end_point) { continue; } } else { - if (e->solid || e->closed_pass == pass) { + if (_get_solid_unchecked(e->id) || e->closed_pass == pass) { continue; } weight_scale = e->weight_scale; @@ -531,12 +576,12 @@ bool AStarGrid2D::_solve(Point *p_begin_point, Point *p_end_point) { return found_route; } -real_t AStarGrid2D::_estimate_cost(const Vector2i &p_from_id, const Vector2i &p_to_id) { +real_t AStarGrid2D::_estimate_cost(const Vector2i &p_from_id, const Vector2i &p_end_id) { real_t scost; - if (GDVIRTUAL_CALL(_estimate_cost, p_from_id, p_to_id, scost)) { + if (GDVIRTUAL_CALL(_estimate_cost, p_from_id, p_end_id, scost)) { return scost; } - return heuristics[default_estimate_heuristic](p_from_id, p_to_id); + return heuristics[default_estimate_heuristic](p_from_id, p_end_id); } real_t AStarGrid2D::_compute_cost(const Vector2i &p_from_id, const Vector2i &p_to_id) { @@ -558,6 +603,33 @@ Vector2 AStarGrid2D::get_point_position(const Vector2i &p_id) const { return _get_point_unchecked(p_id)->pos; } +TypedArray<Dictionary> AStarGrid2D::get_point_data_in_region(const Rect2i &p_region) const { + ERR_FAIL_COND_V_MSG(dirty, TypedArray<Dictionary>(), "Grid is not initialized. Call the update method."); + const Rect2i inter_region = region.intersection(p_region); + + const int32_t start_x = inter_region.position.x - region.position.x; + const int32_t start_y = inter_region.position.y - region.position.y; + const int32_t end_x = inter_region.get_end().x - region.position.x; + const int32_t end_y = inter_region.get_end().y - region.position.y; + + TypedArray<Dictionary> data; + + for (int32_t y = start_y; y < end_y; y++) { + for (int32_t x = start_x; x < end_x; x++) { + const Point &p = points[y][x]; + + Dictionary dict; + dict["id"] = p.id; + dict["position"] = p.pos; + dict["solid"] = _get_solid_unchecked(p.id); + dict["weight_scale"] = p.weight_scale; + data.push_back(dict); + } + } + + return data; +} + Vector<Vector2> AStarGrid2D::get_point_path(const Vector2i &p_from_id, const Vector2i &p_to_id, bool p_allow_partial_path) { ERR_FAIL_COND_V_MSG(dirty, Vector<Vector2>(), "Grid is not initialized. Call the update method."); ERR_FAIL_COND_V_MSG(!is_in_boundsv(p_from_id), Vector<Vector2>(), vformat("Can't get id path. Point %s out of bounds %s.", p_from_id, region)); @@ -694,10 +766,11 @@ void AStarGrid2D::_bind_methods() { ClassDB::bind_method(D_METHOD("clear"), &AStarGrid2D::clear); ClassDB::bind_method(D_METHOD("get_point_position", "id"), &AStarGrid2D::get_point_position); + ClassDB::bind_method(D_METHOD("get_point_data_in_region", "region"), &AStarGrid2D::get_point_data_in_region); ClassDB::bind_method(D_METHOD("get_point_path", "from_id", "to_id", "allow_partial_path"), &AStarGrid2D::get_point_path, DEFVAL(false)); ClassDB::bind_method(D_METHOD("get_id_path", "from_id", "to_id", "allow_partial_path"), &AStarGrid2D::get_id_path, DEFVAL(false)); - GDVIRTUAL_BIND(_estimate_cost, "from_id", "to_id") + GDVIRTUAL_BIND(_estimate_cost, "from_id", "end_id") GDVIRTUAL_BIND(_compute_cost, "from_id", "to_id") ADD_PROPERTY(PropertyInfo(Variant::RECT2I, "region"), "set_region", "get_region"); diff --git a/core/math/a_star_grid_2d.h b/core/math/a_star_grid_2d.h index 1a9f6dcc11..f5ac472f09 100644 --- a/core/math/a_star_grid_2d.h +++ b/core/math/a_star_grid_2d.h @@ -78,7 +78,6 @@ private: struct Point { Vector2i id; - bool solid = false; Vector2 pos; real_t weight_scale = 1.0; @@ -111,6 +110,7 @@ private: } }; + LocalVector<bool> solid_mask; LocalVector<LocalVector<Point>> points; Point *end = nullptr; Point *last_closest_point = nullptr; @@ -118,11 +118,12 @@ private: uint64_t pass = 1; private: // Internal routines. + _FORCE_INLINE_ size_t _to_mask_index(int32_t p_x, int32_t p_y) const { + return ((p_y - region.position.y + 1) * (region.size.x + 2)) + p_x - region.position.x + 1; + } + _FORCE_INLINE_ bool _is_walkable(int32_t p_x, int32_t p_y) const { - if (region.has_point(Vector2i(p_x, p_y))) { - return !points[p_y - region.position.y][p_x - region.position.x].solid; - } - return false; + return !solid_mask[_to_mask_index(p_x, p_y)]; } _FORCE_INLINE_ Point *_get_point(int32_t p_x, int32_t p_y) { @@ -132,6 +133,18 @@ private: // Internal routines. return nullptr; } + _FORCE_INLINE_ void _set_solid_unchecked(int32_t p_x, int32_t p_y, bool p_solid) { + solid_mask[_to_mask_index(p_x, p_y)] = p_solid; + } + + _FORCE_INLINE_ void _set_solid_unchecked(const Vector2i &p_id, bool p_solid) { + solid_mask[_to_mask_index(p_id.x, p_id.y)] = p_solid; + } + + _FORCE_INLINE_ bool _get_solid_unchecked(const Vector2i &p_id) const { + return solid_mask[_to_mask_index(p_id.x, p_id.y)]; + } + _FORCE_INLINE_ Point *_get_point_unchecked(int32_t p_x, int32_t p_y) { return &points[p_y - region.position.y][p_x - region.position.x]; } @@ -146,12 +159,13 @@ private: // Internal routines. void _get_nbors(Point *p_point, LocalVector<Point *> &r_nbors); Point *_jump(Point *p_from, Point *p_to); + 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(); - virtual real_t _estimate_cost(const Vector2i &p_from_id, const Vector2i &p_to_id); + virtual real_t _estimate_cost(const Vector2i &p_from_id, const Vector2i &p_end_id); virtual real_t _compute_cost(const Vector2i &p_from_id, const Vector2i &p_to_id); GDVIRTUAL2RC(real_t, _estimate_cost, Vector2i, Vector2i) @@ -209,6 +223,7 @@ public: void clear(); Vector2 get_point_position(const Vector2i &p_id) const; + TypedArray<Dictionary> get_point_data_in_region(const Rect2i &p_region) const; Vector<Vector2> get_point_path(const Vector2i &p_from, const Vector2i &p_to, bool p_allow_partial_path = false); TypedArray<Vector2i> get_id_path(const Vector2i &p_from, const Vector2i &p_to, bool p_allow_partial_path = false); }; diff --git a/core/math/aabb.h b/core/math/aabb.h index cb358ca7ef..7a5581b5d4 100644 --- a/core/math/aabb.h +++ b/core/math/aabb.h @@ -85,7 +85,7 @@ struct [[nodiscard]] AABB { bool intersects_plane(const Plane &p_plane) const; _FORCE_INLINE_ bool has_point(const Vector3 &p_point) const; - _FORCE_INLINE_ Vector3 get_support(const Vector3 &p_normal) const; + _FORCE_INLINE_ Vector3 get_support(const Vector3 &p_direction) const; Vector3 get_longest_axis() const; int get_longest_axis_index() const; @@ -212,15 +212,18 @@ inline bool AABB::encloses(const AABB &p_aabb) const { (src_max.z >= dst_max.z)); } -Vector3 AABB::get_support(const Vector3 &p_normal) const { - Vector3 half_extents = size * 0.5f; - Vector3 ofs = position + half_extents; - - return Vector3( - (p_normal.x > 0) ? half_extents.x : -half_extents.x, - (p_normal.y > 0) ? half_extents.y : -half_extents.y, - (p_normal.z > 0) ? half_extents.z : -half_extents.z) + - ofs; +Vector3 AABB::get_support(const Vector3 &p_direction) const { + Vector3 support = position; + if (p_direction.x > 0.0f) { + support.x += size.x; + } + if (p_direction.y > 0.0f) { + support.y += size.y; + } + if (p_direction.z > 0.0f) { + support.z += size.z; + } + return support; } Vector3 AABB::get_endpoint(int p_point) const { diff --git a/core/math/color.h b/core/math/color.h index e17b8c9fd7..70fad78acb 100644 --- a/core/math/color.h +++ b/core/math/color.h @@ -129,33 +129,46 @@ struct [[nodiscard]] Color { } _FORCE_INLINE_ uint32_t to_rgbe9995() const { - const float pow2to9 = 512.0f; - const float B = 15.0f; - const float N = 9.0f; - - float sharedexp = 65408.000f; // Result of: ((pow2to9 - 1.0f) / pow2to9) * powf(2.0f, 31.0f - 15.0f) - - float cRed = MAX(0.0f, MIN(sharedexp, r)); - float cGreen = MAX(0.0f, MIN(sharedexp, g)); - float cBlue = MAX(0.0f, MIN(sharedexp, b)); - - float cMax = MAX(cRed, MAX(cGreen, cBlue)); - - float expp = MAX(-B - 1.0f, floor(Math::log(cMax) / (real_t)Math_LN2)) + 1.0f + B; - - float sMax = (float)floor((cMax / Math::pow(2.0f, expp - B - N)) + 0.5f); - - float exps = expp + 1.0f; - - if (0.0f <= sMax && sMax < pow2to9) { - exps = expp; - } - - float sRed = Math::floor((cRed / pow(2.0f, exps - B - N)) + 0.5f); - float sGreen = Math::floor((cGreen / pow(2.0f, exps - B - N)) + 0.5f); - float sBlue = Math::floor((cBlue / pow(2.0f, exps - B - N)) + 0.5f); - - return (uint32_t(Math::fast_ftoi(sRed)) & 0x1FF) | ((uint32_t(Math::fast_ftoi(sGreen)) & 0x1FF) << 9) | ((uint32_t(Math::fast_ftoi(sBlue)) & 0x1FF) << 18) | ((uint32_t(Math::fast_ftoi(exps)) & 0x1F) << 27); + // https://github.com/microsoft/DirectX-Graphics-Samples/blob/v10.0.19041.0/MiniEngine/Core/Color.cpp + static const float kMaxVal = float(0x1FF << 7); + static const float kMinVal = float(1.f / (1 << 16)); + + // Clamp RGB to [0, 1.FF*2^16] + const float _r = CLAMP(r, 0.0f, kMaxVal); + const float _g = CLAMP(g, 0.0f, kMaxVal); + const float _b = CLAMP(b, 0.0f, kMaxVal); + + // Compute the maximum channel, no less than 1.0*2^-15 + const float MaxChannel = MAX(MAX(_r, _g), MAX(_b, kMinVal)); + + // Take the exponent of the maximum channel (rounding up the 9th bit) and + // add 15 to it. When added to the channels, it causes the implicit '1.0' + // bit and the first 8 mantissa bits to be shifted down to the low 9 bits + // of the mantissa, rounding the truncated bits. + union { + float f; + int32_t i; + } R, G, B, E; + + E.f = MaxChannel; + E.i += 0x07804000; // Add 15 to the exponent and 0x4000 to the mantissa + E.i &= 0x7F800000; // Zero the mantissa + + // This shifts the 9-bit values we need into the lowest bits, rounding as + // needed. Note that if the channel has a smaller exponent than the max + // channel, it will shift even more. This is intentional. + R.f = _r + E.f; + G.f = _g + E.f; + B.f = _b + E.f; + + // Convert the Bias to the correct exponent in the upper 5 bits. + E.i <<= 4; + E.i += 0x10000000; + + // Combine the fields. RGB floats have unwanted data in the upper 9 + // bits. Only red needs to mask them off because green and blue shift + // it out to the left. + return E.i | (B.i << 18) | (G.i << 9) | (R.i & 511); } _FORCE_INLINE_ Color blend(const Color &p_over) const { diff --git a/core/math/expression.cpp b/core/math/expression.cpp index 636c2c16bf..0692ece1e6 100644 --- a/core/math/expression.cpp +++ b/core/math/expression.cpp @@ -30,12 +30,7 @@ #include "expression.h" -#include "core/io/marshalls.h" -#include "core/math/math_funcs.h" #include "core/object/class_db.h" -#include "core/object/ref_counted.h" -#include "core/os/os.h" -#include "core/variant/variant_parser.h" Error Expression::_get_token(Token &r_token) { while (true) { @@ -392,7 +387,6 @@ Error Expression::_get_token(Token &r_token) { if (is_digit(c)) { } else if (c == 'e') { reading = READING_EXP; - } else { reading = READING_DONE; } @@ -419,7 +413,9 @@ Error Expression::_get_token(Token &r_token) { is_first_char = false; } - str_ofs--; + if (c != 0) { + str_ofs--; + } r_token.type = TK_CONSTANT; diff --git a/core/math/math_funcs.h b/core/math/math_funcs.h index 3060f31970..1afc5f4bbb 100644 --- a/core/math/math_funcs.h +++ b/core/math/math_funcs.h @@ -105,6 +105,9 @@ public: static _ALWAYS_INLINE_ double fmod(double p_x, double p_y) { return ::fmod(p_x, p_y); } static _ALWAYS_INLINE_ float fmod(float p_x, float p_y) { return ::fmodf(p_x, p_y); } + static _ALWAYS_INLINE_ double modf(double p_x, double *r_y) { return ::modf(p_x, r_y); } + static _ALWAYS_INLINE_ float modf(float p_x, float *r_y) { return ::modff(p_x, r_y); } + static _ALWAYS_INLINE_ double floor(double p_x) { return ::floor(p_x); } static _ALWAYS_INLINE_ float floor(float p_x) { return ::floorf(p_x); } @@ -447,14 +450,22 @@ public: static _ALWAYS_INLINE_ double smoothstep(double p_from, double p_to, double p_s) { if (is_equal_approx(p_from, p_to)) { - return p_from; + if (likely(p_from <= p_to)) { + return p_s <= p_from ? 0.0 : 1.0; + } else { + return p_s <= p_to ? 1.0 : 0.0; + } } double s = CLAMP((p_s - p_from) / (p_to - p_from), 0.0, 1.0); return s * s * (3.0 - 2.0 * s); } static _ALWAYS_INLINE_ float smoothstep(float p_from, float p_to, float p_s) { if (is_equal_approx(p_from, p_to)) { - return p_from; + if (likely(p_from <= p_to)) { + return p_s <= p_from ? 0.0f : 1.0f; + } else { + return p_s <= p_to ? 1.0f : 0.0f; + } } float s = CLAMP((p_s - p_from) / (p_to - p_from), 0.0f, 1.0f); return s * s * (3.0f - 2.0f * s); diff --git a/core/math/random_pcg.cpp b/core/math/random_pcg.cpp index 55787a0b57..c286a60421 100644 --- a/core/math/random_pcg.cpp +++ b/core/math/random_pcg.cpp @@ -60,6 +60,11 @@ int64_t RandomPCG::rand_weighted(const Vector<float> &p_weights) { } } + for (int64_t i = weights_size - 1; i >= 0; --i) { + if (weights[i] > 0) { + return i; + } + } return -1; } diff --git a/core/math/rect2.h b/core/math/rect2.h index 9cb341b689..817923c134 100644 --- a/core/math/rect2.h +++ b/core/math/rect2.h @@ -285,13 +285,15 @@ struct [[nodiscard]] Rect2 { return Rect2(position.round(), size.round()); } - Vector2 get_support(const Vector2 &p_normal) const { - Vector2 half_extents = size * 0.5f; - Vector2 ofs = position + half_extents; - return Vector2( - (p_normal.x > 0) ? -half_extents.x : half_extents.x, - (p_normal.y > 0) ? -half_extents.y : half_extents.y) + - ofs; + Vector2 get_support(const Vector2 &p_direction) const { + Vector2 support = position; + if (p_direction.x > 0.0f) { + support.x += size.x; + } + if (p_direction.y > 0.0f) { + support.y += size.y; + } + return support; } _FORCE_INLINE_ bool intersects_filled_polygon(const Vector2 *p_points, int p_point_count) const { diff --git a/core/math/transform_interpolator.cpp b/core/math/transform_interpolator.cpp index 7cfe880b5a..1cd35b3d1a 100644 --- a/core/math/transform_interpolator.cpp +++ b/core/math/transform_interpolator.cpp @@ -31,46 +31,354 @@ #include "transform_interpolator.h" #include "core/math/transform_2d.h" +#include "core/math/transform_3d.h" void TransformInterpolator::interpolate_transform_2d(const Transform2D &p_prev, const Transform2D &p_curr, Transform2D &r_result, real_t p_fraction) { - // Extract parameters. - Vector2 p1 = p_prev.get_origin(); - Vector2 p2 = p_curr.get_origin(); - // Special case for physics interpolation, if flipping, don't interpolate basis. // If the determinant polarity changes, the handedness of the coordinate system changes. if (_sign(p_prev.determinant()) != _sign(p_curr.determinant())) { r_result.columns[0] = p_curr.columns[0]; r_result.columns[1] = p_curr.columns[1]; - r_result.set_origin(p1.lerp(p2, p_fraction)); + r_result.set_origin(p_prev.get_origin().lerp(p_curr.get_origin(), p_fraction)); return; } - real_t r1 = p_prev.get_rotation(); - real_t r2 = p_curr.get_rotation(); + r_result = p_prev.interpolate_with(p_curr, p_fraction); +} + +void TransformInterpolator::interpolate_transform_3d(const Transform3D &p_prev, const Transform3D &p_curr, Transform3D &r_result, real_t p_fraction) { + r_result.origin = p_prev.origin + ((p_curr.origin - p_prev.origin) * p_fraction); + interpolate_basis(p_prev.basis, p_curr.basis, r_result.basis, p_fraction); +} + +void TransformInterpolator::interpolate_basis(const Basis &p_prev, const Basis &p_curr, Basis &r_result, real_t p_fraction) { + Method method = find_method(p_prev, p_curr); + interpolate_basis_via_method(p_prev, p_curr, r_result, p_fraction, method); +} + +void TransformInterpolator::interpolate_transform_3d_via_method(const Transform3D &p_prev, const Transform3D &p_curr, Transform3D &r_result, real_t p_fraction, Method p_method) { + r_result.origin = p_prev.origin + ((p_curr.origin - p_prev.origin) * p_fraction); + interpolate_basis_via_method(p_prev.basis, p_curr.basis, r_result.basis, p_fraction, p_method); +} + +void TransformInterpolator::interpolate_basis_via_method(const Basis &p_prev, const Basis &p_curr, Basis &r_result, real_t p_fraction, Method p_method) { + switch (p_method) { + default: { + interpolate_basis_linear(p_prev, p_curr, r_result, p_fraction); + } break; + case INTERP_SLERP: { + r_result = _basis_slerp_unchecked(p_prev, p_curr, p_fraction); + } break; + case INTERP_SCALED_SLERP: { + interpolate_basis_scaled_slerp(p_prev, p_curr, r_result, p_fraction); + } break; + } +} + +Quaternion TransformInterpolator::_basis_to_quat_unchecked(const Basis &p_basis) { + Basis m = p_basis; + real_t trace = m.rows[0][0] + m.rows[1][1] + m.rows[2][2]; + real_t temp[4]; + + if (trace > 0.0) { + real_t s = Math::sqrt(trace + 1.0f); + temp[3] = (s * 0.5f); + s = 0.5f / s; + + temp[0] = ((m.rows[2][1] - m.rows[1][2]) * s); + temp[1] = ((m.rows[0][2] - m.rows[2][0]) * s); + temp[2] = ((m.rows[1][0] - m.rows[0][1]) * s); + } else { + int i = m.rows[0][0] < m.rows[1][1] + ? (m.rows[1][1] < m.rows[2][2] ? 2 : 1) + : (m.rows[0][0] < m.rows[2][2] ? 2 : 0); + int j = (i + 1) % 3; + int k = (i + 2) % 3; - Size2 s1 = p_prev.get_scale(); - Size2 s2 = p_curr.get_scale(); + real_t s = Math::sqrt(m.rows[i][i] - m.rows[j][j] - m.rows[k][k] + 1.0f); + temp[i] = s * 0.5f; + s = 0.5f / s; - // Slerp rotation. - Vector2 v1(Math::cos(r1), Math::sin(r1)); - Vector2 v2(Math::cos(r2), Math::sin(r2)); + temp[3] = (m.rows[k][j] - m.rows[j][k]) * s; + temp[j] = (m.rows[j][i] + m.rows[i][j]) * s; + temp[k] = (m.rows[k][i] + m.rows[i][k]) * s; + } - real_t dot = v1.dot(v2); + return Quaternion(temp[0], temp[1], temp[2], temp[3]); +} - dot = CLAMP(dot, -1, 1); +Quaternion TransformInterpolator::_quat_slerp_unchecked(const Quaternion &p_from, const Quaternion &p_to, real_t p_fraction) { + Quaternion to1; + real_t omega, cosom, sinom, scale0, scale1; - Vector2 v; + // Calculate cosine. + cosom = p_from.dot(p_to); - if (dot > 0.9995f) { - v = v1.lerp(v2, p_fraction).normalized(); // Linearly interpolate to avoid numerical precision issues. + // Adjust signs (if necessary) + if (cosom < 0.0f) { + cosom = -cosom; + to1.x = -p_to.x; + to1.y = -p_to.y; + to1.z = -p_to.z; + to1.w = -p_to.w; } else { - real_t angle = p_fraction * Math::acos(dot); - Vector2 v3 = (v2 - v1 * dot).normalized(); - v = v1 * Math::cos(angle) + v3 * Math::sin(angle); + to1.x = p_to.x; + to1.y = p_to.y; + to1.z = p_to.z; + to1.w = p_to.w; + } + + // Calculate coefficients. + + // This check could possibly be removed as we dealt with this + // case in the find_method() function, but is left for safety, it probably + // isn't a bottleneck. + if ((1.0f - cosom) > (real_t)CMP_EPSILON) { + // standard case (slerp) + omega = Math::acos(cosom); + sinom = Math::sin(omega); + scale0 = Math::sin((1.0f - p_fraction) * omega) / sinom; + scale1 = Math::sin(p_fraction * omega) / sinom; + } else { + // "from" and "to" quaternions are very close + // ... so we can do a linear interpolation + scale0 = 1.0f - p_fraction; + scale1 = p_fraction; + } + // Calculate final values. + return Quaternion( + scale0 * p_from.x + scale1 * to1.x, + scale0 * p_from.y + scale1 * to1.y, + scale0 * p_from.z + scale1 * to1.z, + scale0 * p_from.w + scale1 * to1.w); +} + +Basis TransformInterpolator::_basis_slerp_unchecked(Basis p_from, Basis p_to, real_t p_fraction) { + Quaternion from = _basis_to_quat_unchecked(p_from); + Quaternion to = _basis_to_quat_unchecked(p_to); + + Basis b(_quat_slerp_unchecked(from, to, p_fraction)); + return b; +} + +void TransformInterpolator::interpolate_basis_scaled_slerp(Basis p_prev, Basis p_curr, Basis &r_result, real_t p_fraction) { + // Normalize both and find lengths. + Vector3 lengths_prev = _basis_orthonormalize(p_prev); + Vector3 lengths_curr = _basis_orthonormalize(p_curr); + + r_result = _basis_slerp_unchecked(p_prev, p_curr, p_fraction); + + // Now the result is unit length basis, we need to scale. + Vector3 lengths_lerped = lengths_prev + ((lengths_curr - lengths_prev) * p_fraction); + + // Keep a note that the column / row order of the basis is weird, + // so keep an eye for bugs with this. + r_result[0] *= lengths_lerped; + r_result[1] *= lengths_lerped; + r_result[2] *= lengths_lerped; +} + +void TransformInterpolator::interpolate_basis_linear(const Basis &p_prev, const Basis &p_curr, Basis &r_result, real_t p_fraction) { + // Interpolate basis. + r_result = p_prev.lerp(p_curr, p_fraction); + + // It turns out we need to guard against zero scale basis. + // This is kind of silly, as we should probably fix the bugs elsewhere in Godot that can't deal with + // zero scale, but until that time... + for (int n = 0; n < 3; n++) { + Vector3 &axis = r_result[n]; + + // Not ok, this could cause errors due to bugs elsewhere, + // so we will bodge set this to a small value. + const real_t smallest = 0.0001f; + const real_t smallest_squared = smallest * smallest; + if (axis.length_squared() < smallest_squared) { + // Setting a different component to the smallest + // helps prevent the situation where all the axes are pointing in the same direction, + // which could be a problem for e.g. cross products... + axis[n] = smallest; + } + } +} + +// Returns length. +real_t TransformInterpolator::_vec3_normalize(Vector3 &p_vec) { + real_t lengthsq = p_vec.length_squared(); + if (lengthsq == 0.0f) { + p_vec.x = p_vec.y = p_vec.z = 0.0f; + return 0.0f; + } + real_t length = Math::sqrt(lengthsq); + p_vec.x /= length; + p_vec.y /= length; + p_vec.z /= length; + return length; +} + +// Returns lengths. +Vector3 TransformInterpolator::_basis_orthonormalize(Basis &r_basis) { + // Gram-Schmidt Process. + + Vector3 x = r_basis.get_column(0); + Vector3 y = r_basis.get_column(1); + Vector3 z = r_basis.get_column(2); + + Vector3 lengths; + + lengths.x = _vec3_normalize(x); + y = (y - x * (x.dot(y))); + lengths.y = _vec3_normalize(y); + z = (z - x * (x.dot(z)) - y * (y.dot(z))); + lengths.z = _vec3_normalize(z); + + r_basis.set_column(0, x); + r_basis.set_column(1, y); + r_basis.set_column(2, z); + + return lengths; +} + +TransformInterpolator::Method TransformInterpolator::_test_basis(Basis p_basis, bool r_needed_normalize, Quaternion &r_quat) { + // Axis lengths. + Vector3 al = Vector3(p_basis.get_column(0).length_squared(), + p_basis.get_column(1).length_squared(), + p_basis.get_column(2).length_squared()); + + // Non unit scale? + if (r_needed_normalize || !_vec3_is_equal_approx(al, Vector3(1.0, 1.0, 1.0), (real_t)0.001f)) { + // If the basis is not normalized (at least approximately), it will fail the checks needed for slerp. + // So we try to detect a scaled (but not sheared) basis, which we *can* slerp by normalizing first, + // and lerping the scales separately. + + // If any of the axes are really small, it is unlikely to be a valid rotation, or is scaled too small to deal with float error. + const real_t sl_epsilon = 0.00001f; + if ((al.x < sl_epsilon) || + (al.y < sl_epsilon) || + (al.z < sl_epsilon)) { + return INTERP_LERP; + } + + // Normalize the basis. + Basis norm_basis = p_basis; + + al.x = Math::sqrt(al.x); + al.y = Math::sqrt(al.y); + al.z = Math::sqrt(al.z); + + norm_basis.set_column(0, norm_basis.get_column(0) / al.x); + norm_basis.set_column(1, norm_basis.get_column(1) / al.y); + norm_basis.set_column(2, norm_basis.get_column(2) / al.z); + + // This doesn't appear necessary, as the later checks will catch it. + // if (!_basis_is_orthogonal_any_scale(norm_basis)) { + // return INTERP_LERP; + // } + + p_basis = norm_basis; + + // Orthonormalize not necessary as normal normalization(!) works if the + // axes are orthonormal. + // p_basis.orthonormalize(); + + // If we needed to normalize one of the two bases, we will need to normalize both, + // regardless of whether the 2nd needs it, just to make sure it takes the path to return + // INTERP_SCALED_LERP on the 2nd call of _test_basis. + r_needed_normalize = true; + } + + // Apply less stringent tests than the built in slerp, the standard Godot slerp + // is too susceptible to float error to be useful. + real_t det = p_basis.determinant(); + if (!Math::is_equal_approx(det, 1, (real_t)0.01f)) { + return INTERP_LERP; + } + + if (!_basis_is_orthogonal(p_basis)) { + return INTERP_LERP; + } + + // TODO: This could possibly be less stringent too, check this. + r_quat = _basis_to_quat_unchecked(p_basis); + if (!r_quat.is_normalized()) { + return INTERP_LERP; + } + + return r_needed_normalize ? INTERP_SCALED_SLERP : INTERP_SLERP; +} + +// This check doesn't seem to be needed but is preserved in case of bugs. +bool TransformInterpolator::_basis_is_orthogonal_any_scale(const Basis &p_basis) { + Vector3 cross = p_basis.get_column(0).cross(p_basis.get_column(1)); + real_t l = _vec3_normalize(cross); + // Too small numbers, revert to lerp. + if (l < 0.001f) { + return false; + } + + const real_t epsilon = 0.9995f; + + real_t dot = cross.dot(p_basis.get_column(2)); + if (dot < epsilon) { + return false; + } + + cross = p_basis.get_column(1).cross(p_basis.get_column(2)); + l = _vec3_normalize(cross); + // Too small numbers, revert to lerp. + if (l < 0.001f) { + return false; + } + + dot = cross.dot(p_basis.get_column(0)); + if (dot < epsilon) { + return false; + } + + return true; +} + +bool TransformInterpolator::_basis_is_orthogonal(const Basis &p_basis, real_t p_epsilon) { + Basis identity; + Basis m = p_basis * p_basis.transposed(); + + // Less stringent tests than the standard Godot slerp. + if (!_vec3_is_equal_approx(m[0], identity[0], p_epsilon) || !_vec3_is_equal_approx(m[1], identity[1], p_epsilon) || !_vec3_is_equal_approx(m[2], identity[2], p_epsilon)) { + return false; + } + return true; +} + +real_t TransformInterpolator::checksum_transform_3d(const Transform3D &p_transform) { + // just a really basic checksum, this can probably be improved + real_t sum = _vec3_sum(p_transform.origin); + sum -= _vec3_sum(p_transform.basis.rows[0]); + sum += _vec3_sum(p_transform.basis.rows[1]); + sum -= _vec3_sum(p_transform.basis.rows[2]); + return sum; +} + +TransformInterpolator::Method TransformInterpolator::find_method(const Basis &p_a, const Basis &p_b) { + bool needed_normalize = false; + + Quaternion q0; + Method method = _test_basis(p_a, needed_normalize, q0); + if (method == INTERP_LERP) { + return method; + } + + Quaternion q1; + method = _test_basis(p_b, needed_normalize, q1); + if (method == INTERP_LERP) { + return method; + } + + // Are they close together? + // Apply the same test that will revert to lerp as is present in the slerp routine. + // Calculate cosine. + real_t cosom = Math::abs(q0.dot(q1)); + if ((1.0f - cosom) <= (real_t)CMP_EPSILON) { + return INTERP_LERP; } - // Construct matrix. - r_result = Transform2D(Math::atan2(v.y, v.x), p1.lerp(p2, p_fraction)); - r_result.scale_basis(s1.lerp(s2, p_fraction)); + return method; } diff --git a/core/math/transform_interpolator.h b/core/math/transform_interpolator.h index a9bce2bd7f..cc556707e4 100644 --- a/core/math/transform_interpolator.h +++ b/core/math/transform_interpolator.h @@ -32,15 +32,64 @@ #define TRANSFORM_INTERPOLATOR_H #include "core/math/math_defs.h" +#include "core/math/vector3.h" + +// Keep all the functions for fixed timestep interpolation together. +// There are two stages involved: +// Finding a method, for determining the interpolation method between two +// keyframes (which are physics ticks). +// And applying that pre-determined method. + +// Pre-determining the method makes sense because it is expensive and often +// several frames may occur between each physics tick, which will make it cheaper +// than performing every frame. struct Transform2D; +struct Transform3D; +struct Basis; +struct Quaternion; class TransformInterpolator { +public: + enum Method { + INTERP_LERP, + INTERP_SLERP, + INTERP_SCALED_SLERP, + }; + private: - static bool _sign(real_t p_val) { return p_val >= 0; } + _FORCE_INLINE_ static bool _sign(real_t p_val) { return p_val >= 0; } + static real_t _vec3_sum(const Vector3 &p_pt) { return p_pt.x + p_pt.y + p_pt.z; } + static real_t _vec3_normalize(Vector3 &p_vec); + _FORCE_INLINE_ static bool _vec3_is_equal_approx(const Vector3 &p_a, const Vector3 &p_b, real_t p_tolerance) { + return Math::is_equal_approx(p_a.x, p_b.x, p_tolerance) && Math::is_equal_approx(p_a.y, p_b.y, p_tolerance) && Math::is_equal_approx(p_a.z, p_b.z, p_tolerance); + } + static Vector3 _basis_orthonormalize(Basis &r_basis); + static Method _test_basis(Basis p_basis, bool r_needed_normalize, Quaternion &r_quat); + static Basis _basis_slerp_unchecked(Basis p_from, Basis p_to, real_t p_fraction); + static Quaternion _quat_slerp_unchecked(const Quaternion &p_from, const Quaternion &p_to, real_t p_fraction); + static Quaternion _basis_to_quat_unchecked(const Basis &p_basis); + static bool _basis_is_orthogonal(const Basis &p_basis, real_t p_epsilon = 0.01f); + static bool _basis_is_orthogonal_any_scale(const Basis &p_basis); + + static void interpolate_basis_linear(const Basis &p_prev, const Basis &p_curr, Basis &r_result, real_t p_fraction); + static void interpolate_basis_scaled_slerp(Basis p_prev, Basis p_curr, Basis &r_result, real_t p_fraction); public: static void interpolate_transform_2d(const Transform2D &p_prev, const Transform2D &p_curr, Transform2D &r_result, real_t p_fraction); + + // Generic functions, use when you don't know what method should be used, e.g. from GDScript. + // These will be slower. + static void interpolate_transform_3d(const Transform3D &p_prev, const Transform3D &p_curr, Transform3D &r_result, real_t p_fraction); + static void interpolate_basis(const Basis &p_prev, const Basis &p_curr, Basis &r_result, real_t p_fraction); + + // Optimized function when you know ahead of time the method. + static void interpolate_transform_3d_via_method(const Transform3D &p_prev, const Transform3D &p_curr, Transform3D &r_result, real_t p_fraction, Method p_method); + static void interpolate_basis_via_method(const Basis &p_prev, const Basis &p_curr, Basis &r_result, real_t p_fraction, Method p_method); + + static real_t checksum_transform_3d(const Transform3D &p_transform); + + static Method find_method(const Basis &p_a, const Basis &p_b); }; #endif // TRANSFORM_INTERPOLATOR_H diff --git a/core/object/class_db.cpp b/core/object/class_db.cpp index fe4345aa0d..9826d73a9d 100644 --- a/core/object/class_db.cpp +++ b/core/object/class_db.cpp @@ -76,6 +76,21 @@ class PlaceholderExtensionInstance { StringName class_name; HashMap<StringName, Variant> properties; + // Checks if a property is from a runtime class, and not a non-runtime base class. + bool is_runtime_property(const StringName &p_property_name) { + StringName current_class_name = class_name; + + while (ClassDB::is_class_runtime(current_class_name)) { + if (ClassDB::has_property(current_class_name, p_property_name, true)) { + return true; + } + + current_class_name = ClassDB::get_parent_class(current_class_name); + } + + return false; + } + public: PlaceholderExtensionInstance(const StringName &p_class_name) { class_name = p_class_name; @@ -83,27 +98,24 @@ public: ~PlaceholderExtensionInstance() {} - void set(const StringName &p_name, const Variant &p_value) { - bool is_default_valid = false; - Variant default_value = ClassDB::class_get_default_property_value(class_name, p_name, &is_default_valid); - - // If there's a default value, then we know it's a valid property. - if (is_default_valid) { + void set(const StringName &p_name, const Variant &p_value, bool &r_valid) { + r_valid = is_runtime_property(p_name); + if (r_valid) { properties[p_name] = p_value; } } - Variant get(const StringName &p_name) { + Variant get(const StringName &p_name, bool &r_valid) { const Variant *value = properties.getptr(p_name); Variant ret; if (value) { ret = *value; + r_valid = true; } else { - bool is_default_valid = false; - Variant default_value = ClassDB::class_get_default_property_value(class_name, p_name, &is_default_valid); - if (is_default_valid) { - ret = default_value; + r_valid = is_runtime_property(p_name); + if (r_valid) { + ret = ClassDB::class_get_default_property_value(class_name, p_name); } } @@ -115,10 +127,10 @@ public: const StringName &name = *(StringName *)p_name; const Variant &value = *(const Variant *)p_value; - self->set(name, value); + bool valid = false; + self->set(name, value, valid); - // We have to return true so Godot doesn't try to call the real setter function. - return true; + return valid; } static GDExtensionBool placeholder_instance_get(GDExtensionClassInstancePtr p_instance, GDExtensionConstStringNamePtr p_name, GDExtensionVariantPtr r_ret) { @@ -126,10 +138,10 @@ public: const StringName &name = *(StringName *)p_name; Variant *value = (Variant *)r_ret; - *value = self->get(name); + bool valid = false; + *value = self->get(name, valid); - // We have to return true so Godot doesn't try to call the real getter function. - return true; + return valid; } static const GDExtensionPropertyInfo *placeholder_instance_get_property_list(GDExtensionClassInstancePtr p_instance, uint32_t *r_count) { @@ -169,18 +181,18 @@ public: return 0; } - static GDExtensionObjectPtr placeholder_class_create_instance(void *p_class_userdata) { + static GDExtensionObjectPtr placeholder_class_create_instance(void *p_class_userdata, GDExtensionBool p_notify_postinitialize) { ClassDB::ClassInfo *ti = (ClassDB::ClassInfo *)p_class_userdata; - // Find the closest native parent. + // Find the closest native parent, that isn't a runtime class. ClassDB::ClassInfo *native_parent = ti->inherits_ptr; - while (native_parent->gdextension) { + while (native_parent->gdextension || native_parent->is_runtime) { native_parent = native_parent->inherits_ptr; } ERR_FAIL_NULL_V(native_parent->creation_func, nullptr); // Construct a placeholder. - Object *obj = native_parent->creation_func(); + Object *obj = native_parent->creation_func(static_cast<bool>(p_notify_postinitialize)); // ClassDB::set_object_extension_instance() won't be called for placeholders. // We need need to make sure that all the things it would have done (even if @@ -215,16 +227,12 @@ public: #endif bool ClassDB::_is_parent_class(const StringName &p_class, const StringName &p_inherits) { - if (!classes.has(p_class)) { - return false; - } - - StringName inherits = p_class; - while (inherits.operator String().length()) { - if (inherits == p_inherits) { + ClassInfo *c = classes.getptr(p_class); + while (c) { + if (c->name == p_inherits) { return true; } - inherits = _get_parent_class(inherits); + c = c->inherits_ptr; } return false; @@ -259,6 +267,22 @@ void ClassDB::get_extensions_class_list(List<StringName> *p_classes) { p_classes->sort_custom<StringName::AlphCompare>(); } + +void ClassDB::get_extension_class_list(const Ref<GDExtension> &p_extension, List<StringName> *p_classes) { + OBJTYPE_RLOCK; + + for (const KeyValue<StringName, ClassInfo> &E : classes) { + if (E.value.api != API_EXTENSION && E.value.api != API_EDITOR_EXTENSION) { + continue; + } + if (!E.value.gdextension || E.value.gdextension->library != p_extension.ptr()) { + continue; + } + p_classes->push_back(E.key); + } + + p_classes->sort_custom<StringName::AlphCompare>(); +} #endif void ClassDB::get_inheriters_from_class(const StringName &p_class, List<StringName> *p_classes) { @@ -291,6 +315,29 @@ StringName ClassDB::get_parent_class_nocheck(const StringName &p_class) { return ti->inherits; } +bool ClassDB::get_inheritance_chain_nocheck(const StringName &p_class, Vector<StringName> &r_result) { + OBJTYPE_RLOCK; + + ClassInfo *start = classes.getptr(p_class); + if (!start) { + return false; + } + + int classes_to_add = 0; + for (ClassInfo *ti = start; ti; ti = ti->inherits_ptr) { + classes_to_add++; + } + + int64_t old_size = r_result.size(); + r_result.resize(old_size + classes_to_add); + StringName *w = r_result.ptrw() + old_size; + for (ClassInfo *ti = start; ti; ti = ti->inherits_ptr) { + *w++ = ti->name; + } + + return true; +} + StringName ClassDB::get_compatibility_remapped_class(const StringName &p_class) { if (classes.has(p_class)) { return p_class; @@ -490,12 +537,12 @@ StringName ClassDB::get_compatibility_class(const StringName &p_class) { return StringName(); } -Object *ClassDB::_instantiate_internal(const StringName &p_class, bool p_require_real_class) { +Object *ClassDB::_instantiate_internal(const StringName &p_class, bool p_require_real_class, bool p_notify_postinitialize) { ClassInfo *ti; { OBJTYPE_RLOCK; ti = classes.getptr(p_class); - if (!ti || ti->disabled || !ti->creation_func || (ti->gdextension && !ti->gdextension->create_instance)) { + if (!_can_instantiate(ti)) { if (compat_classes.has(p_class)) { ti = classes.getptr(compat_classes[p_class]); } @@ -504,34 +551,78 @@ Object *ClassDB::_instantiate_internal(const StringName &p_class, bool p_require ERR_FAIL_COND_V_MSG(ti->disabled, nullptr, "Class '" + String(p_class) + "' is disabled."); ERR_FAIL_NULL_V_MSG(ti->creation_func, nullptr, "Class '" + String(p_class) + "' or its base class cannot be instantiated."); } + #ifdef TOOLS_ENABLED if ((ti->api == API_EDITOR || ti->api == API_EDITOR_EXTENSION) && !Engine::get_singleton()->is_editor_hint()) { ERR_PRINT("Class '" + String(p_class) + "' can only be instantiated by editor."); return nullptr; } #endif - if (ti->gdextension && ti->gdextension->create_instance) { - ObjectGDExtension *extension = ti->gdextension; -#ifdef TOOLS_ENABLED - if (!p_require_real_class && ti->is_runtime && Engine::get_singleton()->is_editor_hint()) { - extension = get_placeholder_extension(ti->name); - } -#endif - return (Object *)extension->create_instance(extension->class_userdata); - } else { + #ifdef TOOLS_ENABLED - if (!p_require_real_class && ti->is_runtime && Engine::get_singleton()->is_editor_hint()) { - if (!ti->inherits_ptr || !ti->inherits_ptr->creation_func) { - ERR_PRINT(vformat("Cannot make a placeholder instance of runtime class %s because its parent cannot be constructed.", ti->name)); - } else { - ObjectGDExtension *extension = get_placeholder_extension(ti->name); - return (Object *)extension->create_instance(extension->class_userdata); + // Try to create placeholder. + if (!p_require_real_class && ti->is_runtime && Engine::get_singleton()->is_editor_hint()) { + bool can_create_placeholder = false; + if (ti->gdextension) { + if (ti->gdextension->create_instance2) { + can_create_placeholder = true; + } +#ifndef DISABLE_DEPRECATED + else if (ti->gdextension->create_instance) { + can_create_placeholder = true; } +#endif // DISABLE_DEPRECATED + } else if (!ti->inherits_ptr || !ti->inherits_ptr->creation_func) { + ERR_PRINT(vformat("Cannot make a placeholder instance of runtime class %s because its parent cannot be constructed.", ti->name)); + } else { + can_create_placeholder = true; } -#endif - return ti->creation_func(); + if (can_create_placeholder) { + ObjectGDExtension *extension = get_placeholder_extension(ti->name); + return (Object *)extension->create_instance2(extension->class_userdata, p_notify_postinitialize); + } } +#endif // TOOLS_ENABLED + + if (ti->gdextension && ti->gdextension->create_instance2) { + ObjectGDExtension *extension = ti->gdextension; + return (Object *)extension->create_instance2(extension->class_userdata, p_notify_postinitialize); + } +#ifndef DISABLE_DEPRECATED + else if (ti->gdextension && ti->gdextension->create_instance) { + ObjectGDExtension *extension = ti->gdextension; + return (Object *)extension->create_instance(extension->class_userdata); + } +#endif // DISABLE_DEPRECATED + else { + return ti->creation_func(p_notify_postinitialize); + } +} + +bool ClassDB::_can_instantiate(ClassInfo *p_class_info) { + if (!p_class_info) { + return false; + } + + if (p_class_info->disabled || !p_class_info->creation_func) { + return false; + } + + if (!p_class_info->gdextension) { + return true; + } + + if (p_class_info->gdextension->create_instance2) { + return true; + } + +#ifndef DISABLE_DEPRECATED + if (p_class_info->gdextension->create_instance) { + return true; + } +#endif // DISABLE_DEPRECATED + return false; } Object *ClassDB::instantiate(const StringName &p_class) { @@ -542,6 +633,10 @@ Object *ClassDB::instantiate_no_placeholders(const StringName &p_class) { return _instantiate_internal(p_class, true); } +Object *ClassDB::instantiate_without_postinitialization(const StringName &p_class) { + return _instantiate_internal(p_class, true, false); +} + #ifdef TOOLS_ENABLED ObjectGDExtension *ClassDB::get_placeholder_extension(const StringName &p_class) { ObjectGDExtension *placeholder_extension = placeholder_extensions.getptr(p_class); @@ -553,7 +648,7 @@ ObjectGDExtension *ClassDB::get_placeholder_extension(const StringName &p_class) { OBJTYPE_RLOCK; ti = classes.getptr(p_class); - if (!ti || ti->disabled || !ti->creation_func || (ti->gdextension && !ti->gdextension->create_instance)) { + if (!_can_instantiate(ti)) { if (compat_classes.has(p_class)) { ti = classes.getptr(compat_classes[p_class]); } @@ -614,7 +709,10 @@ ObjectGDExtension *ClassDB::get_placeholder_extension(const StringName &p_class) placeholder_extension->get_rid = &PlaceholderExtensionInstance::placeholder_instance_get_rid; placeholder_extension->class_userdata = ti; - placeholder_extension->create_instance = &PlaceholderExtensionInstance::placeholder_class_create_instance; +#ifndef DISABLE_DEPRECATED + placeholder_extension->create_instance = nullptr; +#endif // DISABLE_DEPRECATED + placeholder_extension->create_instance2 = &PlaceholderExtensionInstance::placeholder_class_create_instance; placeholder_extension->free_instance = &PlaceholderExtensionInstance::placeholder_class_free_instance; placeholder_extension->get_virtual = &PlaceholderExtensionInstance::placeholder_class_get_virtual; placeholder_extension->get_virtual_call_data = nullptr; @@ -631,7 +729,7 @@ void ClassDB::set_object_extension_instance(Object *p_object, const StringName & { OBJTYPE_RLOCK; ti = classes.getptr(p_class); - if (!ti || ti->disabled || !ti->creation_func || (ti->gdextension && !ti->gdextension->create_instance)) { + if (!_can_instantiate(ti)) { if (compat_classes.has(p_class)) { ti = classes.getptr(compat_classes[p_class]); } @@ -668,7 +766,33 @@ bool ClassDB::can_instantiate(const StringName &p_class) { return false; } #endif - return (!ti->disabled && ti->creation_func != nullptr && !(ti->gdextension && !ti->gdextension->create_instance)); + return _can_instantiate(ti); +} + +bool ClassDB::is_abstract(const StringName &p_class) { + OBJTYPE_RLOCK; + + ClassInfo *ti = classes.getptr(p_class); + if (!ti) { + if (!ScriptServer::is_global_class(p_class)) { + ERR_FAIL_V_MSG(false, "Cannot get class '" + String(p_class) + "'."); + } + String path = ScriptServer::get_global_class_path(p_class); + Ref<Script> scr = ResourceLoader::load(path); + return scr.is_valid() && scr->is_valid() && scr->is_abstract(); + } + + if (ti->creation_func != nullptr) { + return false; + } + if (!ti->gdextension) { + return true; + } +#ifndef DISABLE_DEPRECATED + return ti->gdextension->create_instance2 == nullptr && ti->gdextension->create_instance == nullptr; +#else + return ti->gdextension->create_instance2 == nullptr; +#endif // DISABLE_DEPRECATED } bool ClassDB::is_virtual(const StringName &p_class) { @@ -688,7 +812,7 @@ bool ClassDB::is_virtual(const StringName &p_class) { return false; } #endif - return (!ti->disabled && ti->creation_func != nullptr && !(ti->gdextension && !ti->gdextension->create_instance) && ti->is_virtual); + return (_can_instantiate(ti) && ti->is_virtual); } void ClassDB::_add_class2(const StringName &p_class, const StringName &p_inherits) { @@ -1520,14 +1644,16 @@ bool ClassDB::get_property(Object *p_object, const StringName &p_property, Varia Variant index = psg->index; const Variant *arg[1] = { &index }; Callable::CallError ce; - r_value = p_object->callp(psg->getter, arg, 1, ce); + const Variant value = p_object->callp(psg->getter, arg, 1, ce); + r_value = (ce.error == Callable::CallError::CALL_OK) ? value : Variant(); } else { Callable::CallError ce; if (psg->_getptr) { r_value = psg->_getptr->call(p_object, nullptr, 0, ce); } else { - r_value = p_object->callp(psg->getter, nullptr, 0, ce); + const Variant value = p_object->callp(psg->getter, nullptr, 0, ce); + r_value = (ce.error == Callable::CallError::CALL_OK) ? value : Variant(); } } return true; @@ -1952,6 +2078,14 @@ bool ClassDB::is_class_reloadable(const StringName &p_class) { return ti->reloadable; } +bool ClassDB::is_class_runtime(const StringName &p_class) { + OBJTYPE_RLOCK; + + ClassInfo *ti = classes.getptr(p_class); + ERR_FAIL_NULL_V_MSG(ti, false, "Cannot get class '" + String(p_class) + "'."); + return ti->is_runtime; +} + void ClassDB::add_resource_base_extension(const StringName &p_extension, const StringName &p_class) { if (resource_base_extensions.has(p_extension)) { return; @@ -2063,6 +2197,11 @@ void ClassDB::register_extension_class(ObjectGDExtension *p_extension) { ClassInfo *parent = classes.getptr(p_extension->parent_class_name); +#ifdef TOOLS_ENABLED + // @todo This is a limitation of the current implementation, but it should be possible to remove. + ERR_FAIL_COND_MSG(p_extension->is_runtime && parent->gdextension && !parent->is_runtime, "Extension runtime class " + String(p_extension->class_name) + " cannot descend from " + parent->name + " which isn't also a runtime class"); +#endif + ClassInfo c; c.api = p_extension->editor_class ? API_EDITOR_EXTENSION : API_EXTENSION; c.gdextension = p_extension; @@ -2167,4 +2306,11 @@ void ClassDB::cleanup() { native_structs.clear(); } +// Array to use in optional parameters on methods and the DEFVAL_ARRAY macro. +Array ClassDB::default_array_arg = Array::create_read_only(); + +bool ClassDB::is_default_array_arg(const Array &p_array) { + return p_array.is_same_instance(default_array_arg); +} + // diff --git a/core/object/class_db.h b/core/object/class_db.h index 37a864c109..81100d7586 100644 --- a/core/object/class_db.h +++ b/core/object/class_db.h @@ -43,6 +43,7 @@ #include <type_traits> #define DEFVAL(m_defval) (m_defval) +#define DEFVAL_ARRAY DEFVAL(ClassDB::default_array_arg) #ifdef DEBUG_METHODS_ENABLED @@ -134,15 +135,21 @@ public: bool reloadable = false; bool is_virtual = false; bool is_runtime = false; - Object *(*creation_func)() = nullptr; + // The bool argument indicates the need to postinitialize. + Object *(*creation_func)(bool) = nullptr; ClassInfo() {} ~ClassInfo() {} }; template <typename T> - static Object *creator() { - return memnew(T); + static Object *creator(bool p_notify_postinitialize) { + Object *ret = new ("") T; + ret->_initialize(); + if (p_notify_postinitialize) { + ret->_postinitialize(); + } + return ret; } static RWLock lock; @@ -175,6 +182,9 @@ public: }; static HashMap<StringName, NativeStruct> native_structs; + static Array default_array_arg; + static bool is_default_array_arg(const Array &p_array); + private: // Non-locking variants of get_parent_class and is_parent_class. static StringName _get_parent_class(const StringName &p_class); @@ -183,7 +193,9 @@ private: static MethodBind *_bind_vararg_method(MethodBind *p_bind, const StringName &p_name, const Vector<Variant> &p_default_args, bool p_compatibility); static void _bind_method_custom(const StringName &p_class, MethodBind *p_method, bool p_compatibility); - static Object *_instantiate_internal(const StringName &p_class, bool p_require_real_class = false); + static Object *_instantiate_internal(const StringName &p_class, bool p_require_real_class = false, bool p_notify_postinitialize = true); + + static bool _can_instantiate(ClassInfo *p_class_info); public: // DO NOT USE THIS!!!!!! NEEDS TO BE PUBLIC BUT DO NOT USE NO MATTER WHAT!!! @@ -256,8 +268,8 @@ public: static void unregister_extension_class(const StringName &p_class, bool p_free_method_binds = true); template <typename T> - static Object *_create_ptr_func() { - return T::create(); + static Object *_create_ptr_func(bool p_notify_postinitialize) { + return T::create(p_notify_postinitialize); } template <typename T> @@ -277,19 +289,23 @@ public: static void get_class_list(List<StringName> *p_classes); #ifdef TOOLS_ENABLED static void get_extensions_class_list(List<StringName> *p_classes); + static void get_extension_class_list(const Ref<GDExtension> &p_extension, List<StringName> *p_classes); static ObjectGDExtension *get_placeholder_extension(const StringName &p_class); #endif static void get_inheriters_from_class(const StringName &p_class, List<StringName> *p_classes); static void get_direct_inheriters_from_class(const StringName &p_class, List<StringName> *p_classes); static StringName get_parent_class_nocheck(const StringName &p_class); + static bool get_inheritance_chain_nocheck(const StringName &p_class, Vector<StringName> &r_result); static StringName get_parent_class(const StringName &p_class); static StringName get_compatibility_remapped_class(const StringName &p_class); static bool class_exists(const StringName &p_class); static bool is_parent_class(const StringName &p_class, const StringName &p_inherits); static bool can_instantiate(const StringName &p_class); + static bool is_abstract(const StringName &p_class); static bool is_virtual(const StringName &p_class); static Object *instantiate(const StringName &p_class); static Object *instantiate_no_placeholders(const StringName &p_class); + static Object *instantiate_without_postinitialization(const StringName &p_class); static void set_object_extension_instance(Object *p_object, const StringName &p_class, GDExtensionClassInstancePtr p_instance); static APIType get_api_type(const StringName &p_class); @@ -460,6 +476,7 @@ public: static bool is_class_exposed(const StringName &p_class); static bool is_class_reloadable(const StringName &p_class); + static bool is_class_runtime(const StringName &p_class); static void add_resource_base_extension(const StringName &p_extension, const StringName &p_class); static void get_resource_base_extensions(List<String> *p_extensions); diff --git a/core/object/object.cpp b/core/object/object.cpp index 97a3a405b9..000d5328b4 100644 --- a/core/object/object.cpp +++ b/core/object/object.cpp @@ -29,7 +29,6 @@ /**************************************************************************/ #include "object.h" -#include "object.compat.inc" #include "core/extension/gdextension_manager.h" #include "core/io/resource.h" @@ -38,7 +37,7 @@ #include "core/object/script_language.h" #include "core/os/os.h" #include "core/string/print_string.h" -#include "core/string/translation.h" +#include "core/string/translation_server.h" #include "core/templates/local_vector.h" #include "core/variant/typed_array.h" @@ -207,10 +206,13 @@ void Object::cancel_free() { _predelete_ok = false; } -void Object::_postinitialize() { - _class_name_ptr = _get_class_namev(); // Set the direct pointer, which is much faster to obtain, but can only happen after postinitialize. +void Object::_initialize() { + _class_name_ptr = _get_class_namev(); // Set the direct pointer, which is much faster to obtain, but can only happen after _initialize. _initialize_classv(); _class_name_ptr = nullptr; // May have been called from a constructor. +} + +void Object::_postinitialize() { notification(NOTIFICATION_POSTINITIALIZE); } @@ -602,7 +604,7 @@ Variant Object::_call_bind(const Variant **p_args, int p_argcount, Callable::Cal return Variant(); } - if (p_args[0]->get_type() != Variant::STRING_NAME && p_args[0]->get_type() != Variant::STRING) { + if (!p_args[0]->is_string()) { r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = 0; r_error.expected = Variant::STRING_NAME; @@ -621,7 +623,7 @@ Variant Object::_call_deferred_bind(const Variant **p_args, int p_argcount, Call return Variant(); } - if (p_args[0]->get_type() != Variant::STRING_NAME && p_args[0]->get_type() != Variant::STRING) { + if (!p_args[0]->is_string()) { r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = 0; r_error.expected = Variant::STRING_NAME; @@ -717,7 +719,7 @@ Variant Object::getvar(const Variant &p_key, bool *r_valid) const { *r_valid = false; } - if (p_key.get_type() == Variant::STRING_NAME || p_key.get_type() == Variant::STRING) { + if (p_key.is_string()) { return get(p_key, r_valid); } return Variant(); @@ -727,7 +729,7 @@ void Object::setvar(const Variant &p_key, const Variant &p_value, bool *r_valid) if (r_valid) { *r_valid = false; } - if (p_key.get_type() == Variant::STRING_NAME || p_key.get_type() == Variant::STRING) { + if (p_key.is_string()) { return set(p_key, p_value, r_valid); } } @@ -743,7 +745,7 @@ Variant Object::callv(const StringName &p_method, const Array &p_args) { } Callable::CallError ce; - Variant ret = callp(p_method, argptrs, p_args.size(), ce); + const Variant ret = callp(p_method, argptrs, p_args.size(), ce); if (ce.error != Callable::CallError::CALL_OK) { ERR_FAIL_V_MSG(Variant(), "Error calling method from 'callv': " + Variant::get_call_error_text(this, p_method, argptrs, p_args.size(), ce) + "."); } @@ -763,7 +765,7 @@ Variant Object::callp(const StringName &p_method, const Variant **p_args, int p_ } if (is_ref_counted()) { r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; - ERR_FAIL_V_MSG(Variant(), "Can't 'free' a reference."); + ERR_FAIL_V_MSG(Variant(), "Can't free a RefCounted object."); } if (_lock_index.get() > 1) { @@ -784,7 +786,7 @@ Variant Object::callp(const StringName &p_method, const Variant **p_args, int p_ if (script_instance) { ret = script_instance->callp(p_method, p_args, p_argcount, r_error); - //force jumptable + // Force jump table. switch (r_error.error) { case Callable::CallError::CALL_OK: return ret; @@ -994,7 +996,7 @@ void Object::set_meta(const StringName &p_name, const Variant &p_value) { if (E) { E->value = p_value; } else { - ERR_FAIL_COND_MSG(!p_name.operator String().is_valid_identifier(), "Invalid metadata identifier: '" + p_name + "'."); + ERR_FAIL_COND_MSG(!p_name.operator String().is_valid_ascii_identifier(), "Invalid metadata identifier: '" + p_name + "'."); Variant *V = &metadata.insert(p_name, p_value)->value; const String &sname = p_name; @@ -1020,6 +1022,14 @@ void Object::remove_meta(const StringName &p_name) { set_meta(p_name, Variant()); } +void Object::merge_meta_from(const Object *p_src) { + List<StringName> meta_keys; + p_src->get_meta_list(&meta_keys); + for (const StringName &key : meta_keys) { + set_meta(key, p_src->get_meta(key)); + } +} + TypedArray<Dictionary> Object::_get_property_list_bind() const { List<PropertyInfo> lpi; get_property_list(&lpi); @@ -1093,7 +1103,7 @@ Error Object::_emit_signal(const Variant **p_args, int p_argcount, Callable::Cal ERR_FAIL_V(Error::ERR_INVALID_PARAMETER); } - if (unlikely(p_args[0]->get_type() != Variant::STRING_NAME && p_args[0]->get_type() != Variant::STRING)) { + if (unlikely(!p_args[0]->is_string())) { r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = 0; r_error.expected = Variant::STRING_NAME; @@ -1713,33 +1723,65 @@ void Object::_bind_methods() { #define BIND_OBJ_CORE_METHOD(m_method) \ ::ClassDB::add_virtual_method(get_class_static(), m_method, true, Vector<String>(), true); - MethodInfo notification_mi("_notification", PropertyInfo(Variant::INT, "what")); - notification_mi.arguments_metadata.push_back(GodotTypeInfo::Metadata::METADATA_INT_IS_INT32); - BIND_OBJ_CORE_METHOD(notification_mi); - BIND_OBJ_CORE_METHOD(MethodInfo(Variant::BOOL, "_set", PropertyInfo(Variant::STRING_NAME, "property"), PropertyInfo(Variant::NIL, "value"))); + BIND_OBJ_CORE_METHOD(MethodInfo("_init")); + + BIND_OBJ_CORE_METHOD(MethodInfo(Variant::STRING, "_to_string")); + + { + MethodInfo mi("_notification"); + mi.arguments.push_back(PropertyInfo(Variant::INT, "what")); + mi.arguments_metadata.push_back(GodotTypeInfo::Metadata::METADATA_INT_IS_INT32); + BIND_OBJ_CORE_METHOD(mi); + } + + { + MethodInfo mi("_set"); + mi.arguments.push_back(PropertyInfo(Variant::STRING_NAME, "property")); + mi.arguments.push_back(PropertyInfo(Variant::NIL, "value", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT)); + mi.return_val.type = Variant::BOOL; + BIND_OBJ_CORE_METHOD(mi); + } + #ifdef TOOLS_ENABLED - MethodInfo miget("_get", PropertyInfo(Variant::STRING_NAME, "property")); - miget.return_val.name = "Variant"; - miget.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; - BIND_OBJ_CORE_METHOD(miget); + { + MethodInfo mi("_get"); + mi.arguments.push_back(PropertyInfo(Variant::STRING_NAME, "property")); + mi.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; + BIND_OBJ_CORE_METHOD(mi); + } - MethodInfo plget("_get_property_list"); - plget.return_val.type = Variant::ARRAY; - plget.return_val.hint = PROPERTY_HINT_ARRAY_TYPE; - plget.return_val.hint_string = "Dictionary"; - BIND_OBJ_CORE_METHOD(plget); + { + MethodInfo mi("_get_property_list"); + mi.return_val.type = Variant::ARRAY; + mi.return_val.hint = PROPERTY_HINT_ARRAY_TYPE; + mi.return_val.hint_string = "Dictionary"; + BIND_OBJ_CORE_METHOD(mi); + } BIND_OBJ_CORE_METHOD(MethodInfo(Variant::NIL, "_validate_property", PropertyInfo(Variant::DICTIONARY, "property"))); BIND_OBJ_CORE_METHOD(MethodInfo(Variant::BOOL, "_property_can_revert", PropertyInfo(Variant::STRING_NAME, "property"))); - MethodInfo mipgr("_property_get_revert", PropertyInfo(Variant::STRING_NAME, "property")); - mipgr.return_val.name = "Variant"; - mipgr.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; - BIND_OBJ_CORE_METHOD(mipgr); + { + MethodInfo mi("_property_get_revert"); + mi.arguments.push_back(PropertyInfo(Variant::STRING_NAME, "property")); + mi.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; + BIND_OBJ_CORE_METHOD(mi); + } + + // These are actually `Variant` methods, but that doesn't matter since scripts can't inherit built-in types. + + BIND_OBJ_CORE_METHOD(MethodInfo(Variant::BOOL, "_iter_init", PropertyInfo(Variant::ARRAY, "iter"))); + + BIND_OBJ_CORE_METHOD(MethodInfo(Variant::BOOL, "_iter_next", PropertyInfo(Variant::ARRAY, "iter"))); + + { + MethodInfo mi("_iter_get"); + mi.arguments.push_back(PropertyInfo(Variant::NIL, "iter", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT)); + mi.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; + BIND_OBJ_CORE_METHOD(mi); + } #endif - BIND_OBJ_CORE_METHOD(MethodInfo("_init")); - BIND_OBJ_CORE_METHOD(MethodInfo(Variant::STRING, "_to_string")); BIND_CONSTANT(NOTIFICATION_POSTINITIALIZE); BIND_CONSTANT(NOTIFICATION_PREDELETE); @@ -1901,7 +1943,7 @@ void Object::set_instance_binding(void *p_token, void *p_binding, const GDExtens void *Object::get_instance_binding(void *p_token, const GDExtensionInstanceBindingCallbacks *p_callbacks) { void *binding = nullptr; - _instance_binding_mutex.lock(); + MutexLock instance_binding_lock(_instance_binding_mutex); for (uint32_t i = 0; i < _instance_binding_count; i++) { if (_instance_bindings[i].token == p_token) { binding = _instance_bindings[i].binding; @@ -1932,14 +1974,12 @@ void *Object::get_instance_binding(void *p_token, const GDExtensionInstanceBindi _instance_binding_count++; } - _instance_binding_mutex.unlock(); - return binding; } bool Object::has_instance_binding(void *p_token) { bool found = false; - _instance_binding_mutex.lock(); + MutexLock instance_binding_lock(_instance_binding_mutex); for (uint32_t i = 0; i < _instance_binding_count; i++) { if (_instance_bindings[i].token == p_token) { found = true; @@ -1947,14 +1987,12 @@ bool Object::has_instance_binding(void *p_token) { } } - _instance_binding_mutex.unlock(); - return found; } void Object::free_instance_binding(void *p_token) { bool found = false; - _instance_binding_mutex.lock(); + MutexLock instance_binding_lock(_instance_binding_mutex); for (uint32_t i = 0; i < _instance_binding_count; i++) { if (!found && _instance_bindings[i].token == p_token) { if (_instance_bindings[i].free_callback) { @@ -1973,7 +2011,6 @@ void Object::free_instance_binding(void *p_token) { if (found) { _instance_binding_count--; } - _instance_binding_mutex.unlock(); } #ifdef TOOLS_ENABLED @@ -2097,7 +2134,11 @@ Object::~Object() { // Disconnect signals that connect to this object. while (connections.size()) { Connection c = connections.front()->get(); - bool disconnected = c.signal.get_object()->_disconnect(c.signal.get_name(), c.callable, true); + Object *obj = c.callable.get_object(); + bool disconnected = false; + if (likely(obj)) { + disconnected = c.signal.get_object()->_disconnect(c.signal.get_name(), c.callable, true); + } if (unlikely(!disconnected)) { // If the disconnect has failed, abandon the connection to avoid getting trapped in an infinite loop here. connections.pop_front(); @@ -2125,6 +2166,7 @@ bool predelete_handler(Object *p_object) { } void postinitialize_handler(Object *p_object) { + p_object->_initialize(); p_object->_postinitialize(); } @@ -2286,7 +2328,7 @@ void ObjectDB::cleanup() { // Ensure calling the native classes because if a leaked instance has a script // that overrides any of those methods, it'd not be OK to call them at this point, // now the scripting languages have already been terminated. - MethodBind *node_get_name = ClassDB::get_method("Node", "get_name"); + MethodBind *node_get_path = ClassDB::get_method("Node", "get_path"); MethodBind *resource_get_path = ClassDB::get_method("Resource", "get_path"); Callable::CallError call_error; @@ -2296,7 +2338,7 @@ void ObjectDB::cleanup() { String extra_info; if (obj->is_class("Node")) { - extra_info = " - Node name: " + String(node_get_name->call(obj, nullptr, 0, call_error)); + extra_info = " - Node path: " + String(node_get_path->call(obj, nullptr, 0, call_error)); } if (obj->is_class("Resource")) { extra_info = " - Resource path: " + String(resource_get_path->call(obj, nullptr, 0, call_error)); diff --git a/core/object/object.h b/core/object/object.h index adb50268d2..6d22f320af 100644 --- a/core/object/object.h +++ b/core/object/object.h @@ -86,6 +86,7 @@ enum PropertyHint { PROPERTY_HINT_HIDE_QUATERNION_EDIT, /// Only Node3D::transform should hide the quaternion editor. PROPERTY_HINT_PASSWORD, PROPERTY_HINT_LAYERS_AVOIDANCE, + PROPERTY_HINT_DICTIONARY_TYPE, PROPERTY_HINT_MAX, }; @@ -350,7 +351,10 @@ struct ObjectGDExtension { } void *class_userdata = nullptr; +#ifndef DISABLE_DEPRECATED GDExtensionClassCreateInstance create_instance; +#endif // DISABLE_DEPRECATED + GDExtensionClassCreateInstance2 create_instance2; GDExtensionClassFreeInstance free_instance; GDExtensionClassGetVirtual get_virtual; GDExtensionClassGetVirtualCallData get_virtual_call_data; @@ -384,18 +388,6 @@ struct ObjectGDExtension { * much alone defines the object model. */ -#define REVERSE_GET_PROPERTY_LIST \ -public: \ - _FORCE_INLINE_ bool _is_gpl_reversed() const { return true; }; \ - \ -private: - -#define UNREVERSE_GET_PROPERTY_LIST \ -public: \ - _FORCE_INLINE_ bool _is_gpl_reversed() const { return false; }; \ - \ -private: - #define GDCLASS(m_class, m_inherits) \ private: \ void operator=(const m_class &p_rval) {} \ @@ -507,15 +499,10 @@ protected: m_inherits::_get_property_listv(p_list, p_reversed); \ } \ p_list->push_back(PropertyInfo(Variant::NIL, get_class_static(), PROPERTY_HINT_NONE, get_class_static(), PROPERTY_USAGE_CATEGORY)); \ - if (!_is_gpl_reversed()) { \ - ::ClassDB::get_property_list(#m_class, p_list, true, this); \ - } \ + ::ClassDB::get_property_list(#m_class, p_list, true, this); \ if (m_class::_get_get_property_list() != m_inherits::_get_get_property_list()) { \ _get_property_list(p_list); \ } \ - if (_is_gpl_reversed()) { \ - ::ClassDB::get_property_list(#m_class, p_list, true, this); \ - } \ if (p_reversed) { \ m_inherits::_get_property_listv(p_list, p_reversed); \ } \ @@ -632,6 +619,7 @@ private: int _predelete_ok = 0; ObjectID _instance_id; bool _predelete(); + void _initialize(); void _postinitialize(); bool _can_translate = true; bool _emitting = false; @@ -680,7 +668,7 @@ protected: _FORCE_INLINE_ bool _instance_binding_reference(bool p_reference) { bool can_die = true; if (_instance_bindings) { - _instance_binding_mutex.lock(); + MutexLock instance_binding_lock(_instance_binding_mutex); for (uint32_t i = 0; i < _instance_binding_count; i++) { if (_instance_bindings[i].reference_callback) { if (!_instance_bindings[i].reference_callback(_instance_bindings[i].token, _instance_bindings[i].binding, p_reference)) { @@ -688,7 +676,6 @@ protected: } } } - _instance_binding_mutex.unlock(); } return can_die; } @@ -706,11 +693,7 @@ protected: virtual void _notificationv(int p_notification, bool p_reversed) {} static void _bind_methods(); -#ifndef DISABLE_DEPRECATED - static void _bind_compatibility_methods(); -#else static void _bind_compatibility_methods() {} -#endif bool _set(const StringName &p_name, const Variant &p_property) { return false; }; bool _get(const StringName &p_name, Variant &r_property) const { return false; }; void _get_property_list(List<PropertyInfo> *p_list) const {}; @@ -795,8 +778,6 @@ public: return &ptr; } - bool _is_gpl_reversed() const { return false; } - void detach_from_objectdb(); _FORCE_INLINE_ ObjectID get_instance_id() const { return _instance_id; } @@ -883,7 +864,8 @@ public: argptrs[i] = &args[i]; } Callable::CallError cerr; - return callp(p_method, sizeof...(p_args) == 0 ? nullptr : (const Variant **)argptrs, sizeof...(p_args), cerr); + const Variant ret = callp(p_method, sizeof...(p_args) == 0 ? nullptr : (const Variant **)argptrs, sizeof...(p_args), cerr); + return (cerr.error == Callable::CallError::CALL_OK) ? ret : Variant(); } void notification(int p_notification, bool p_reversed = false); @@ -910,6 +892,7 @@ public: MTVIRTUAL void remove_meta(const StringName &p_name); MTVIRTUAL Variant get_meta(const StringName &p_name, const Variant &p_default = Variant()) const; MTVIRTUAL void get_meta_list(List<StringName> *p_list) const; + MTVIRTUAL void merge_meta_from(const Object *p_src); #ifdef TOOLS_ENABLED void set_edited(bool p_edited); diff --git a/core/object/script_language.cpp b/core/object/script_language.cpp index 0b528e908a..d5b7bc768d 100644 --- a/core/object/script_language.cpp +++ b/core/object/script_language.cpp @@ -41,6 +41,7 @@ ScriptLanguage *ScriptServer::_languages[MAX_LANGUAGES]; int ScriptServer::_language_count = 0; bool ScriptServer::languages_ready = false; Mutex ScriptServer::languages_mutex; +thread_local bool ScriptServer::thread_entered = false; bool ScriptServer::scripting_enabled = true; bool ScriptServer::reload_scripts_on_save = false; @@ -326,6 +327,10 @@ bool ScriptServer::are_languages_initialized() { return languages_ready; } +bool ScriptServer::thread_is_entered() { + return thread_entered; +} + void ScriptServer::set_reload_scripts_on_save(bool p_enable) { reload_scripts_on_save = p_enable; } @@ -335,6 +340,10 @@ bool ScriptServer::is_reload_scripts_on_save_enabled() { } void ScriptServer::thread_enter() { + if (thread_entered) { + return; + } + MutexLock lock(languages_mutex); if (!languages_ready) { return; @@ -342,9 +351,15 @@ void ScriptServer::thread_enter() { for (int i = 0; i < _language_count; i++) { _languages[i]->thread_enter(); } + + thread_entered = true; } void ScriptServer::thread_exit() { + if (!thread_entered) { + return; + } + MutexLock lock(languages_mutex); if (!languages_ready) { return; @@ -352,6 +367,8 @@ void ScriptServer::thread_exit() { for (int i = 0; i < _language_count; i++) { _languages[i]->thread_exit(); } + + thread_entered = false; } HashMap<StringName, ScriptServer::GlobalScriptClass> ScriptServer::global_classes; @@ -491,10 +508,6 @@ void ScriptServer::save_global_classes() { ProjectSettings::get_singleton()->store_global_class_list(gcarr); } -String ScriptServer::get_global_class_cache_file_path() { - return ProjectSettings::get_singleton()->get_global_class_list_path(); -} - //////////////////// ScriptCodeCompletionCache *ScriptCodeCompletionCache::singleton = nullptr; @@ -708,6 +721,19 @@ bool PlaceHolderScriptInstance::has_method(const StringName &p_method) const { return false; } +Variant PlaceHolderScriptInstance::callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { + r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; +#if TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + return String("Attempt to call a method on a placeholder instance. Check if the script is in tool mode."); + } else { + return String("Attempt to call a method on a placeholder instance. Probably a bug, please report."); + } +#else + return Variant(); +#endif // TOOLS_ENABLED +} + void PlaceHolderScriptInstance::update(const List<PropertyInfo> &p_properties, const HashMap<StringName, Variant> &p_values) { HashSet<StringName> new_values; for (const PropertyInfo &E : p_properties) { diff --git a/core/object/script_language.h b/core/object/script_language.h index 223f114150..d9e2ab1d3c 100644 --- a/core/object/script_language.h +++ b/core/object/script_language.h @@ -54,6 +54,7 @@ class ScriptServer { static int _language_count; static bool languages_ready; static Mutex languages_mutex; + static thread_local bool thread_entered; static bool scripting_enabled; static bool reload_scripts_on_save; @@ -97,11 +98,11 @@ public: static void get_global_class_list(List<StringName> *r_global_classes); static void get_inheriters_list(const StringName &p_base_type, List<StringName> *r_classes); static void save_global_classes(); - static String get_global_class_cache_file_path(); static void init_languages(); static void finish_languages(); static bool are_languages_initialized(); + static bool thread_is_entered(); }; class PlaceHolderScriptInstance; @@ -455,10 +456,7 @@ public: return 0; } - virtual Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; - return Variant(); - } + virtual Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override; virtual void notification(int p_notification, bool p_reversed = false) override {} virtual Ref<Script> get_script() const override { return script; } diff --git a/core/object/script_language_extension.cpp b/core/object/script_language_extension.cpp index 7b643e4637..73f7ec5a54 100644 --- a/core/object/script_language_extension.cpp +++ b/core/object/script_language_extension.cpp @@ -142,6 +142,7 @@ void ScriptLanguageExtension::_bind_methods() { GDVIRTUAL_BIND(_debug_get_current_stack_info); GDVIRTUAL_BIND(_reload_all_scripts); + GDVIRTUAL_BIND(_reload_scripts, "scripts", "soft_reload"); GDVIRTUAL_BIND(_reload_tool_script, "script", "soft_reload"); GDVIRTUAL_BIND(_get_recognized_extensions); diff --git a/core/object/worker_thread_pool.cpp b/core/object/worker_thread_pool.cpp index 8f56ca37de..cf396c2676 100644 --- a/core/object/worker_thread_pool.cpp +++ b/core/object/worker_thread_pool.cpp @@ -32,6 +32,7 @@ #include "core/object/script_language.h" #include "core/os/os.h" +#include "core/os/safe_binary_mutex.h" #include "core/os/thread_safe.h" WorkerThreadPool::Task *const WorkerThreadPool::ThreadData::YIELDING = (Task *)1; @@ -46,7 +47,7 @@ void WorkerThreadPool::Task::free_template_userdata() { WorkerThreadPool *WorkerThreadPool::singleton = nullptr; #ifdef THREADS_ENABLED -thread_local uintptr_t WorkerThreadPool::unlockable_mutexes[MAX_UNLOCKABLE_MUTEXES] = {}; +thread_local WorkerThreadPool::UnlockableLocks WorkerThreadPool::unlockable_locks[MAX_UNLOCKABLE_LOCKS]; #endif void WorkerThreadPool::_process_task(Task *p_task) { @@ -59,19 +60,17 @@ void WorkerThreadPool::_process_task(Task *p_task) { CallQueue *call_queue_backup = MessageQueue::get_singleton() != MessageQueue::get_main_singleton() ? MessageQueue::get_singleton() : nullptr; { - // Tasks must start with this unset. They are free to set-and-forget otherwise. + // Tasks must start with these at default values. They are free to set-and-forget otherwise. set_current_thread_safe_for_nodes(false); + MessageQueue::set_thread_singleton_override(nullptr); + // Since the WorkerThreadPool is started before the script server, // its pre-created threads can't have ScriptServer::thread_enter() called on them early. // Therefore, we do it late at the first opportunity, so in case the task // about to be run uses scripting, guarantees are held. + ScriptServer::thread_enter(); + task_mutex.lock(); - if (!curr_thread.ready_for_scripting && ScriptServer::are_languages_initialized()) { - task_mutex.unlock(); - ScriptServer::thread_enter(); - task_mutex.lock(); - curr_thread.ready_for_scripting = true; - } p_task->pool_thread_index = pool_thread_index; prev_task = curr_thread.current_task; curr_thread.current_task = p_task; @@ -82,6 +81,10 @@ void WorkerThreadPool::_process_task(Task *p_task) { } #endif +#ifdef THREADS_ENABLED + bool low_priority = p_task->low_priority; +#endif + if (p_task->group) { // Handling a group bool do_post = false; @@ -121,9 +124,8 @@ void WorkerThreadPool::_process_task(Task *p_task) { if (finished_users == max_users) { // Get rid of the group, because nobody else is using it. - task_mutex.lock(); + MutexLock task_lock(task_mutex); group_allocator.free(p_task->group); - task_mutex.unlock(); } // For groups, tasks get rid of themselves. @@ -158,7 +160,7 @@ void WorkerThreadPool::_process_task(Task *p_task) { #ifdef THREADS_ENABLED { curr_thread.current_task = prev_task; - if (p_task->low_priority) { + if (low_priority) { low_priority_threads_used--; if (_try_promote_low_priority_task()) { @@ -321,6 +323,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."); + task_mutex.lock(); // Get a free task Task *task = task_allocator.alloc(); @@ -343,17 +347,13 @@ WorkerThreadPool::TaskID WorkerThreadPool::add_task(const Callable &p_action, bo } bool WorkerThreadPool::is_task_completed(TaskID p_task_id) const { - task_mutex.lock(); + MutexLock task_lock(task_mutex); const Task *const *taskp = tasks.getptr(p_task_id); if (!taskp) { - task_mutex.unlock(); ERR_FAIL_V_MSG(false, "Invalid Task ID"); // Invalid task } - bool completed = (*taskp)->completed; - task_mutex.unlock(); - - return completed; + return (*taskp)->completed; } Error WorkerThreadPool::wait_for_task_completion(TaskID p_task_id) { @@ -397,16 +397,17 @@ Error WorkerThreadPool::wait_for_task_completion(TaskID p_task_id) { task->waiting_user++; } - task_mutex.unlock(); - if (caller_pool_thread) { + task_mutex.unlock(); _wait_collaboratively(caller_pool_thread, task); + task_mutex.lock(); task->waiting_pool--; if (task->waiting_pool == 0 && task->waiting_user == 0) { tasks.erase(p_task_id); task_allocator.free(task); } } else { + task_mutex.unlock(); task->done_semaphore.wait(); task_mutex.lock(); task->waiting_user--; @@ -414,21 +415,17 @@ Error WorkerThreadPool::wait_for_task_completion(TaskID p_task_id) { tasks.erase(p_task_id); task_allocator.free(task); } - task_mutex.unlock(); } + task_mutex.unlock(); return OK; } void WorkerThreadPool::_lock_unlockable_mutexes() { #ifdef THREADS_ENABLED - for (uint32_t i = 0; i < MAX_UNLOCKABLE_MUTEXES; i++) { - if (unlockable_mutexes[i]) { - if ((((uintptr_t)unlockable_mutexes[i]) & 1) == 0) { - ((Mutex *)unlockable_mutexes[i])->lock(); - } else { - ((BinaryMutex *)unlockable_mutexes[i])->lock(); - } + for (uint32_t i = 0; i < MAX_UNLOCKABLE_LOCKS; i++) { + if (unlockable_locks[i].ulock) { + unlockable_locks[i].ulock->lock(); } } #endif @@ -436,13 +433,9 @@ void WorkerThreadPool::_lock_unlockable_mutexes() { void WorkerThreadPool::_unlock_unlockable_mutexes() { #ifdef THREADS_ENABLED - for (uint32_t i = 0; i < MAX_UNLOCKABLE_MUTEXES; i++) { - if (unlockable_mutexes[i]) { - if ((((uintptr_t)unlockable_mutexes[i]) & 1) == 0) { - ((Mutex *)unlockable_mutexes[i])->unlock(); - } else { - ((BinaryMutex *)unlockable_mutexes[i])->unlock(); - } + for (uint32_t i = 0; i < MAX_UNLOCKABLE_LOCKS; i++) { + if (unlockable_locks[i].ulock) { + unlockable_locks[i].ulock->unlock(); } } #endif @@ -462,7 +455,10 @@ void WorkerThreadPool::_wait_collaboratively(ThreadData *p_caller_pool_thread, T p_caller_pool_thread->signaled = false; if (IS_WAIT_OVER) { - p_caller_pool_thread->yield_is_over = false; + if (unlikely(p_task == ThreadData::YIELDING)) { + p_caller_pool_thread->yield_is_over = false; + } + if (!exit_threads && 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. @@ -517,13 +513,18 @@ 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(); } void WorkerThreadPool::notify_yield_over(TaskID p_task_id) { - task_mutex.lock(); + MutexLock task_lock(task_mutex); Task **taskp = tasks.getptr(p_task_id); if (!taskp) { - task_mutex.unlock(); ERR_FAIL_MSG("Invalid Task ID."); } Task *task = *taskp; @@ -532,7 +533,6 @@ void WorkerThreadPool::notify_yield_over(TaskID p_task_id) { // This avoids a race condition where a task is created and yield-over called before it's processed. task->pending_notify_yield_over = true; } - task_mutex.unlock(); return; } @@ -540,11 +540,10 @@ void WorkerThreadPool::notify_yield_over(TaskID p_task_id) { td.yield_is_over = true; td.signaled = true; td.cond_var.notify_one(); - - task_mutex.unlock(); } 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()); @@ -599,26 +598,20 @@ WorkerThreadPool::GroupID WorkerThreadPool::add_group_task(const Callable &p_act } uint32_t WorkerThreadPool::get_group_processed_element_count(GroupID p_group) const { - task_mutex.lock(); + MutexLock task_lock(task_mutex); const Group *const *groupp = groups.getptr(p_group); if (!groupp) { - task_mutex.unlock(); ERR_FAIL_V_MSG(0, "Invalid Group ID"); } - uint32_t elements = (*groupp)->completed_index.get(); - task_mutex.unlock(); - return elements; + return (*groupp)->completed_index.get(); } bool WorkerThreadPool::is_group_task_completed(GroupID p_group) const { - task_mutex.lock(); + MutexLock task_lock(task_mutex); const Group *const *groupp = groups.getptr(p_group); if (!groupp) { - task_mutex.unlock(); ERR_FAIL_V_MSG(false, "Invalid Group ID"); } - bool completed = (*groupp)->completed.is_set(); - task_mutex.unlock(); - return completed; + return (*groupp)->completed.is_set(); } void WorkerThreadPool::wait_for_group_task_completion(GroupID p_group) { @@ -642,15 +635,13 @@ void WorkerThreadPool::wait_for_group_task_completion(GroupID p_group) { if (finished_users == max_users) { // All tasks using this group are gone (finished before the group), so clear the group too. - task_mutex.lock(); + MutexLock task_lock(task_mutex); group_allocator.free(group); - task_mutex.unlock(); } } - task_mutex.lock(); // This mutex is needed when Physics 2D and/or 3D is selected to run on a separate thread. + MutexLock task_lock(task_mutex); // This mutex is needed when Physics 2D and/or 3D is selected to run on a separate thread. groups.erase(p_group); - task_mutex.unlock(); #endif } @@ -659,38 +650,38 @@ int WorkerThreadPool::get_thread_index() { return singleton->thread_ids.has(tid) ? singleton->thread_ids[tid] : -1; } -#ifdef THREADS_ENABLED -uint32_t WorkerThreadPool::thread_enter_unlock_allowance_zone(Mutex *p_mutex) { - return _thread_enter_unlock_allowance_zone(p_mutex, false); -} - -uint32_t WorkerThreadPool::thread_enter_unlock_allowance_zone(BinaryMutex *p_mutex) { - return _thread_enter_unlock_allowance_zone(p_mutex, true); +WorkerThreadPool::TaskID WorkerThreadPool::get_caller_task_id() { + int th_index = get_thread_index(); + if (th_index != -1 && singleton->threads[th_index].current_task) { + return singleton->threads[th_index].current_task->self; + } else { + return INVALID_TASK_ID; + } } -uint32_t WorkerThreadPool::_thread_enter_unlock_allowance_zone(void *p_mutex, bool p_is_binary) { - for (uint32_t i = 0; i < MAX_UNLOCKABLE_MUTEXES; i++) { - if (unlikely(unlockable_mutexes[i] == (uintptr_t)p_mutex)) { +#ifdef THREADS_ENABLED +uint32_t WorkerThreadPool::_thread_enter_unlock_allowance_zone(THREADING_NAMESPACE::unique_lock<THREADING_NAMESPACE::mutex> &p_ulock) { + for (uint32_t i = 0; i < MAX_UNLOCKABLE_LOCKS; i++) { + DEV_ASSERT((bool)unlockable_locks[i].ulock == (bool)unlockable_locks[i].rc); + if (unlockable_locks[i].ulock == &p_ulock) { // Already registered in the current thread. - return UINT32_MAX; - } - if (!unlockable_mutexes[i]) { - unlockable_mutexes[i] = (uintptr_t)p_mutex; - if (p_is_binary) { - unlockable_mutexes[i] |= 1; - } + unlockable_locks[i].rc++; + return i; + } else if (!unlockable_locks[i].ulock) { + unlockable_locks[i].ulock = &p_ulock; + unlockable_locks[i].rc = 1; return i; } } - ERR_FAIL_V_MSG(UINT32_MAX, "No more unlockable mutex slots available. Engine bug."); + ERR_FAIL_V_MSG(UINT32_MAX, "No more unlockable lock slots available. Engine bug."); } void WorkerThreadPool::thread_exit_unlock_allowance_zone(uint32_t p_zone_id) { - if (p_zone_id == UINT32_MAX) { - return; + DEV_ASSERT(unlockable_locks[p_zone_id].ulock && unlockable_locks[p_zone_id].rc); + unlockable_locks[p_zone_id].rc--; + if (unlockable_locks[p_zone_id].rc == 0) { + unlockable_locks[p_zone_id].ulock = nullptr; } - DEV_ASSERT(unlockable_mutexes[p_zone_id]); - unlockable_mutexes[p_zone_id] = 0; } #endif @@ -702,6 +693,8 @@ void WorkerThreadPool::init(int p_thread_count, float p_low_priority_task_ratio) max_low_priority_threads = CLAMP(p_thread_count * p_low_priority_task_ratio, 1, p_thread_count - 1); + print_verbose(vformat("WorkerThreadPool: %d threads, %d max low-priority.", p_thread_count, max_low_priority_threads)); + threads.resize(p_thread_count); for (uint32_t i = 0; i < threads.size(); i++) { @@ -762,5 +755,5 @@ WorkerThreadPool::WorkerThreadPool() { } WorkerThreadPool::~WorkerThreadPool() { - finish(); + DEV_ASSERT(threads.size() == 0 && "finish() hasn't been called!"); } diff --git a/core/object/worker_thread_pool.h b/core/object/worker_thread_pool.h index 8774143abf..6374dbe8c7 100644 --- a/core/object/worker_thread_pool.h +++ b/core/object/worker_thread_pool.h @@ -112,7 +112,6 @@ private: uint32_t index = 0; Thread thread; - bool ready_for_scripting : 1; bool signaled : 1; bool yield_is_over : 1; Task *current_task = nullptr; @@ -120,7 +119,6 @@ private: ConditionVariable cond_var; ThreadData() : - ready_for_scripting(false), signaled(false), yield_is_over(false) {} }; @@ -162,8 +160,12 @@ private: static WorkerThreadPool *singleton; #ifdef THREADS_ENABLED - static const uint32_t MAX_UNLOCKABLE_MUTEXES = 2; - static thread_local uintptr_t unlockable_mutexes[MAX_UNLOCKABLE_MUTEXES]; + static const uint32_t MAX_UNLOCKABLE_LOCKS = 2; + struct UnlockableLocks { + THREADING_NAMESPACE::unique_lock<THREADING_NAMESPACE::mutex> *ulock = nullptr; + uint32_t rc = 0; + }; + static thread_local UnlockableLocks unlockable_locks[MAX_UNLOCKABLE_LOCKS]; #endif TaskID _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); @@ -192,7 +194,7 @@ private: void _wait_collaboratively(ThreadData *p_caller_pool_thread, Task *p_task); #ifdef THREADS_ENABLED - static uint32_t _thread_enter_unlock_allowance_zone(void *p_mutex, bool p_is_binary); + static uint32_t _thread_enter_unlock_allowance_zone(THREADING_NAMESPACE::unique_lock<THREADING_NAMESPACE::mutex> &p_ulock); #endif void _lock_unlockable_mutexes(); @@ -239,13 +241,17 @@ public: static WorkerThreadPool *get_singleton() { return singleton; } static int get_thread_index(); + static TaskID get_caller_task_id(); #ifdef THREADS_ENABLED - static uint32_t thread_enter_unlock_allowance_zone(Mutex *p_mutex); - static uint32_t thread_enter_unlock_allowance_zone(BinaryMutex *p_mutex); + _ALWAYS_INLINE_ static uint32_t thread_enter_unlock_allowance_zone(const MutexLock<BinaryMutex> &p_lock) { return _thread_enter_unlock_allowance_zone(p_lock._get_lock()); } + template <int Tag> + _ALWAYS_INLINE_ static uint32_t thread_enter_unlock_allowance_zone(const SafeBinaryMutex<Tag> &p_mutex) { return _thread_enter_unlock_allowance_zone(p_mutex._get_lock()); } static void thread_exit_unlock_allowance_zone(uint32_t p_zone_id); #else - static uint32_t thread_enter_unlock_allowance_zone(void *p_mutex) { return UINT32_MAX; } + static uint32_t thread_enter_unlock_allowance_zone(const MutexLock<BinaryMutex> &p_lock) { return UINT32_MAX; } + template <int Tag> + static uint32_t thread_enter_unlock_allowance_zone(const SafeBinaryMutex<Tag> &p_mutex) { return UINT32_MAX; } static void thread_exit_unlock_allowance_zone(uint32_t p_zone_id) {} #endif diff --git a/core/os/condition_variable.h b/core/os/condition_variable.h index fa1355e98c..c819fa6b40 100644 --- a/core/os/condition_variable.h +++ b/core/os/condition_variable.h @@ -32,6 +32,7 @@ #define CONDITION_VARIABLE_H #include "core/os/mutex.h" +#include "core/os/safe_binary_mutex.h" #ifdef THREADS_ENABLED @@ -56,7 +57,12 @@ class ConditionVariable { public: template <typename BinaryMutexT> _ALWAYS_INLINE_ void wait(const MutexLock<BinaryMutexT> &p_lock) const { - condition.wait(const_cast<THREADING_NAMESPACE::unique_lock<THREADING_NAMESPACE::mutex> &>(p_lock.lock)); + condition.wait(const_cast<THREADING_NAMESPACE::unique_lock<THREADING_NAMESPACE::mutex> &>(p_lock._get_lock())); + } + + template <int Tag> + _ALWAYS_INLINE_ void wait(const MutexLock<SafeBinaryMutex<Tag>> &p_lock) const { + condition.wait(const_cast<THREADING_NAMESPACE::unique_lock<THREADING_NAMESPACE::mutex> &>(p_lock.mutex._get_lock())); } _ALWAYS_INLINE_ void notify_one() const { diff --git a/core/os/main_loop.h b/core/os/main_loop.h index e48541d074..9c22cbaf3c 100644 --- a/core/os/main_loop.h +++ b/core/os/main_loop.h @@ -64,6 +64,7 @@ public: virtual void initialize(); virtual void iteration_prepare() {} virtual bool physics_process(double p_time); + virtual void iteration_end() {} virtual bool process(double p_time); virtual void finalize(); diff --git a/core/os/memory.cpp b/core/os/memory.cpp index 32c316e58e..dae0a31fe0 100644 --- a/core/os/memory.cpp +++ b/core/os/memory.cpp @@ -35,6 +35,7 @@ #include <stdio.h> #include <stdlib.h> +#include <string.h> void *operator new(size_t p_size, const char *p_description) { return Memory::alloc_static(p_size, false); @@ -65,6 +66,38 @@ SafeNumeric<uint64_t> Memory::max_usage; SafeNumeric<uint64_t> Memory::alloc_count; +inline bool is_power_of_2(size_t x) { return x && ((x & (x - 1U)) == 0U); } + +void *Memory::alloc_aligned_static(size_t p_bytes, size_t p_alignment) { + DEV_ASSERT(is_power_of_2(p_alignment)); + + void *p1, *p2; + if ((p1 = (void *)malloc(p_bytes + p_alignment - 1 + sizeof(uint32_t))) == nullptr) { + return nullptr; + } + + p2 = (void *)(((uintptr_t)p1 + sizeof(uint32_t) + p_alignment - 1) & ~((p_alignment)-1)); + *((uint32_t *)p2 - 1) = (uint32_t)((uintptr_t)p2 - (uintptr_t)p1); + return p2; +} + +void *Memory::realloc_aligned_static(void *p_memory, size_t p_bytes, size_t p_prev_bytes, size_t p_alignment) { + if (p_memory == nullptr) { + return alloc_aligned_static(p_bytes, p_alignment); + } + + void *ret = alloc_aligned_static(p_bytes, p_alignment); + memcpy(ret, p_memory, p_prev_bytes); + free_aligned_static(p_memory); + return ret; +} + +void Memory::free_aligned_static(void *p_memory) { + uint32_t offset = *((uint32_t *)p_memory - 1); + void *p = (void *)((uint8_t *)p_memory - offset); + free(p); +} + void *Memory::alloc_static(size_t p_bytes, bool p_pad_align) { #ifdef DEBUG_ENABLED bool prepad = true; diff --git a/core/os/memory.h b/core/os/memory.h index d03e08d785..033e417cb5 100644 --- a/core/os/memory.h +++ b/core/os/memory.h @@ -62,6 +62,30 @@ public: static void *realloc_static(void *p_memory, size_t p_bytes, bool p_pad_align = false); static void free_static(void *p_ptr, bool p_pad_align = false); + // ↓ return value of alloc_aligned_static + // ┌─────────────────┬─────────┬─────────┬──────────────────┐ + // │ padding (up to │ uint32_t│ void* │ padding (up to │ + // │ p_alignment - 1)│ offset │ p_bytes │ p_alignment - 1) │ + // └─────────────────┴─────────┴─────────┴──────────────────┘ + // + // alloc_aligned_static will allocate p_bytes + p_alignment - 1 + sizeof(uint32_t) and + // then offset the pointer until alignment is satisfied. + // + // This offset is stored before the start of the returned ptr so we can retrieve the original/real + // start of the ptr in order to free it. + // + // The rest is wasted as padding in the beginning and end of the ptr. The sum of padding at + // both start and end of the block must add exactly to p_alignment - 1. + // + // p_alignment MUST be a power of 2. + static void *alloc_aligned_static(size_t p_bytes, size_t p_alignment); + static void *realloc_aligned_static(void *p_memory, size_t p_bytes, size_t p_prev_bytes, size_t p_alignment); + // Pass the ptr returned by alloc_aligned_static to free it. + // e.g. + // void *data = realloc_aligned_static( bytes, 16 ); + // free_aligned_static( data ); + static void free_aligned_static(void *p_memory); + static uint64_t get_mem_available(); static uint64_t get_mem_usage(); static uint64_t get_mem_max_usage(); diff --git a/core/os/mutex.h b/core/os/mutex.h index 3e7aa81bc1..a968fd7029 100644 --- a/core/os/mutex.h +++ b/core/os/mutex.h @@ -72,13 +72,28 @@ public: template <typename MutexT> class MutexLock { - friend class ConditionVariable; - - THREADING_NAMESPACE::unique_lock<typename MutexT::StdMutexType> lock; + mutable THREADING_NAMESPACE::unique_lock<typename MutexT::StdMutexType> lock; public: explicit MutexLock(const MutexT &p_mutex) : lock(p_mutex.mutex) {} + + // Clarification: all the funny syntax is needed so this function exists only for binary mutexes. + template <typename T = MutexT> + _ALWAYS_INLINE_ THREADING_NAMESPACE::unique_lock<THREADING_NAMESPACE::mutex> &_get_lock( + typename std::enable_if<std::is_same<T, THREADING_NAMESPACE::mutex>::value> * = nullptr) const { + return lock; + } + + _ALWAYS_INLINE_ void temp_relock() const { + lock.lock(); + } + + _ALWAYS_INLINE_ void temp_unlock() const { + lock.unlock(); + } + + // TODO: Implement a `try_temp_relock` if needed (will also need a dummy method below). }; using Mutex = MutexImpl<THREADING_NAMESPACE::recursive_mutex>; // Recursive, for general use @@ -104,6 +119,9 @@ template <typename MutexT> class MutexLock { public: MutexLock(const MutexT &p_mutex) {} + + void temp_relock() const {} + void temp_unlock() const {} }; using Mutex = MutexImpl; diff --git a/core/os/os.h b/core/os/os.h index 63cc6ed50e..30d2a4266f 100644 --- a/core/os/os.h +++ b/core/os/os.h @@ -75,6 +75,7 @@ class OS { int _display_driver_id = -1; String _current_rendering_driver_name; String _current_rendering_method; + bool _is_gles_over_gl = false; RemoteFilesystemClient default_rfs; @@ -111,9 +112,6 @@ protected: virtual void initialize() = 0; virtual void initialize_joypads() = 0; - void set_current_rendering_driver_name(const String &p_driver_name) { _current_rendering_driver_name = p_driver_name; } - void set_current_rendering_method(const String &p_name) { _current_rendering_method = p_name; } - void set_display_driver_id(int p_display_driver_id) { _display_driver_id = p_display_driver_id; } virtual void set_main_loop(MainLoop *p_main_loop) = 0; @@ -131,12 +129,18 @@ public: static OS *get_singleton(); + void set_current_rendering_driver_name(const String &p_driver_name) { _current_rendering_driver_name = p_driver_name; } + void set_current_rendering_method(const String &p_name) { _current_rendering_method = p_name; } + void set_gles_over_gl(bool p_enabled) { _is_gles_over_gl = p_enabled; } + String get_current_rendering_driver_name() const { return _current_rendering_driver_name; } String get_current_rendering_method() const { return _current_rendering_method; } + bool get_gles_over_gl() const { return _is_gles_over_gl; } int get_display_driver_id() const { return _display_driver_id; } virtual Vector<String> get_video_adapter_driver_info() const = 0; + virtual bool get_user_prefers_integrated_gpu() const { return false; } void print_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify = false, Logger::ErrorType p_type = Logger::ERR_ERROR); void print(const char *p_format, ...) _PRINTF_FORMAT_ATTRIBUTE_2_3; @@ -178,7 +182,7 @@ public: virtual Vector<String> get_system_font_path_for_text(const String &p_font_name, const String &p_text, const String &p_locale = String(), const String &p_script = String(), int p_weight = 400, int p_stretch = 100, bool p_italic = false) const { return Vector<String>(); }; virtual String get_executable_path() const; virtual Error execute(const String &p_path, const List<String> &p_arguments, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr, bool p_open_console = false) = 0; - virtual Dictionary execute_with_pipe(const String &p_path, const List<String> &p_arguments) { return Dictionary(); } + virtual Dictionary execute_with_pipe(const String &p_path, const List<String> &p_arguments, bool p_blocking = true) { return Dictionary(); } virtual Error create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id = nullptr, bool p_open_console = false) = 0; virtual Error create_instance(const List<String> &p_arguments, ProcessID *r_child_id = nullptr) { return create_process(get_executable_path(), p_arguments, r_child_id); }; virtual Error kill(const ProcessID &p_pid) = 0; @@ -328,8 +332,6 @@ public: virtual void benchmark_end_measure(const String &p_context, const String &p_what); virtual void benchmark_dump(); - virtual void process_and_drop_events() {} - virtual Error setup_remote_filesystem(const String &p_server_host, int p_port, const String &p_password, String &r_project_path); enum PreferredTextureFormat { diff --git a/core/os/pool_allocator.cpp b/core/os/pool_allocator.cpp deleted file mode 100644 index 9a993cd14f..0000000000 --- a/core/os/pool_allocator.cpp +++ /dev/null @@ -1,588 +0,0 @@ -/**************************************************************************/ -/* pool_allocator.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 "pool_allocator.h" - -#include "core/error/error_macros.h" -#include "core/os/memory.h" -#include "core/os/os.h" -#include "core/string/print_string.h" - -#define COMPACT_CHUNK(m_entry, m_to_pos) \ - if constexpr (true) { \ - void *_dst = &((unsigned char *)pool)[m_to_pos]; \ - void *_src = &((unsigned char *)pool)[(m_entry).pos]; \ - memmove(_dst, _src, aligned((m_entry).len)); \ - (m_entry).pos = m_to_pos; \ - } else \ - ((void)0) - -void PoolAllocator::mt_lock() const { -} - -void PoolAllocator::mt_unlock() const { -} - -bool PoolAllocator::get_free_entry(EntryArrayPos *p_pos) { - if (entry_count == entry_max) { - return false; - } - - for (int i = 0; i < entry_max; i++) { - if (entry_array[i].len == 0) { - *p_pos = i; - return true; - } - } - - ERR_PRINT("Out of memory Chunks!"); - - return false; // -} - -/** - * Find a hole - * @param p_pos The hole is behind the block pointed by this variable upon return. if pos==entry_count, then allocate at end - * @param p_for_size hole size - * @return false if hole found, true if no hole found - */ -bool PoolAllocator::find_hole(EntryArrayPos *p_pos, int p_for_size) { - /* position where previous entry ends. Defaults to zero (begin of pool) */ - - int prev_entry_end_pos = 0; - - for (int i = 0; i < entry_count; i++) { - Entry &entry = entry_array[entry_indices[i]]; - - /* determine hole size to previous entry */ - - int hole_size = entry.pos - prev_entry_end_pos; - - /* determine if what we want fits in that hole */ - if (hole_size >= p_for_size) { - *p_pos = i; - return true; - } - - /* prepare for next one */ - prev_entry_end_pos = entry_end(entry); - } - - /* No holes between entries, check at the end..*/ - - if ((pool_size - prev_entry_end_pos) >= p_for_size) { - *p_pos = entry_count; - return true; - } - - return false; -} - -void PoolAllocator::compact(int p_up_to) { - uint32_t prev_entry_end_pos = 0; - - if (p_up_to < 0) { - p_up_to = entry_count; - } - for (int i = 0; i < p_up_to; i++) { - Entry &entry = entry_array[entry_indices[i]]; - - /* determine hole size to previous entry */ - - int hole_size = entry.pos - prev_entry_end_pos; - - /* if we can compact, do it */ - if (hole_size > 0 && !entry.lock) { - COMPACT_CHUNK(entry, prev_entry_end_pos); - } - - /* prepare for next one */ - prev_entry_end_pos = entry_end(entry); - } -} - -void PoolAllocator::compact_up(int p_from) { - uint32_t next_entry_end_pos = pool_size; // - static_area_size; - - for (int i = entry_count - 1; i >= p_from; i--) { - Entry &entry = entry_array[entry_indices[i]]; - - /* determine hole size for next entry */ - - int hole_size = next_entry_end_pos - (entry.pos + aligned(entry.len)); - - /* if we can compact, do it */ - if (hole_size > 0 && !entry.lock) { - COMPACT_CHUNK(entry, (next_entry_end_pos - aligned(entry.len))); - } - - /* prepare for next one */ - next_entry_end_pos = entry.pos; - } -} - -bool PoolAllocator::find_entry_index(EntryIndicesPos *p_map_pos, const Entry *p_entry) { - EntryArrayPos entry_pos = entry_max; - - for (int i = 0; i < entry_count; i++) { - if (&entry_array[entry_indices[i]] == p_entry) { - entry_pos = i; - break; - } - } - - if (entry_pos == entry_max) { - return false; - } - - *p_map_pos = entry_pos; - return true; -} - -PoolAllocator::ID PoolAllocator::alloc(int p_size) { - ERR_FAIL_COND_V(p_size < 1, POOL_ALLOCATOR_INVALID_ID); - ERR_FAIL_COND_V(p_size > free_mem, POOL_ALLOCATOR_INVALID_ID); - - mt_lock(); - - if (entry_count == entry_max) { - mt_unlock(); - ERR_PRINT("entry_count==entry_max"); - return POOL_ALLOCATOR_INVALID_ID; - } - - int size_to_alloc = aligned(p_size); - - EntryIndicesPos new_entry_indices_pos; - - if (!find_hole(&new_entry_indices_pos, size_to_alloc)) { - /* No hole could be found, try compacting mem */ - compact(); - /* Then search again */ - - if (!find_hole(&new_entry_indices_pos, size_to_alloc)) { - mt_unlock(); - ERR_FAIL_V_MSG(POOL_ALLOCATOR_INVALID_ID, "Memory can't be compacted further."); - } - } - - EntryArrayPos new_entry_array_pos; - - bool found_free_entry = get_free_entry(&new_entry_array_pos); - - if (!found_free_entry) { - mt_unlock(); - ERR_FAIL_V_MSG(POOL_ALLOCATOR_INVALID_ID, "No free entry found in PoolAllocator."); - } - - /* move all entry indices up, make room for this one */ - for (int i = entry_count; i > new_entry_indices_pos; i--) { - entry_indices[i] = entry_indices[i - 1]; - } - - entry_indices[new_entry_indices_pos] = new_entry_array_pos; - - entry_count++; - - Entry &entry = entry_array[entry_indices[new_entry_indices_pos]]; - - entry.len = p_size; - entry.pos = (new_entry_indices_pos == 0) ? 0 : entry_end(entry_array[entry_indices[new_entry_indices_pos - 1]]); //alloc either at beginning or end of previous - entry.lock = 0; - entry.check = (check_count++) & CHECK_MASK; - free_mem -= size_to_alloc; - if (free_mem < free_mem_peak) { - free_mem_peak = free_mem; - } - - ID retval = (entry_indices[new_entry_indices_pos] << CHECK_BITS) | entry.check; - mt_unlock(); - - //ERR_FAIL_COND_V( (uintptr_t)get(retval)%align != 0, retval ); - - return retval; -} - -PoolAllocator::Entry *PoolAllocator::get_entry(ID p_mem) { - unsigned int check = p_mem & CHECK_MASK; - int entry = p_mem >> CHECK_BITS; - ERR_FAIL_INDEX_V(entry, entry_max, nullptr); - ERR_FAIL_COND_V(entry_array[entry].check != check, nullptr); - ERR_FAIL_COND_V(entry_array[entry].len == 0, nullptr); - - return &entry_array[entry]; -} - -const PoolAllocator::Entry *PoolAllocator::get_entry(ID p_mem) const { - unsigned int check = p_mem & CHECK_MASK; - int entry = p_mem >> CHECK_BITS; - ERR_FAIL_INDEX_V(entry, entry_max, nullptr); - ERR_FAIL_COND_V(entry_array[entry].check != check, nullptr); - ERR_FAIL_COND_V(entry_array[entry].len == 0, nullptr); - - return &entry_array[entry]; -} - -void PoolAllocator::free(ID p_mem) { - mt_lock(); - Entry *e = get_entry(p_mem); - if (!e) { - mt_unlock(); - ERR_PRINT("!e"); - return; - } - if (e->lock) { - mt_unlock(); - ERR_PRINT("e->lock"); - return; - } - - EntryIndicesPos entry_indices_pos; - - bool index_found = find_entry_index(&entry_indices_pos, e); - if (!index_found) { - mt_unlock(); - ERR_FAIL_COND(!index_found); - } - - for (int i = entry_indices_pos; i < (entry_count - 1); i++) { - entry_indices[i] = entry_indices[i + 1]; - } - - entry_count--; - free_mem += aligned(e->len); - e->clear(); - mt_unlock(); -} - -int PoolAllocator::get_size(ID p_mem) const { - int size; - mt_lock(); - - const Entry *e = get_entry(p_mem); - if (!e) { - mt_unlock(); - ERR_PRINT("!e"); - return 0; - } - - size = e->len; - - mt_unlock(); - - return size; -} - -Error PoolAllocator::resize(ID p_mem, int p_new_size) { - mt_lock(); - Entry *e = get_entry(p_mem); - - if (!e) { - mt_unlock(); - ERR_FAIL_NULL_V(e, ERR_INVALID_PARAMETER); - } - - if (needs_locking && e->lock) { - mt_unlock(); - ERR_FAIL_COND_V(e->lock, ERR_ALREADY_IN_USE); - } - - uint32_t alloc_size = aligned(p_new_size); - - if ((uint32_t)aligned(e->len) == alloc_size) { - e->len = p_new_size; - mt_unlock(); - return OK; - } else if (e->len > (uint32_t)p_new_size) { - free_mem += aligned(e->len); - free_mem -= alloc_size; - e->len = p_new_size; - mt_unlock(); - return OK; - } - - //p_new_size = align(p_new_size) - int _free = free_mem; // - static_area_size; - - if (uint32_t(_free + aligned(e->len)) < alloc_size) { - mt_unlock(); - ERR_FAIL_V(ERR_OUT_OF_MEMORY); - } - - EntryIndicesPos entry_indices_pos; - - bool index_found = find_entry_index(&entry_indices_pos, e); - - if (!index_found) { - mt_unlock(); - ERR_FAIL_COND_V(!index_found, ERR_BUG); - } - - //no need to move stuff around, it fits before the next block - uint32_t next_pos; - if (entry_indices_pos + 1 == entry_count) { - next_pos = pool_size; // - static_area_size; - } else { - next_pos = entry_array[entry_indices[entry_indices_pos + 1]].pos; - } - - if ((next_pos - e->pos) > alloc_size) { - free_mem += aligned(e->len); - e->len = p_new_size; - free_mem -= alloc_size; - mt_unlock(); - return OK; - } - //it doesn't fit, compact around BEFORE current index (make room behind) - - compact(entry_indices_pos + 1); - - if ((next_pos - e->pos) > alloc_size) { - //now fits! hooray! - free_mem += aligned(e->len); - e->len = p_new_size; - free_mem -= alloc_size; - mt_unlock(); - if (free_mem < free_mem_peak) { - free_mem_peak = free_mem; - } - return OK; - } - - //STILL doesn't fit, compact around AFTER current index (make room after) - - compact_up(entry_indices_pos + 1); - - if ((entry_array[entry_indices[entry_indices_pos + 1]].pos - e->pos) > alloc_size) { - //now fits! hooray! - free_mem += aligned(e->len); - e->len = p_new_size; - free_mem -= alloc_size; - mt_unlock(); - if (free_mem < free_mem_peak) { - free_mem_peak = free_mem; - } - return OK; - } - - mt_unlock(); - ERR_FAIL_V(ERR_OUT_OF_MEMORY); -} - -Error PoolAllocator::lock(ID p_mem) { - if (!needs_locking) { - return OK; - } - mt_lock(); - Entry *e = get_entry(p_mem); - if (!e) { - mt_unlock(); - ERR_PRINT("!e"); - return ERR_INVALID_PARAMETER; - } - e->lock++; - mt_unlock(); - return OK; -} - -bool PoolAllocator::is_locked(ID p_mem) const { - if (!needs_locking) { - return false; - } - - mt_lock(); - const Entry *e = const_cast<PoolAllocator *>(this)->get_entry(p_mem); - if (!e) { - mt_unlock(); - ERR_PRINT("!e"); - return false; - } - bool locked = e->lock; - mt_unlock(); - return locked; -} - -const void *PoolAllocator::get(ID p_mem) const { - if (!needs_locking) { - const Entry *e = get_entry(p_mem); - ERR_FAIL_NULL_V(e, nullptr); - return &pool[e->pos]; - } - - mt_lock(); - const Entry *e = get_entry(p_mem); - - if (!e) { - mt_unlock(); - ERR_FAIL_NULL_V(e, nullptr); - } - if (e->lock == 0) { - mt_unlock(); - ERR_PRINT("e->lock == 0"); - return nullptr; - } - - if ((int)e->pos >= pool_size) { - mt_unlock(); - ERR_PRINT("e->pos<0 || e->pos>=pool_size"); - return nullptr; - } - const void *ptr = &pool[e->pos]; - - mt_unlock(); - - return ptr; -} - -void *PoolAllocator::get(ID p_mem) { - if (!needs_locking) { - Entry *e = get_entry(p_mem); - ERR_FAIL_NULL_V(e, nullptr); - return &pool[e->pos]; - } - - mt_lock(); - Entry *e = get_entry(p_mem); - - if (!e) { - mt_unlock(); - ERR_FAIL_NULL_V(e, nullptr); - } - if (e->lock == 0) { - mt_unlock(); - ERR_PRINT("e->lock == 0"); - return nullptr; - } - - if ((int)e->pos >= pool_size) { - mt_unlock(); - ERR_PRINT("e->pos<0 || e->pos>=pool_size"); - return nullptr; - } - void *ptr = &pool[e->pos]; - - mt_unlock(); - - return ptr; -} - -void PoolAllocator::unlock(ID p_mem) { - if (!needs_locking) { - return; - } - mt_lock(); - Entry *e = get_entry(p_mem); - if (!e) { - mt_unlock(); - ERR_FAIL_NULL(e); - } - if (e->lock == 0) { - mt_unlock(); - ERR_PRINT("e->lock == 0"); - return; - } - e->lock--; - mt_unlock(); -} - -int PoolAllocator::get_used_mem() const { - return pool_size - free_mem; -} - -int PoolAllocator::get_free_peak() { - return free_mem_peak; -} - -int PoolAllocator::get_free_mem() { - return free_mem; -} - -void PoolAllocator::create_pool(void *p_mem, int p_size, int p_max_entries) { - pool = (uint8_t *)p_mem; - pool_size = p_size; - - entry_array = memnew_arr(Entry, p_max_entries); - entry_indices = memnew_arr(int, p_max_entries); - entry_max = p_max_entries; - entry_count = 0; - - free_mem = p_size; - free_mem_peak = p_size; - - check_count = 0; -} - -PoolAllocator::PoolAllocator(int p_size, bool p_needs_locking, int p_max_entries) { - mem_ptr = memalloc(p_size); - ERR_FAIL_NULL(mem_ptr); - align = 1; - create_pool(mem_ptr, p_size, p_max_entries); - needs_locking = p_needs_locking; -} - -PoolAllocator::PoolAllocator(void *p_mem, int p_size, int p_align, bool p_needs_locking, int p_max_entries) { - if (p_align > 1) { - uint8_t *mem8 = (uint8_t *)p_mem; - uint64_t ofs = (uint64_t)mem8; - if (ofs % p_align) { - int dif = p_align - (ofs % p_align); - mem8 += p_align - (ofs % p_align); - p_size -= dif; - p_mem = (void *)mem8; - } - } - - create_pool(p_mem, p_size, p_max_entries); - needs_locking = p_needs_locking; - align = p_align; - mem_ptr = nullptr; -} - -PoolAllocator::PoolAllocator(int p_align, int p_size, bool p_needs_locking, int p_max_entries) { - ERR_FAIL_COND(p_align < 1); - mem_ptr = Memory::alloc_static(p_size + p_align, true); - uint8_t *mem8 = (uint8_t *)mem_ptr; - uint64_t ofs = (uint64_t)mem8; - if (ofs % p_align) { - mem8 += p_align - (ofs % p_align); - } - create_pool(mem8, p_size, p_max_entries); - needs_locking = p_needs_locking; - align = p_align; -} - -PoolAllocator::~PoolAllocator() { - if (mem_ptr) { - memfree(mem_ptr); - } - - memdelete_arr(entry_array); - memdelete_arr(entry_indices); -} diff --git a/core/os/pool_allocator.h b/core/os/pool_allocator.h deleted file mode 100644 index be8b6e061b..0000000000 --- a/core/os/pool_allocator.h +++ /dev/null @@ -1,148 +0,0 @@ -/**************************************************************************/ -/* pool_allocator.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 POOL_ALLOCATOR_H -#define POOL_ALLOCATOR_H - -#include "core/typedefs.h" - -/** - * Generic Pool Allocator. - * This is a generic memory pool allocator, with locking, compacting and alignment. (@TODO alignment) - * It used as a standard way to manage allocation in a specific region of memory, such as texture memory, - * audio sample memory, or just any kind of memory overall. - * (@TODO) abstraction should be greater, because in many platforms, you need to manage a nonreachable memory. - */ - -enum { - POOL_ALLOCATOR_INVALID_ID = -1 ///< default invalid value. use INVALID_ID( id ) to test -}; - -class PoolAllocator { -public: - typedef int ID; - -private: - enum { - CHECK_BITS = 8, - CHECK_LEN = (1 << CHECK_BITS), - CHECK_MASK = CHECK_LEN - 1 - - }; - - struct Entry { - unsigned int pos = 0; - unsigned int len = 0; - unsigned int lock = 0; - unsigned int check = 0; - - inline void clear() { - pos = 0; - len = 0; - lock = 0; - check = 0; - } - Entry() {} - }; - - typedef int EntryArrayPos; - typedef int EntryIndicesPos; - - Entry *entry_array = nullptr; - int *entry_indices = nullptr; - int entry_max = 0; - int entry_count = 0; - - uint8_t *pool = nullptr; - void *mem_ptr = nullptr; - int pool_size = 0; - - int free_mem = 0; - int free_mem_peak = 0; - - unsigned int check_count = 0; - int align = 1; - - bool needs_locking = false; - - inline int entry_end(const Entry &p_entry) const { - return p_entry.pos + aligned(p_entry.len); - } - inline int aligned(int p_size) const { - int rem = p_size % align; - if (rem) { - p_size += align - rem; - } - - return p_size; - } - - void compact(int p_up_to = -1); - void compact_up(int p_from = 0); - bool get_free_entry(EntryArrayPos *p_pos); - bool find_hole(EntryArrayPos *p_pos, int p_for_size); - bool find_entry_index(EntryIndicesPos *p_map_pos, const Entry *p_entry); - Entry *get_entry(ID p_mem); - const Entry *get_entry(ID p_mem) const; - - void create_pool(void *p_mem, int p_size, int p_max_entries); - -protected: - virtual void mt_lock() const; ///< Reimplement for custom mt locking - virtual void mt_unlock() const; ///< Reimplement for custom mt locking - -public: - enum { - DEFAULT_MAX_ALLOCS = 4096, - }; - - ID alloc(int p_size); ///< Alloc memory, get an ID on success, POOL_ALOCATOR_INVALID_ID on failure - void free(ID p_mem); ///< Free allocated memory - Error resize(ID p_mem, int p_new_size); ///< resize a memory chunk - int get_size(ID p_mem) const; - - int get_free_mem(); ///< get free memory - int get_used_mem() const; - int get_free_peak(); ///< get free memory - - Error lock(ID p_mem); //@todo move this out - void *get(ID p_mem); - const void *get(ID p_mem) const; - void unlock(ID p_mem); - bool is_locked(ID p_mem) const; - - PoolAllocator(int p_size, bool p_needs_locking = false, int p_max_entries = DEFAULT_MAX_ALLOCS); - PoolAllocator(void *p_mem, int p_size, int p_align = 1, bool p_needs_locking = false, int p_max_entries = DEFAULT_MAX_ALLOCS); - PoolAllocator(int p_align, int p_size, bool p_needs_locking = false, int p_max_entries = DEFAULT_MAX_ALLOCS); - - virtual ~PoolAllocator(); -}; - -#endif // POOL_ALLOCATOR_H diff --git a/core/os/safe_binary_mutex.h b/core/os/safe_binary_mutex.h index 1e98cc074c..74a20043a3 100644 --- a/core/os/safe_binary_mutex.h +++ b/core/os/safe_binary_mutex.h @@ -37,6 +37,11 @@ #ifdef THREADS_ENABLED +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wundefined-var-template" +#endif + // A very special kind of mutex, used in scenarios where these // requirements hold at the same time: // - Must be used with a condition variable (only binary mutexes are suitable). @@ -47,69 +52,90 @@ // Also, don't forget to declare the thread_local variable on each use. template <int Tag> class SafeBinaryMutex { - friend class MutexLock<SafeBinaryMutex>; + friend class MutexLock<SafeBinaryMutex<Tag>>; using StdMutexType = THREADING_NAMESPACE::mutex; mutable THREADING_NAMESPACE::mutex mutex; - static thread_local uint32_t count; + + struct TLSData { + mutable THREADING_NAMESPACE::unique_lock<THREADING_NAMESPACE::mutex> lock; + uint32_t count = 0; + + TLSData(SafeBinaryMutex<Tag> &p_mutex) : + lock(p_mutex.mutex, THREADING_NAMESPACE::defer_lock) {} + }; + static thread_local TLSData tls_data; public: _ALWAYS_INLINE_ void lock() const { - if (++count == 1) { - mutex.lock(); + if (++tls_data.count == 1) { + tls_data.lock.lock(); } } _ALWAYS_INLINE_ void unlock() const { - DEV_ASSERT(count); - if (--count == 0) { - mutex.unlock(); + DEV_ASSERT(tls_data.count); + if (--tls_data.count == 0) { + tls_data.lock.unlock(); } } - _ALWAYS_INLINE_ bool try_lock() const { - if (count) { - count++; - return true; - } else { - if (mutex.try_lock()) { - count++; - return true; - } else { - return false; - } - } + _ALWAYS_INLINE_ THREADING_NAMESPACE::unique_lock<THREADING_NAMESPACE::mutex> &_get_lock() const { + return const_cast<THREADING_NAMESPACE::unique_lock<THREADING_NAMESPACE::mutex> &>(tls_data.lock); + } + + _ALWAYS_INLINE_ SafeBinaryMutex() { } - ~SafeBinaryMutex() { - DEV_ASSERT(!count); + _ALWAYS_INLINE_ ~SafeBinaryMutex() { + DEV_ASSERT(!tls_data.count); } }; -// This specialization is needed so manual locking and MutexLock can be used -// at the same time on a SafeBinaryMutex. template <int Tag> class MutexLock<SafeBinaryMutex<Tag>> { friend class ConditionVariable; - THREADING_NAMESPACE::unique_lock<THREADING_NAMESPACE::mutex> lock; + const SafeBinaryMutex<Tag> &mutex; public: - _ALWAYS_INLINE_ explicit MutexLock(const SafeBinaryMutex<Tag> &p_mutex) : - lock(p_mutex.mutex) { - SafeBinaryMutex<Tag>::count++; - }; - _ALWAYS_INLINE_ ~MutexLock() { - SafeBinaryMutex<Tag>::count--; - }; + explicit MutexLock(const SafeBinaryMutex<Tag> &p_mutex) : + mutex(p_mutex) { + mutex.lock(); + } + + ~MutexLock() { + mutex.unlock(); + } + + _ALWAYS_INLINE_ void temp_relock() const { + mutex.lock(); + } + + _ALWAYS_INLINE_ void temp_unlock() const { + mutex.unlock(); + } + + // TODO: Implement a `try_temp_relock` if needed (will also need a dummy method below). }; +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + #else // No threads. template <int Tag> -class SafeBinaryMutex : public MutexImpl { - static thread_local uint32_t count; +class SafeBinaryMutex { + struct TLSData { + TLSData(SafeBinaryMutex<Tag> &p_mutex) {} + }; + static thread_local TLSData tls_data; + +public: + void lock() const {} + void unlock() const {} }; template <int Tag> @@ -117,6 +143,9 @@ class MutexLock<SafeBinaryMutex<Tag>> { public: MutexLock(const SafeBinaryMutex<Tag> &p_mutex) {} ~MutexLock() {} + + void temp_relock() const {} + void temp_unlock() const {} }; #endif // THREADS_ENABLED diff --git a/core/register_core_types.cpp b/core/register_core_types.cpp index c0a86e9fb7..220ed9da31 100644 --- a/core/register_core_types.cpp +++ b/core/register_core_types.cpp @@ -79,6 +79,7 @@ #include "core/os/time.h" #include "core/string/optimized_translation.h" #include "core/string/translation.h" +#include "core/string/translation_server.h" static Ref<ResourceFormatSaverBinary> resource_saver_binary; static Ref<ResourceFormatLoaderBinary> resource_loader_binary; @@ -106,8 +107,6 @@ 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; @@ -296,8 +295,6 @@ 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"); } @@ -348,7 +345,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", worker_thread_pool)); + Engine::get_singleton()->add_singleton(Engine::Singleton("WorkerThreadPool", WorkerThreadPool::get_singleton())); OS::get_singleton()->benchmark_end_measure("Core", "Register Singletons"); } @@ -381,8 +378,6 @@ 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/node_path.cpp b/core/string/node_path.cpp index 8ae2efb787..fdc72bc8dc 100644 --- a/core/string/node_path.cpp +++ b/core/string/node_path.cpp @@ -215,7 +215,10 @@ StringName NodePath::get_concatenated_names() const { String concatenated; const StringName *sn = data->path.ptr(); for (int i = 0; i < pc; i++) { - concatenated += i == 0 ? sn[i].operator String() : "/" + sn[i]; + if (i > 0) { + concatenated += "/"; + } + concatenated += sn[i].operator String(); } data->concatenated_path = concatenated; } @@ -230,7 +233,10 @@ StringName NodePath::get_concatenated_subnames() const { String concatenated; const StringName *ssn = data->subpath.ptr(); for (int i = 0; i < spc; i++) { - concatenated += i == 0 ? ssn[i].operator String() : ":" + ssn[i]; + if (i > 0) { + concatenated += ":"; + } + concatenated += ssn[i].operator String(); } data->concatenated_subpath = concatenated; } diff --git a/core/string/string_name.cpp b/core/string/string_name.cpp index 658297d805..dff19b3a41 100644 --- a/core/string/string_name.cpp +++ b/core/string/string_name.cpp @@ -39,18 +39,33 @@ StaticCString StaticCString::create(const char *p_ptr) { return scs; } -StringName::_Data *StringName::_table[STRING_TABLE_LEN]; +bool StringName::_Data::operator==(const String &p_name) const { + if (cname) { + return p_name == cname; + } else { + return name == p_name; + } +} -StringName _scs_create(const char *p_chr, bool p_static) { - return (p_chr[0] ? StringName(StaticCString::create(p_chr), p_static) : StringName()); +bool StringName::_Data::operator!=(const String &p_name) const { + return !operator==(p_name); } -bool StringName::configured = false; -Mutex StringName::mutex; +bool StringName::_Data::operator==(const char *p_name) const { + if (cname) { + return strcmp(cname, p_name) == 0; + } else { + return name == p_name; + } +} -#ifdef DEBUG_ENABLED -bool StringName::debug_stringname = false; -#endif +bool StringName::_Data::operator!=(const char *p_name) const { + return !operator==(p_name); +} + +StringName _scs_create(const char *p_chr, bool p_static) { + return (p_chr[0] ? StringName(StaticCString::create(p_chr), p_static) : StringName()); +} void StringName::setup() { ERR_FAIL_COND(configured); @@ -147,20 +162,25 @@ void StringName::unref() { _data = nullptr; } +uint32_t StringName::get_empty_hash() { + static uint32_t empty_hash = String::hash(""); + return empty_hash; +} + bool StringName::operator==(const String &p_name) const { - if (!_data) { - return (p_name.length() == 0); + if (_data) { + return _data->operator==(p_name); } - return (_data->get_name() == p_name); + return p_name.is_empty(); } bool StringName::operator==(const char *p_name) const { - if (!_data) { - return (p_name[0] == 0); + if (_data) { + return _data->operator==(p_name); } - return (_data->get_name() == p_name); + return p_name[0] == 0; } bool StringName::operator!=(const String &p_name) const { @@ -177,9 +197,47 @@ bool StringName::operator!=(const StringName &p_name) const { return _data != p_name._data; } -void StringName::operator=(const StringName &p_name) { +char32_t StringName::operator[](int p_index) const { + if (_data) { + if (_data->cname) { + CRASH_BAD_INDEX(p_index, static_cast<long>(strlen(_data->cname))); + return _data->cname[p_index]; + } else { + return _data->name[p_index]; + } + } + + CRASH_BAD_INDEX(p_index, 0); + return 0; +} + +int StringName::length() const { + if (_data) { + if (_data->cname) { + return strlen(_data->cname); + } else { + return _data->name.length(); + } + } + + return 0; +} + +bool StringName::is_empty() const { + if (_data) { + if (_data->cname) { + return _data->cname[0] == 0; + } else { + return _data->name.is_empty(); + } + } + + return true; +} + +StringName &StringName::operator=(const StringName &p_name) { if (this == &p_name) { - return; + return *this; } unref(); @@ -187,6 +245,8 @@ void StringName::operator=(const StringName &p_name) { if (p_name._data && p_name._data->refcount.ref()) { _data = p_name._data; } + + return *this; } StringName::StringName(const StringName &p_name) { @@ -200,11 +260,10 @@ StringName::StringName(const StringName &p_name) { } void StringName::assign_static_unique_class_name(StringName *ptr, const char *p_name) { - mutex.lock(); + MutexLock lock(mutex); if (*ptr == StringName()) { *ptr = StringName(p_name, true); } - mutex.unlock(); } StringName::StringName(const char *p_name, bool p_static) { @@ -226,7 +285,7 @@ StringName::StringName(const char *p_name, bool p_static) { while (_data) { // compare hash first - if (_data->hash == hash && _data->get_name() == p_name) { + if (_data->hash == hash && _data->operator==(p_name)) { break; } _data = _data->next; @@ -285,7 +344,7 @@ StringName::StringName(const StaticCString &p_static_string, bool p_static) { while (_data) { // compare hash first - if (_data->hash == hash && _data->get_name() == p_static_string.ptr) { + if (_data->hash == hash && _data->operator==(p_static_string.ptr)) { break; } _data = _data->next; @@ -343,7 +402,7 @@ StringName::StringName(const String &p_name, bool p_static) { _data = _table[idx]; while (_data) { - if (_data->hash == hash && _data->get_name() == p_name) { + if (_data->hash == hash && _data->operator==(p_name)) { break; } _data = _data->next; @@ -402,7 +461,7 @@ StringName StringName::search(const char *p_name) { while (_data) { // compare hash first - if (_data->hash == hash && _data->get_name() == p_name) { + if (_data->hash == hash && _data->operator==(p_name)) { break; } _data = _data->next; @@ -439,7 +498,7 @@ StringName StringName::search(const char32_t *p_name) { while (_data) { // compare hash first - if (_data->hash == hash && _data->get_name() == p_name) { + if (_data->hash == hash && _data->operator==(p_name)) { break; } _data = _data->next; @@ -465,7 +524,7 @@ StringName StringName::search(const String &p_name) { while (_data) { // compare hash first - if (_data->hash == hash && p_name == _data->get_name()) { + if (_data->hash == hash && _data->operator==(p_name)) { break; } _data = _data->next; @@ -484,15 +543,15 @@ StringName StringName::search(const String &p_name) { } bool operator==(const String &p_name, const StringName &p_string_name) { - return p_name == p_string_name.operator String(); + return p_string_name.operator==(p_name); } bool operator!=(const String &p_name, const StringName &p_string_name) { - return p_name != p_string_name.operator String(); + return p_string_name.operator!=(p_name); } bool operator==(const char *p_name, const StringName &p_string_name) { - return p_name == p_string_name.operator String(); + return p_string_name.operator==(p_name); } bool operator!=(const char *p_name, const StringName &p_string_name) { - return p_name != p_string_name.operator String(); + return p_string_name.operator!=(p_name); } diff --git a/core/string/string_name.h b/core/string/string_name.h index 89b4c07e0e..d4b70d311d 100644 --- a/core/string/string_name.h +++ b/core/string/string_name.h @@ -60,6 +60,11 @@ class StringName { uint32_t debug_references = 0; #endif String get_name() const { return cname ? String(cname) : name; } + bool operator==(const String &p_name) const; + bool operator!=(const String &p_name) const; + bool operator==(const char *p_name) const; + bool operator!=(const char *p_name) const; + int idx = 0; uint32_t hash = 0; _Data *prev = nullptr; @@ -67,7 +72,7 @@ class StringName { _Data() {} }; - static _Data *_table[STRING_TABLE_LEN]; + static inline _Data *_table[STRING_TABLE_LEN]; _Data *_data = nullptr; @@ -75,10 +80,11 @@ class StringName { friend void register_core_types(); friend void unregister_core_types(); friend class Main; - static Mutex mutex; + static inline Mutex mutex; static void setup(); static void cleanup(); - static bool configured; + static uint32_t get_empty_hash(); + static inline bool configured = false; #ifdef DEBUG_ENABLED struct DebugSortReferences { bool operator()(const _Data *p_left, const _Data *p_right) const { @@ -86,7 +92,7 @@ class StringName { } }; - static bool debug_stringname; + static inline bool debug_stringname = false; #endif StringName(_Data *p_data) { _data = p_data; } @@ -99,6 +105,10 @@ public: bool operator!=(const String &p_name) const; bool operator!=(const char *p_name) const; + char32_t operator[](int p_index) const; + int length() const; + bool is_empty() const; + _FORCE_INLINE_ bool is_node_unique_name() const { if (!_data) { return false; @@ -130,7 +140,7 @@ public: if (_data) { return _data->hash; } else { - return 0; + return get_empty_hash(); } } _FORCE_INLINE_ const void *data_unique_pointer() const { @@ -175,7 +185,7 @@ public: } }; - void operator=(const StringName &p_name); + StringName &operator=(const StringName &p_name); StringName(const char *p_name, bool p_static = false); StringName(const StringName &p_name); StringName(const String &p_name, bool p_static = false); diff --git a/core/string/translation.cpp b/core/string/translation.cpp index 432016284a..020949371f 100644 --- a/core/string/translation.cpp +++ b/core/string/translation.cpp @@ -29,16 +29,10 @@ /**************************************************************************/ #include "translation.h" -#include "translation.compat.inc" -#include "core/config/project_settings.h" -#include "core/io/resource_loader.h" #include "core/os/os.h" -#include "core/string/locales.h" - -#ifdef TOOLS_ENABLED -#include "main/main.h" -#endif +#include "core/os/thread.h" +#include "core/string/translation_server.h" Dictionary Translation::_get_messages() const { Dictionary d; @@ -173,911 +167,3 @@ void Translation::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "messages", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_messages", "_get_messages"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "locale"), "set_locale", "get_locale"); } - -/////////////////////////////////////////////// - -struct _character_accent_pair { - const char32_t character; - const char32_t *accented_character; -}; - -static _character_accent_pair _character_to_accented[] = { - { 'A', U"Å" }, - { 'B', U"ß" }, - { 'C', U"Ç" }, - { 'D', U"Ð" }, - { 'E', U"É" }, - { 'F', U"F́" }, - { 'G', U"Ĝ" }, - { 'H', U"Ĥ" }, - { 'I', U"Ĩ" }, - { 'J', U"Ĵ" }, - { 'K', U"ĸ" }, - { 'L', U"Ł" }, - { 'M', U"Ḿ" }, - { 'N', U"й" }, - { 'O', U"Ö" }, - { 'P', U"Ṕ" }, - { 'Q', U"Q́" }, - { 'R', U"Ř" }, - { 'S', U"Ŝ" }, - { 'T', U"Ŧ" }, - { 'U', U"Ũ" }, - { 'V', U"Ṽ" }, - { 'W', U"Ŵ" }, - { 'X', U"X́" }, - { 'Y', U"Ÿ" }, - { 'Z', U"Ž" }, - { 'a', U"á" }, - { 'b', U"ḅ" }, - { 'c', U"ć" }, - { 'd', U"d́" }, - { 'e', U"é" }, - { 'f', U"f́" }, - { 'g', U"ǵ" }, - { 'h', U"h̀" }, - { 'i', U"í" }, - { 'j', U"ǰ" }, - { 'k', U"ḱ" }, - { 'l', U"ł" }, - { 'm', U"m̀" }, - { 'n', U"ή" }, - { 'o', U"ô" }, - { 'p', U"ṕ" }, - { 'q', U"q́" }, - { 'r', U"ŕ" }, - { 's', U"š" }, - { 't', U"ŧ" }, - { 'u', U"ü" }, - { 'v', U"ṽ" }, - { 'w', U"ŵ" }, - { 'x', U"x́" }, - { 'y', U"ý" }, - { 'z', U"ź" }, -}; - -Vector<TranslationServer::LocaleScriptInfo> TranslationServer::locale_script_info; - -HashMap<String, String> TranslationServer::language_map; -HashMap<String, String> TranslationServer::script_map; -HashMap<String, String> TranslationServer::locale_rename_map; -HashMap<String, String> TranslationServer::country_name_map; -HashMap<String, String> TranslationServer::variant_map; -HashMap<String, String> TranslationServer::country_rename_map; - -void TranslationServer::init_locale_info() { - // Init locale info. - language_map.clear(); - int idx = 0; - while (language_list[idx][0] != nullptr) { - language_map[language_list[idx][0]] = String::utf8(language_list[idx][1]); - idx++; - } - - // Init locale-script map. - locale_script_info.clear(); - idx = 0; - while (locale_scripts[idx][0] != nullptr) { - LocaleScriptInfo info; - info.name = locale_scripts[idx][0]; - info.script = locale_scripts[idx][1]; - info.default_country = locale_scripts[idx][2]; - Vector<String> supported_countries = String(locale_scripts[idx][3]).split(",", false); - for (int i = 0; i < supported_countries.size(); i++) { - info.supported_countries.insert(supported_countries[i]); - } - locale_script_info.push_back(info); - idx++; - } - - // Init supported script list. - script_map.clear(); - idx = 0; - while (script_list[idx][0] != nullptr) { - script_map[script_list[idx][1]] = String::utf8(script_list[idx][0]); - idx++; - } - - // Init regional variant map. - variant_map.clear(); - idx = 0; - while (locale_variants[idx][0] != nullptr) { - variant_map[locale_variants[idx][0]] = locale_variants[idx][1]; - idx++; - } - - // Init locale renames. - locale_rename_map.clear(); - idx = 0; - while (locale_renames[idx][0] != nullptr) { - if (!String(locale_renames[idx][1]).is_empty()) { - locale_rename_map[locale_renames[idx][0]] = locale_renames[idx][1]; - } - idx++; - } - - // Init country names. - country_name_map.clear(); - idx = 0; - while (country_names[idx][0] != nullptr) { - country_name_map[String(country_names[idx][0])] = String::utf8(country_names[idx][1]); - idx++; - } - - // Init country renames. - country_rename_map.clear(); - idx = 0; - while (country_renames[idx][0] != nullptr) { - if (!String(country_renames[idx][1]).is_empty()) { - country_rename_map[country_renames[idx][0]] = country_renames[idx][1]; - } - idx++; - } -} - -String TranslationServer::standardize_locale(const String &p_locale) const { - return _standardize_locale(p_locale, false); -} - -String TranslationServer::_standardize_locale(const String &p_locale, bool p_add_defaults) const { - // Replaces '-' with '_' for macOS style locales. - String univ_locale = p_locale.replace("-", "_"); - - // Extract locale elements. - String lang_name, script_name, country_name, variant_name; - Vector<String> locale_elements = univ_locale.get_slice("@", 0).split("_"); - lang_name = locale_elements[0]; - if (locale_elements.size() >= 2) { - if (locale_elements[1].length() == 4 && is_ascii_upper_case(locale_elements[1][0]) && is_ascii_lower_case(locale_elements[1][1]) && is_ascii_lower_case(locale_elements[1][2]) && is_ascii_lower_case(locale_elements[1][3])) { - script_name = locale_elements[1]; - } - if (locale_elements[1].length() == 2 && is_ascii_upper_case(locale_elements[1][0]) && is_ascii_upper_case(locale_elements[1][1])) { - country_name = locale_elements[1]; - } - } - if (locale_elements.size() >= 3) { - if (locale_elements[2].length() == 2 && is_ascii_upper_case(locale_elements[2][0]) && is_ascii_upper_case(locale_elements[2][1])) { - country_name = locale_elements[2]; - } else if (variant_map.has(locale_elements[2].to_lower()) && variant_map[locale_elements[2].to_lower()] == lang_name) { - variant_name = locale_elements[2].to_lower(); - } - } - if (locale_elements.size() >= 4) { - if (variant_map.has(locale_elements[3].to_lower()) && variant_map[locale_elements[3].to_lower()] == lang_name) { - variant_name = locale_elements[3].to_lower(); - } - } - - // Try extract script and variant from the extra part. - Vector<String> script_extra = univ_locale.get_slice("@", 1).split(";"); - for (int i = 0; i < script_extra.size(); i++) { - if (script_extra[i].to_lower() == "cyrillic") { - script_name = "Cyrl"; - break; - } else if (script_extra[i].to_lower() == "latin") { - script_name = "Latn"; - break; - } else if (script_extra[i].to_lower() == "devanagari") { - script_name = "Deva"; - break; - } else if (variant_map.has(script_extra[i].to_lower()) && variant_map[script_extra[i].to_lower()] == lang_name) { - variant_name = script_extra[i].to_lower(); - } - } - - // Handles known non-ISO language names used e.g. on Windows. - if (locale_rename_map.has(lang_name)) { - lang_name = locale_rename_map[lang_name]; - } - - // Handle country renames. - if (country_rename_map.has(country_name)) { - country_name = country_rename_map[country_name]; - } - - // Remove unsupported script codes. - if (!script_map.has(script_name)) { - script_name = ""; - } - - // Add script code base on language and country codes for some ambiguous cases. - if (p_add_defaults) { - if (script_name.is_empty()) { - for (int i = 0; i < locale_script_info.size(); i++) { - const LocaleScriptInfo &info = locale_script_info[i]; - if (info.name == lang_name) { - if (country_name.is_empty() || info.supported_countries.has(country_name)) { - script_name = info.script; - break; - } - } - } - } - if (!script_name.is_empty() && country_name.is_empty()) { - // Add conntry code based on script for some ambiguous cases. - for (int i = 0; i < locale_script_info.size(); i++) { - const LocaleScriptInfo &info = locale_script_info[i]; - if (info.name == lang_name && info.script == script_name) { - country_name = info.default_country; - break; - } - } - } - } - - // Combine results. - String out = lang_name; - if (!script_name.is_empty()) { - out = out + "_" + script_name; - } - if (!country_name.is_empty()) { - out = out + "_" + country_name; - } - if (!variant_name.is_empty()) { - out = out + "_" + variant_name; - } - return out; -} - -int TranslationServer::compare_locales(const String &p_locale_a, const String &p_locale_b) const { - String locale_a = _standardize_locale(p_locale_a, true); - String locale_b = _standardize_locale(p_locale_b, true); - - if (locale_a == locale_b) { - // Exact match. - return 10; - } - - Vector<String> locale_a_elements = locale_a.split("_"); - Vector<String> locale_b_elements = locale_b.split("_"); - if (locale_a_elements[0] == locale_b_elements[0]) { - // Matching language, both locales have extra parts. - // Return number of matching elements. - int matching_elements = 1; - for (int i = 1; i < locale_a_elements.size(); i++) { - for (int j = 1; j < locale_b_elements.size(); j++) { - if (locale_a_elements[i] == locale_b_elements[j]) { - matching_elements++; - } - } - } - return matching_elements; - } else { - // No match. - return 0; - } -} - -String TranslationServer::get_locale_name(const String &p_locale) const { - String lang_name, script_name, country_name; - Vector<String> locale_elements = standardize_locale(p_locale).split("_"); - lang_name = locale_elements[0]; - if (locale_elements.size() >= 2) { - if (locale_elements[1].length() == 4 && is_ascii_upper_case(locale_elements[1][0]) && is_ascii_lower_case(locale_elements[1][1]) && is_ascii_lower_case(locale_elements[1][2]) && is_ascii_lower_case(locale_elements[1][3])) { - script_name = locale_elements[1]; - } - if (locale_elements[1].length() == 2 && is_ascii_upper_case(locale_elements[1][0]) && is_ascii_upper_case(locale_elements[1][1])) { - country_name = locale_elements[1]; - } - } - if (locale_elements.size() >= 3) { - if (locale_elements[2].length() == 2 && is_ascii_upper_case(locale_elements[2][0]) && is_ascii_upper_case(locale_elements[2][1])) { - country_name = locale_elements[2]; - } - } - - String name = language_map[lang_name]; - if (!script_name.is_empty()) { - name = name + " (" + script_map[script_name] + ")"; - } - if (!country_name.is_empty()) { - name = name + ", " + country_name_map[country_name]; - } - return name; -} - -Vector<String> TranslationServer::get_all_languages() const { - Vector<String> languages; - - for (const KeyValue<String, String> &E : language_map) { - languages.push_back(E.key); - } - - return languages; -} - -String TranslationServer::get_language_name(const String &p_language) const { - return language_map[p_language]; -} - -Vector<String> TranslationServer::get_all_scripts() const { - Vector<String> scripts; - - for (const KeyValue<String, String> &E : script_map) { - scripts.push_back(E.key); - } - - return scripts; -} - -String TranslationServer::get_script_name(const String &p_script) const { - return script_map[p_script]; -} - -Vector<String> TranslationServer::get_all_countries() const { - Vector<String> countries; - - for (const KeyValue<String, String> &E : country_name_map) { - countries.push_back(E.key); - } - - return countries; -} - -String TranslationServer::get_country_name(const String &p_country) const { - return country_name_map[p_country]; -} - -void TranslationServer::set_locale(const String &p_locale) { - String new_locale = standardize_locale(p_locale); - if (locale == new_locale) { - return; - } - - locale = new_locale; - ResourceLoader::reload_translation_remaps(); - - if (OS::get_singleton()->get_main_loop()) { - OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_TRANSLATION_CHANGED); - } -} - -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); - } - - return locales; -} - -void TranslationServer::add_translation(const Ref<Translation> &p_translation) { - translations.insert(p_translation); -} - -void TranslationServer::remove_translation(const Ref<Translation> &p_translation) { - translations.erase(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; -} - -void TranslationServer::clear() { - translations.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; - } - - return pseudolocalization_enabled ? pseudolocalize(res) : res; -} - -StringName TranslationServer::translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const { - if (!enabled) { - if (p_n == 1) { - return p_message; - } - 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; -} - -TranslationServer *TranslationServer::singleton = nullptr; - -bool TranslationServer::_load_translations(const String &p_from) { - if (ProjectSettings::get_singleton()->has_setting(p_from)) { - const Vector<String> &translation_names = GLOBAL_GET(p_from); - - int tcount = translation_names.size(); - - if (tcount) { - const String *r = translation_names.ptr(); - - for (int i = 0; i < tcount; i++) { - Ref<Translation> tr = ResourceLoader::load(r[i]); - if (tr.is_valid()) { - add_translation(tr); - } - } - } - return true; - } - - return false; -} - -void TranslationServer::setup() { - String test = GLOBAL_DEF("internationalization/locale/test", ""); - test = test.strip_edges(); - if (!test.is_empty()) { - set_locale(test); - } else { - set_locale(OS::get_singleton()->get_locale()); - } - - fallback = GLOBAL_DEF("internationalization/locale/fallback", "en"); - pseudolocalization_enabled = GLOBAL_DEF("internationalization/pseudolocalization/use_pseudolocalization", false); - pseudolocalization_accents_enabled = GLOBAL_DEF("internationalization/pseudolocalization/replace_with_accents", true); - pseudolocalization_double_vowels_enabled = GLOBAL_DEF("internationalization/pseudolocalization/double_vowels", false); - pseudolocalization_fake_bidi_enabled = GLOBAL_DEF("internationalization/pseudolocalization/fake_bidi", false); - pseudolocalization_override_enabled = GLOBAL_DEF("internationalization/pseudolocalization/override", false); - expansion_ratio = GLOBAL_DEF("internationalization/pseudolocalization/expansion_ratio", 0.0); - pseudolocalization_prefix = GLOBAL_DEF("internationalization/pseudolocalization/prefix", "["); - pseudolocalization_suffix = GLOBAL_DEF("internationalization/pseudolocalization/suffix", "]"); - pseudolocalization_skip_placeholders_enabled = GLOBAL_DEF("internationalization/pseudolocalization/skip_placeholders", true); - -#ifdef TOOLS_ENABLED - ProjectSettings::get_singleton()->set_custom_property_info(PropertyInfo(Variant::STRING, "internationalization/locale/fallback", PROPERTY_HINT_LOCALE_ID, "")); -#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 { - return "en"; - } - } 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. - } - } - } - return best_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; -} - -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; -} - -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; -} - -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; -} - -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; -} - -bool TranslationServer::is_pseudolocalization_enabled() const { - return pseudolocalization_enabled; -} - -void TranslationServer::set_pseudolocalization_enabled(bool p_enabled) { - pseudolocalization_enabled = p_enabled; - - ResourceLoader::reload_translation_remaps(); - - if (OS::get_singleton()->get_main_loop()) { - OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_TRANSLATION_CHANGED); - } -} - -void TranslationServer::reload_pseudolocalization() { - pseudolocalization_accents_enabled = GLOBAL_GET("internationalization/pseudolocalization/replace_with_accents"); - pseudolocalization_double_vowels_enabled = GLOBAL_GET("internationalization/pseudolocalization/double_vowels"); - pseudolocalization_fake_bidi_enabled = GLOBAL_GET("internationalization/pseudolocalization/fake_bidi"); - pseudolocalization_override_enabled = GLOBAL_GET("internationalization/pseudolocalization/override"); - expansion_ratio = GLOBAL_GET("internationalization/pseudolocalization/expansion_ratio"); - pseudolocalization_prefix = GLOBAL_GET("internationalization/pseudolocalization/prefix"); - pseudolocalization_suffix = GLOBAL_GET("internationalization/pseudolocalization/suffix"); - pseudolocalization_skip_placeholders_enabled = GLOBAL_GET("internationalization/pseudolocalization/skip_placeholders"); - - ResourceLoader::reload_translation_remaps(); - - if (OS::get_singleton()->get_main_loop()) { - OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_TRANSLATION_CHANGED); - } -} - -StringName TranslationServer::pseudolocalize(const StringName &p_message) const { - String message = p_message; - int length = message.length(); - if (pseudolocalization_override_enabled) { - message = get_override_string(message); - } - - if (pseudolocalization_double_vowels_enabled) { - message = double_vowels(message); - } - - if (pseudolocalization_accents_enabled) { - message = replace_with_accented_string(message); - } - - if (pseudolocalization_fake_bidi_enabled) { - message = wrap_with_fakebidi_characters(message); - } - - StringName res = add_padding(message, length); - return res; -} - -StringName TranslationServer::tool_pseudolocalize(const StringName &p_message) const { - String message = p_message; - message = double_vowels(message); - message = replace_with_accented_string(message); - StringName res = "[!!! " + message + " !!!]"; - return res; -} - -String TranslationServer::get_override_string(String &p_message) const { - String res; - for (int i = 0; i < p_message.length(); i++) { - if (pseudolocalization_skip_placeholders_enabled && is_placeholder(p_message, i)) { - res += p_message[i]; - res += p_message[i + 1]; - i++; - continue; - } - res += '*'; - } - return res; -} - -String TranslationServer::double_vowels(String &p_message) const { - String res; - for (int i = 0; i < p_message.length(); i++) { - if (pseudolocalization_skip_placeholders_enabled && is_placeholder(p_message, i)) { - res += p_message[i]; - res += p_message[i + 1]; - i++; - continue; - } - res += p_message[i]; - if (p_message[i] == 'a' || p_message[i] == 'e' || p_message[i] == 'i' || p_message[i] == 'o' || p_message[i] == 'u' || - p_message[i] == 'A' || p_message[i] == 'E' || p_message[i] == 'I' || p_message[i] == 'O' || p_message[i] == 'U') { - res += p_message[i]; - } - } - return res; -}; - -String TranslationServer::replace_with_accented_string(String &p_message) const { - String res; - for (int i = 0; i < p_message.length(); i++) { - if (pseudolocalization_skip_placeholders_enabled && is_placeholder(p_message, i)) { - res += p_message[i]; - res += p_message[i + 1]; - i++; - continue; - } - const char32_t *accented = get_accented_version(p_message[i]); - if (accented) { - res += accented; - } else { - res += p_message[i]; - } - } - return res; -} - -String TranslationServer::wrap_with_fakebidi_characters(String &p_message) const { - String res; - char32_t fakebidiprefix = U'\u202e'; - char32_t fakebidisuffix = U'\u202c'; - res += fakebidiprefix; - // The fake bidi unicode gets popped at every newline so pushing it back at every newline. - for (int i = 0; i < p_message.length(); i++) { - if (p_message[i] == '\n') { - res += fakebidisuffix; - res += p_message[i]; - res += fakebidiprefix; - } else if (pseudolocalization_skip_placeholders_enabled && is_placeholder(p_message, i)) { - res += fakebidisuffix; - res += p_message[i]; - res += p_message[i + 1]; - res += fakebidiprefix; - i++; - } else { - res += p_message[i]; - } - } - res += fakebidisuffix; - return res; -} - -String TranslationServer::add_padding(const String &p_message, int p_length) const { - String underscores = String("_").repeat(p_length * expansion_ratio / 2); - String prefix = pseudolocalization_prefix + underscores; - String suffix = underscores + pseudolocalization_suffix; - - return prefix + p_message + suffix; -} - -const char32_t *TranslationServer::get_accented_version(char32_t p_character) const { - if (!is_ascii_alphabet_char(p_character)) { - return nullptr; - } - - for (unsigned int i = 0; i < sizeof(_character_to_accented) / sizeof(_character_to_accented[0]); i++) { - if (_character_to_accented[i].character == p_character) { - return _character_to_accented[i].accented_character; - } - } - - return nullptr; -} - -bool TranslationServer::is_placeholder(String &p_message, int p_index) const { - return p_index < p_message.length() - 1 && p_message[p_index] == '%' && - (p_message[p_index + 1] == 's' || p_message[p_index + 1] == 'c' || p_message[p_index + 1] == 'd' || - p_message[p_index + 1] == 'o' || p_message[p_index + 1] == 'x' || p_message[p_index + 1] == 'X' || p_message[p_index + 1] == 'f'); -} - -#ifdef TOOLS_ENABLED -void TranslationServer::get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const { - const String pf = p_function; - if (p_idx == 0) { - HashMap<String, String> *target_hash_map = nullptr; - if (pf == "get_language_name") { - target_hash_map = &language_map; - } else if (pf == "get_script_name") { - target_hash_map = &script_map; - } else if (pf == "get_country_name") { - target_hash_map = &country_name_map; - } - - if (target_hash_map) { - for (const KeyValue<String, String> &E : *target_hash_map) { - r_options->push_back(E.key.quote()); - } - } - } - Object::get_argument_options(p_function, p_idx, r_options); -} -#endif // TOOLS_ENABLED - -void TranslationServer::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_locale", "locale"), &TranslationServer::set_locale); - ClassDB::bind_method(D_METHOD("get_locale"), &TranslationServer::get_locale); - ClassDB::bind_method(D_METHOD("get_tool_locale"), &TranslationServer::get_tool_locale); - - ClassDB::bind_method(D_METHOD("compare_locales", "locale_a", "locale_b"), &TranslationServer::compare_locales); - ClassDB::bind_method(D_METHOD("standardize_locale", "locale"), &TranslationServer::standardize_locale); - - ClassDB::bind_method(D_METHOD("get_all_languages"), &TranslationServer::get_all_languages); - ClassDB::bind_method(D_METHOD("get_language_name", "language"), &TranslationServer::get_language_name); - - ClassDB::bind_method(D_METHOD("get_all_scripts"), &TranslationServer::get_all_scripts); - ClassDB::bind_method(D_METHOD("get_script_name", "script"), &TranslationServer::get_script_name); - - ClassDB::bind_method(D_METHOD("get_all_countries"), &TranslationServer::get_all_countries); - ClassDB::bind_method(D_METHOD("get_country_name", "country"), &TranslationServer::get_country_name); - - ClassDB::bind_method(D_METHOD("get_locale_name", "locale"), &TranslationServer::get_locale_name); - - ClassDB::bind_method(D_METHOD("translate", "message", "context"), &TranslationServer::translate, DEFVAL(StringName())); - ClassDB::bind_method(D_METHOD("translate_plural", "message", "plural_message", "n", "context"), &TranslationServer::translate_plural, DEFVAL(StringName())); - - ClassDB::bind_method(D_METHOD("add_translation", "translation"), &TranslationServer::add_translation); - 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("clear"), &TranslationServer::clear); - - ClassDB::bind_method(D_METHOD("get_loaded_locales"), &TranslationServer::get_loaded_locales); - - ClassDB::bind_method(D_METHOD("is_pseudolocalization_enabled"), &TranslationServer::is_pseudolocalization_enabled); - ClassDB::bind_method(D_METHOD("set_pseudolocalization_enabled", "enabled"), &TranslationServer::set_pseudolocalization_enabled); - ClassDB::bind_method(D_METHOD("reload_pseudolocalization"), &TranslationServer::reload_pseudolocalization); - ClassDB::bind_method(D_METHOD("pseudolocalize", "message"), &TranslationServer::pseudolocalize); - ADD_PROPERTY(PropertyInfo(Variant::Type::BOOL, "pseudolocalization_enabled"), "set_pseudolocalization_enabled", "is_pseudolocalization_enabled"); -} - -void TranslationServer::load_translations() { - _load_translations("internationalization/locale/translations"); //all - _load_translations("internationalization/locale/translations_" + locale.substr(0, 2)); - - if (locale.substr(0, 2) != locale) { - _load_translations("internationalization/locale/translations_" + locale); - } -} - -TranslationServer::TranslationServer() { - singleton = this; - init_locale_info(); -} diff --git a/core/string/translation.h b/core/string/translation.h index 0a7eacc45f..4e8cffc90c 100644 --- a/core/string/translation.h +++ b/core/string/translation.h @@ -51,10 +51,6 @@ class Translation : public Resource { protected: static void _bind_methods(); -#ifndef DISABLE_DEPRECATED - static void _bind_compatibility_methods(); -#endif - GDVIRTUAL2RC(StringName, _get_message, StringName, StringName); GDVIRTUAL4RC(StringName, _get_plural_message, StringName, StringName, int, StringName); @@ -74,132 +70,4 @@ public: Translation() {} }; -class TranslationServer : public Object { - GDCLASS(TranslationServer, 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; - - bool enabled = true; - - bool pseudolocalization_enabled = false; - bool pseudolocalization_accents_enabled = false; - bool pseudolocalization_double_vowels_enabled = false; - bool pseudolocalization_fake_bidi_enabled = false; - bool pseudolocalization_override_enabled = false; - bool pseudolocalization_skip_placeholders_enabled = false; - float expansion_ratio = 0.0; - String pseudolocalization_prefix; - String pseudolocalization_suffix; - - StringName tool_pseudolocalize(const StringName &p_message) const; - String get_override_string(String &p_message) const; - String double_vowels(String &p_message) const; - String replace_with_accented_string(String &p_message) const; - String wrap_with_fakebidi_characters(String &p_message) const; - String add_padding(const String &p_message, int p_length) const; - const char32_t *get_accented_version(char32_t p_character) const; - bool is_placeholder(String &p_message, int p_index) const; - - static TranslationServer *singleton; - bool _load_translations(const String &p_from); - String _standardize_locale(const String &p_locale, bool p_add_defaults) const; - - 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(); - -#ifndef DISABLE_DEPRECATED - static void _bind_compatibility_methods(); -#endif - - struct LocaleScriptInfo { - String name; - String script; - String default_country; - HashSet<String> supported_countries; - }; - static Vector<LocaleScriptInfo> locale_script_info; - - static HashMap<String, String> language_map; - static HashMap<String, String> script_map; - static HashMap<String, String> locale_rename_map; - static HashMap<String, String> country_name_map; - static HashMap<String, String> country_rename_map; - static HashMap<String, String> variant_map; - - void init_locale_info(); - -public: - _FORCE_INLINE_ static TranslationServer *get_singleton() { return singleton; } - - void set_enabled(bool p_enabled) { enabled = p_enabled; } - _FORCE_INLINE_ bool is_enabled() const { return enabled; } - - void set_locale(const String &p_locale); - String get_locale() const; - Ref<Translation> get_translation_object(const String &p_locale); - - Vector<String> get_all_languages() const; - String get_language_name(const String &p_language) const; - - Vector<String> get_all_scripts() const; - String get_script_name(const String &p_script) const; - - Vector<String> get_all_countries() const; - String get_country_name(const String &p_country) const; - - String get_locale_name(const String &p_locale) const; - - PackedStringArray get_loaded_locales() const; - - void add_translation(const Ref<Translation> &p_translation); - void remove_translation(const Ref<Translation> &p_translation); - - 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; - - StringName pseudolocalize(const StringName &p_message) const; - - bool is_pseudolocalization_enabled() const; - void set_pseudolocalization_enabled(bool p_enabled); - void reload_pseudolocalization(); - - String standardize_locale(const String &p_locale) const; - - 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; - - void setup(); - - void clear(); - - void load_translations(); - -#ifdef TOOLS_ENABLED - virtual void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override; -#endif // TOOLS_ENABLED - - TranslationServer(); -}; - #endif // TRANSLATION_H diff --git a/core/string/translation_server.cpp b/core/string/translation_server.cpp new file mode 100644 index 0000000000..d4aa152340 --- /dev/null +++ b/core/string/translation_server.cpp @@ -0,0 +1,951 @@ +/**************************************************************************/ +/* translation_server.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_server.h" + +#include "core/config/project_settings.h" +#include "core/io/resource_loader.h" +#include "core/os/os.h" +#include "core/string/locales.h" + +#ifdef TOOLS_ENABLED +#include "main/main.h" +#endif + +struct _character_accent_pair { + const char32_t character; + const char32_t *accented_character; +}; + +static _character_accent_pair _character_to_accented[] = { + { 'A', U"Å" }, + { 'B', U"ß" }, + { 'C', U"Ç" }, + { 'D', U"Ð" }, + { 'E', U"É" }, + { 'F', U"F́" }, + { 'G', U"Ĝ" }, + { 'H', U"Ĥ" }, + { 'I', U"Ĩ" }, + { 'J', U"Ĵ" }, + { 'K', U"ĸ" }, + { 'L', U"Ł" }, + { 'M', U"Ḿ" }, + { 'N', U"й" }, + { 'O', U"Ö" }, + { 'P', U"Ṕ" }, + { 'Q', U"Q́" }, + { 'R', U"Ř" }, + { 'S', U"Ŝ" }, + { 'T', U"Ŧ" }, + { 'U', U"Ũ" }, + { 'V', U"Ṽ" }, + { 'W', U"Ŵ" }, + { 'X', U"X́" }, + { 'Y', U"Ÿ" }, + { 'Z', U"Ž" }, + { 'a', U"á" }, + { 'b', U"ḅ" }, + { 'c', U"ć" }, + { 'd', U"d́" }, + { 'e', U"é" }, + { 'f', U"f́" }, + { 'g', U"ǵ" }, + { 'h', U"h̀" }, + { 'i', U"í" }, + { 'j', U"ǰ" }, + { 'k', U"ḱ" }, + { 'l', U"ł" }, + { 'm', U"m̀" }, + { 'n', U"ή" }, + { 'o', U"ô" }, + { 'p', U"ṕ" }, + { 'q', U"q́" }, + { 'r', U"ŕ" }, + { 's', U"š" }, + { 't', U"ŧ" }, + { 'u', U"ü" }, + { 'v', U"ṽ" }, + { 'w', U"ŵ" }, + { 'x', U"x́" }, + { 'y', U"ý" }, + { 'z', U"ź" }, +}; + +Vector<TranslationServer::LocaleScriptInfo> TranslationServer::locale_script_info; + +HashMap<String, String> TranslationServer::language_map; +HashMap<String, String> TranslationServer::script_map; +HashMap<String, String> TranslationServer::locale_rename_map; +HashMap<String, String> TranslationServer::country_name_map; +HashMap<String, String> TranslationServer::variant_map; +HashMap<String, String> TranslationServer::country_rename_map; + +void TranslationServer::init_locale_info() { + // Init locale info. + language_map.clear(); + int idx = 0; + while (language_list[idx][0] != nullptr) { + language_map[language_list[idx][0]] = String::utf8(language_list[idx][1]); + idx++; + } + + // Init locale-script map. + locale_script_info.clear(); + idx = 0; + while (locale_scripts[idx][0] != nullptr) { + LocaleScriptInfo info; + info.name = locale_scripts[idx][0]; + info.script = locale_scripts[idx][1]; + info.default_country = locale_scripts[idx][2]; + Vector<String> supported_countries = String(locale_scripts[idx][3]).split(",", false); + for (int i = 0; i < supported_countries.size(); i++) { + info.supported_countries.insert(supported_countries[i]); + } + locale_script_info.push_back(info); + idx++; + } + + // Init supported script list. + script_map.clear(); + idx = 0; + while (script_list[idx][0] != nullptr) { + script_map[script_list[idx][1]] = String::utf8(script_list[idx][0]); + idx++; + } + + // Init regional variant map. + variant_map.clear(); + idx = 0; + while (locale_variants[idx][0] != nullptr) { + variant_map[locale_variants[idx][0]] = locale_variants[idx][1]; + idx++; + } + + // Init locale renames. + locale_rename_map.clear(); + idx = 0; + while (locale_renames[idx][0] != nullptr) { + if (!String(locale_renames[idx][1]).is_empty()) { + locale_rename_map[locale_renames[idx][0]] = locale_renames[idx][1]; + } + idx++; + } + + // Init country names. + country_name_map.clear(); + idx = 0; + while (country_names[idx][0] != nullptr) { + country_name_map[String(country_names[idx][0])] = String::utf8(country_names[idx][1]); + idx++; + } + + // Init country renames. + country_rename_map.clear(); + idx = 0; + while (country_renames[idx][0] != nullptr) { + if (!String(country_renames[idx][1]).is_empty()) { + country_rename_map[country_renames[idx][0]] = country_renames[idx][1]; + } + idx++; + } +} + +String TranslationServer::standardize_locale(const String &p_locale) const { + return _standardize_locale(p_locale, false); +} + +String TranslationServer::_standardize_locale(const String &p_locale, bool p_add_defaults) const { + // Replaces '-' with '_' for macOS style locales. + String univ_locale = p_locale.replace("-", "_"); + + // Extract locale elements. + String lang_name, script_name, country_name, variant_name; + Vector<String> locale_elements = univ_locale.get_slice("@", 0).split("_"); + lang_name = locale_elements[0]; + if (locale_elements.size() >= 2) { + if (locale_elements[1].length() == 4 && is_ascii_upper_case(locale_elements[1][0]) && is_ascii_lower_case(locale_elements[1][1]) && is_ascii_lower_case(locale_elements[1][2]) && is_ascii_lower_case(locale_elements[1][3])) { + script_name = locale_elements[1]; + } + if (locale_elements[1].length() == 2 && is_ascii_upper_case(locale_elements[1][0]) && is_ascii_upper_case(locale_elements[1][1])) { + country_name = locale_elements[1]; + } + } + if (locale_elements.size() >= 3) { + if (locale_elements[2].length() == 2 && is_ascii_upper_case(locale_elements[2][0]) && is_ascii_upper_case(locale_elements[2][1])) { + country_name = locale_elements[2]; + } else if (variant_map.has(locale_elements[2].to_lower()) && variant_map[locale_elements[2].to_lower()] == lang_name) { + variant_name = locale_elements[2].to_lower(); + } + } + if (locale_elements.size() >= 4) { + if (variant_map.has(locale_elements[3].to_lower()) && variant_map[locale_elements[3].to_lower()] == lang_name) { + variant_name = locale_elements[3].to_lower(); + } + } + + // Try extract script and variant from the extra part. + Vector<String> script_extra = univ_locale.get_slice("@", 1).split(";"); + for (int i = 0; i < script_extra.size(); i++) { + if (script_extra[i].to_lower() == "cyrillic") { + script_name = "Cyrl"; + break; + } else if (script_extra[i].to_lower() == "latin") { + script_name = "Latn"; + break; + } else if (script_extra[i].to_lower() == "devanagari") { + script_name = "Deva"; + break; + } else if (variant_map.has(script_extra[i].to_lower()) && variant_map[script_extra[i].to_lower()] == lang_name) { + variant_name = script_extra[i].to_lower(); + } + } + + // Handles known non-ISO language names used e.g. on Windows. + if (locale_rename_map.has(lang_name)) { + lang_name = locale_rename_map[lang_name]; + } + + // Handle country renames. + if (country_rename_map.has(country_name)) { + country_name = country_rename_map[country_name]; + } + + // Remove unsupported script codes. + if (!script_map.has(script_name)) { + script_name = ""; + } + + // Add script code base on language and country codes for some ambiguous cases. + if (p_add_defaults) { + if (script_name.is_empty()) { + for (int i = 0; i < locale_script_info.size(); i++) { + const LocaleScriptInfo &info = locale_script_info[i]; + if (info.name == lang_name) { + if (country_name.is_empty() || info.supported_countries.has(country_name)) { + script_name = info.script; + break; + } + } + } + } + if (!script_name.is_empty() && country_name.is_empty()) { + // Add conntry code based on script for some ambiguous cases. + for (int i = 0; i < locale_script_info.size(); i++) { + const LocaleScriptInfo &info = locale_script_info[i]; + if (info.name == lang_name && info.script == script_name) { + country_name = info.default_country; + break; + } + } + } + } + + // Combine results. + String out = lang_name; + if (!script_name.is_empty()) { + out = out + "_" + script_name; + } + if (!country_name.is_empty()) { + out = out + "_" + country_name; + } + if (!variant_name.is_empty()) { + out = out + "_" + variant_name; + } + return out; +} + +int TranslationServer::compare_locales(const String &p_locale_a, const String &p_locale_b) const { + if (p_locale_a == p_locale_b) { + // Exact match. + return 10; + } + + String locale_a = _standardize_locale(p_locale_a, true); + String locale_b = _standardize_locale(p_locale_b, true); + + if (locale_a == locale_b) { + // Exact match. + return 10; + } + + Vector<String> locale_a_elements = locale_a.split("_"); + Vector<String> locale_b_elements = locale_b.split("_"); + if (locale_a_elements[0] == locale_b_elements[0]) { + // Matching language, both locales have extra parts. + // Return number of matching elements. + int matching_elements = 1; + for (int i = 1; i < locale_a_elements.size(); i++) { + for (int j = 1; j < locale_b_elements.size(); j++) { + if (locale_a_elements[i] == locale_b_elements[j]) { + matching_elements++; + } + } + } + return matching_elements; + } else { + // No match. + return 0; + } +} + +String TranslationServer::get_locale_name(const String &p_locale) const { + String lang_name, script_name, country_name; + Vector<String> locale_elements = standardize_locale(p_locale).split("_"); + lang_name = locale_elements[0]; + if (locale_elements.size() >= 2) { + if (locale_elements[1].length() == 4 && is_ascii_upper_case(locale_elements[1][0]) && is_ascii_lower_case(locale_elements[1][1]) && is_ascii_lower_case(locale_elements[1][2]) && is_ascii_lower_case(locale_elements[1][3])) { + script_name = locale_elements[1]; + } + if (locale_elements[1].length() == 2 && is_ascii_upper_case(locale_elements[1][0]) && is_ascii_upper_case(locale_elements[1][1])) { + country_name = locale_elements[1]; + } + } + if (locale_elements.size() >= 3) { + if (locale_elements[2].length() == 2 && is_ascii_upper_case(locale_elements[2][0]) && is_ascii_upper_case(locale_elements[2][1])) { + country_name = locale_elements[2]; + } + } + + String name = language_map[lang_name]; + if (!script_name.is_empty()) { + name = name + " (" + script_map[script_name] + ")"; + } + if (!country_name.is_empty()) { + name = name + ", " + country_name_map[country_name]; + } + return name; +} + +Vector<String> TranslationServer::get_all_languages() const { + Vector<String> languages; + + for (const KeyValue<String, String> &E : language_map) { + languages.push_back(E.key); + } + + return languages; +} + +String TranslationServer::get_language_name(const String &p_language) const { + return language_map[p_language]; +} + +Vector<String> TranslationServer::get_all_scripts() const { + Vector<String> scripts; + + for (const KeyValue<String, String> &E : script_map) { + scripts.push_back(E.key); + } + + return scripts; +} + +String TranslationServer::get_script_name(const String &p_script) const { + return script_map[p_script]; +} + +Vector<String> TranslationServer::get_all_countries() const { + Vector<String> countries; + + for (const KeyValue<String, String> &E : country_name_map) { + countries.push_back(E.key); + } + + return countries; +} + +String TranslationServer::get_country_name(const String &p_country) const { + return country_name_map[p_country]; +} + +void TranslationServer::set_locale(const String &p_locale) { + String new_locale = standardize_locale(p_locale); + if (locale == new_locale) { + return; + } + + locale = new_locale; + ResourceLoader::reload_translation_remaps(); + + if (OS::get_singleton()->get_main_loop()) { + OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_TRANSLATION_CHANGED); + } +} + +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); + } + + return locales; +} + +void TranslationServer::add_translation(const Ref<Translation> &p_translation) { + translations.insert(p_translation); +} + +void TranslationServer::remove_translation(const Ref<Translation> &p_translation) { + translations.erase(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; +} + +void TranslationServer::clear() { + translations.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; + } + + return pseudolocalization_enabled ? pseudolocalize(res) : res; +} + +StringName TranslationServer::translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const { + if (!enabled) { + if (p_n == 1) { + return p_message; + } + 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; +} + +TranslationServer *TranslationServer::singleton = nullptr; + +bool TranslationServer::_load_translations(const String &p_from) { + if (ProjectSettings::get_singleton()->has_setting(p_from)) { + const Vector<String> &translation_names = GLOBAL_GET(p_from); + + int tcount = translation_names.size(); + + if (tcount) { + const String *r = translation_names.ptr(); + + for (int i = 0; i < tcount; i++) { + Ref<Translation> tr = ResourceLoader::load(r[i]); + if (tr.is_valid()) { + add_translation(tr); + } + } + } + return true; + } + + return false; +} + +void TranslationServer::setup() { + String test = GLOBAL_DEF("internationalization/locale/test", ""); + test = test.strip_edges(); + if (!test.is_empty()) { + set_locale(test); + } else { + set_locale(OS::get_singleton()->get_locale()); + } + + fallback = GLOBAL_DEF("internationalization/locale/fallback", "en"); + pseudolocalization_enabled = GLOBAL_DEF("internationalization/pseudolocalization/use_pseudolocalization", false); + pseudolocalization_accents_enabled = GLOBAL_DEF("internationalization/pseudolocalization/replace_with_accents", true); + pseudolocalization_double_vowels_enabled = GLOBAL_DEF("internationalization/pseudolocalization/double_vowels", false); + pseudolocalization_fake_bidi_enabled = GLOBAL_DEF("internationalization/pseudolocalization/fake_bidi", false); + pseudolocalization_override_enabled = GLOBAL_DEF("internationalization/pseudolocalization/override", false); + expansion_ratio = GLOBAL_DEF("internationalization/pseudolocalization/expansion_ratio", 0.0); + pseudolocalization_prefix = GLOBAL_DEF("internationalization/pseudolocalization/prefix", "["); + pseudolocalization_suffix = GLOBAL_DEF("internationalization/pseudolocalization/suffix", "]"); + pseudolocalization_skip_placeholders_enabled = GLOBAL_DEF("internationalization/pseudolocalization/skip_placeholders", true); + +#ifdef TOOLS_ENABLED + ProjectSettings::get_singleton()->set_custom_property_info(PropertyInfo(Variant::STRING, "internationalization/locale/fallback", PROPERTY_HINT_LOCALE_ID, "")); +#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 { + return "en"; + } + } 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. + } + } + } + return best_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; +} + +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; +} + +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; +} + +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; +} + +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; +} + +bool TranslationServer::is_pseudolocalization_enabled() const { + return pseudolocalization_enabled; +} + +void TranslationServer::set_pseudolocalization_enabled(bool p_enabled) { + pseudolocalization_enabled = p_enabled; + + ResourceLoader::reload_translation_remaps(); + + if (OS::get_singleton()->get_main_loop()) { + OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_TRANSLATION_CHANGED); + } +} + +void TranslationServer::reload_pseudolocalization() { + pseudolocalization_accents_enabled = GLOBAL_GET("internationalization/pseudolocalization/replace_with_accents"); + pseudolocalization_double_vowels_enabled = GLOBAL_GET("internationalization/pseudolocalization/double_vowels"); + pseudolocalization_fake_bidi_enabled = GLOBAL_GET("internationalization/pseudolocalization/fake_bidi"); + pseudolocalization_override_enabled = GLOBAL_GET("internationalization/pseudolocalization/override"); + expansion_ratio = GLOBAL_GET("internationalization/pseudolocalization/expansion_ratio"); + pseudolocalization_prefix = GLOBAL_GET("internationalization/pseudolocalization/prefix"); + pseudolocalization_suffix = GLOBAL_GET("internationalization/pseudolocalization/suffix"); + pseudolocalization_skip_placeholders_enabled = GLOBAL_GET("internationalization/pseudolocalization/skip_placeholders"); + + ResourceLoader::reload_translation_remaps(); + + if (OS::get_singleton()->get_main_loop()) { + OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_TRANSLATION_CHANGED); + } +} + +StringName TranslationServer::pseudolocalize(const StringName &p_message) const { + String message = p_message; + int length = message.length(); + if (pseudolocalization_override_enabled) { + message = get_override_string(message); + } + + if (pseudolocalization_double_vowels_enabled) { + message = double_vowels(message); + } + + if (pseudolocalization_accents_enabled) { + message = replace_with_accented_string(message); + } + + if (pseudolocalization_fake_bidi_enabled) { + message = wrap_with_fakebidi_characters(message); + } + + StringName res = add_padding(message, length); + return res; +} + +StringName TranslationServer::tool_pseudolocalize(const StringName &p_message) const { + String message = p_message; + message = double_vowels(message); + message = replace_with_accented_string(message); + StringName res = "[!!! " + message + " !!!]"; + return res; +} + +String TranslationServer::get_override_string(String &p_message) const { + String res; + for (int i = 0; i < p_message.length(); i++) { + if (pseudolocalization_skip_placeholders_enabled && is_placeholder(p_message, i)) { + res += p_message[i]; + res += p_message[i + 1]; + i++; + continue; + } + res += '*'; + } + return res; +} + +String TranslationServer::double_vowels(String &p_message) const { + String res; + for (int i = 0; i < p_message.length(); i++) { + if (pseudolocalization_skip_placeholders_enabled && is_placeholder(p_message, i)) { + res += p_message[i]; + res += p_message[i + 1]; + i++; + continue; + } + res += p_message[i]; + if (p_message[i] == 'a' || p_message[i] == 'e' || p_message[i] == 'i' || p_message[i] == 'o' || p_message[i] == 'u' || + p_message[i] == 'A' || p_message[i] == 'E' || p_message[i] == 'I' || p_message[i] == 'O' || p_message[i] == 'U') { + res += p_message[i]; + } + } + return res; +}; + +String TranslationServer::replace_with_accented_string(String &p_message) const { + String res; + for (int i = 0; i < p_message.length(); i++) { + if (pseudolocalization_skip_placeholders_enabled && is_placeholder(p_message, i)) { + res += p_message[i]; + res += p_message[i + 1]; + i++; + continue; + } + const char32_t *accented = get_accented_version(p_message[i]); + if (accented) { + res += accented; + } else { + res += p_message[i]; + } + } + return res; +} + +String TranslationServer::wrap_with_fakebidi_characters(String &p_message) const { + String res; + char32_t fakebidiprefix = U'\u202e'; + char32_t fakebidisuffix = U'\u202c'; + res += fakebidiprefix; + // The fake bidi unicode gets popped at every newline so pushing it back at every newline. + for (int i = 0; i < p_message.length(); i++) { + if (p_message[i] == '\n') { + res += fakebidisuffix; + res += p_message[i]; + res += fakebidiprefix; + } else if (pseudolocalization_skip_placeholders_enabled && is_placeholder(p_message, i)) { + res += fakebidisuffix; + res += p_message[i]; + res += p_message[i + 1]; + res += fakebidiprefix; + i++; + } else { + res += p_message[i]; + } + } + res += fakebidisuffix; + return res; +} + +String TranslationServer::add_padding(const String &p_message, int p_length) const { + String underscores = String("_").repeat(p_length * expansion_ratio / 2); + String prefix = pseudolocalization_prefix + underscores; + String suffix = underscores + pseudolocalization_suffix; + + return prefix + p_message + suffix; +} + +const char32_t *TranslationServer::get_accented_version(char32_t p_character) const { + if (!is_ascii_alphabet_char(p_character)) { + return nullptr; + } + + for (unsigned int i = 0; i < sizeof(_character_to_accented) / sizeof(_character_to_accented[0]); i++) { + if (_character_to_accented[i].character == p_character) { + return _character_to_accented[i].accented_character; + } + } + + return nullptr; +} + +bool TranslationServer::is_placeholder(String &p_message, int p_index) const { + return p_index < p_message.length() - 1 && p_message[p_index] == '%' && + (p_message[p_index + 1] == 's' || p_message[p_index + 1] == 'c' || p_message[p_index + 1] == 'd' || + p_message[p_index + 1] == 'o' || p_message[p_index + 1] == 'x' || p_message[p_index + 1] == 'X' || p_message[p_index + 1] == 'f'); +} + +#ifdef TOOLS_ENABLED +void TranslationServer::get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const { + const String pf = p_function; + if (p_idx == 0) { + HashMap<String, String> *target_hash_map = nullptr; + if (pf == "get_language_name") { + target_hash_map = &language_map; + } else if (pf == "get_script_name") { + target_hash_map = &script_map; + } else if (pf == "get_country_name") { + target_hash_map = &country_name_map; + } + + if (target_hash_map) { + for (const KeyValue<String, String> &E : *target_hash_map) { + r_options->push_back(E.key.quote()); + } + } + } + Object::get_argument_options(p_function, p_idx, r_options); +} +#endif // TOOLS_ENABLED + +void TranslationServer::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_locale", "locale"), &TranslationServer::set_locale); + ClassDB::bind_method(D_METHOD("get_locale"), &TranslationServer::get_locale); + ClassDB::bind_method(D_METHOD("get_tool_locale"), &TranslationServer::get_tool_locale); + + ClassDB::bind_method(D_METHOD("compare_locales", "locale_a", "locale_b"), &TranslationServer::compare_locales); + ClassDB::bind_method(D_METHOD("standardize_locale", "locale"), &TranslationServer::standardize_locale); + + ClassDB::bind_method(D_METHOD("get_all_languages"), &TranslationServer::get_all_languages); + ClassDB::bind_method(D_METHOD("get_language_name", "language"), &TranslationServer::get_language_name); + + ClassDB::bind_method(D_METHOD("get_all_scripts"), &TranslationServer::get_all_scripts); + ClassDB::bind_method(D_METHOD("get_script_name", "script"), &TranslationServer::get_script_name); + + ClassDB::bind_method(D_METHOD("get_all_countries"), &TranslationServer::get_all_countries); + ClassDB::bind_method(D_METHOD("get_country_name", "country"), &TranslationServer::get_country_name); + + ClassDB::bind_method(D_METHOD("get_locale_name", "locale"), &TranslationServer::get_locale_name); + + ClassDB::bind_method(D_METHOD("translate", "message", "context"), &TranslationServer::translate, DEFVAL(StringName())); + ClassDB::bind_method(D_METHOD("translate_plural", "message", "plural_message", "n", "context"), &TranslationServer::translate_plural, DEFVAL(StringName())); + + ClassDB::bind_method(D_METHOD("add_translation", "translation"), &TranslationServer::add_translation); + 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("clear"), &TranslationServer::clear); + + ClassDB::bind_method(D_METHOD("get_loaded_locales"), &TranslationServer::get_loaded_locales); + + ClassDB::bind_method(D_METHOD("is_pseudolocalization_enabled"), &TranslationServer::is_pseudolocalization_enabled); + ClassDB::bind_method(D_METHOD("set_pseudolocalization_enabled", "enabled"), &TranslationServer::set_pseudolocalization_enabled); + ClassDB::bind_method(D_METHOD("reload_pseudolocalization"), &TranslationServer::reload_pseudolocalization); + ClassDB::bind_method(D_METHOD("pseudolocalize", "message"), &TranslationServer::pseudolocalize); + ADD_PROPERTY(PropertyInfo(Variant::Type::BOOL, "pseudolocalization_enabled"), "set_pseudolocalization_enabled", "is_pseudolocalization_enabled"); +} + +void TranslationServer::load_translations() { + _load_translations("internationalization/locale/translations"); //all + _load_translations("internationalization/locale/translations_" + locale.substr(0, 2)); + + if (locale.substr(0, 2) != locale) { + _load_translations("internationalization/locale/translations_" + locale); + } +} + +TranslationServer::TranslationServer() { + singleton = this; + init_locale_info(); +} diff --git a/core/string/translation_server.h b/core/string/translation_server.h new file mode 100644 index 0000000000..bb285ab19c --- /dev/null +++ b/core/string/translation_server.h @@ -0,0 +1,160 @@ +/**************************************************************************/ +/* translation_server.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_SERVER_H +#define TRANSLATION_SERVER_H + +#include "core/string/translation.h" + +class TranslationServer : public Object { + GDCLASS(TranslationServer, 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; + + bool enabled = true; + + bool pseudolocalization_enabled = false; + bool pseudolocalization_accents_enabled = false; + bool pseudolocalization_double_vowels_enabled = false; + bool pseudolocalization_fake_bidi_enabled = false; + bool pseudolocalization_override_enabled = false; + bool pseudolocalization_skip_placeholders_enabled = false; + float expansion_ratio = 0.0; + String pseudolocalization_prefix; + String pseudolocalization_suffix; + + StringName tool_pseudolocalize(const StringName &p_message) const; + String get_override_string(String &p_message) const; + String double_vowels(String &p_message) const; + String replace_with_accented_string(String &p_message) const; + String wrap_with_fakebidi_characters(String &p_message) const; + String add_padding(const String &p_message, int p_length) const; + const char32_t *get_accented_version(char32_t p_character) const; + bool is_placeholder(String &p_message, int p_index) const; + + static TranslationServer *singleton; + bool _load_translations(const String &p_from); + String _standardize_locale(const String &p_locale, bool p_add_defaults) const; + + 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 { + String name; + String script; + String default_country; + HashSet<String> supported_countries; + }; + static Vector<LocaleScriptInfo> locale_script_info; + + static HashMap<String, String> language_map; + static HashMap<String, String> script_map; + static HashMap<String, String> locale_rename_map; + static HashMap<String, String> country_name_map; + static HashMap<String, String> country_rename_map; + static HashMap<String, String> variant_map; + + void init_locale_info(); + +public: + _FORCE_INLINE_ static TranslationServer *get_singleton() { return singleton; } + + void set_enabled(bool p_enabled) { enabled = p_enabled; } + _FORCE_INLINE_ bool is_enabled() const { return enabled; } + + void set_locale(const String &p_locale); + String get_locale() const; + Ref<Translation> get_translation_object(const String &p_locale); + + Vector<String> get_all_languages() const; + String get_language_name(const String &p_language) const; + + Vector<String> get_all_scripts() const; + String get_script_name(const String &p_script) const; + + Vector<String> get_all_countries() const; + String get_country_name(const String &p_country) const; + + String get_locale_name(const String &p_locale) const; + + PackedStringArray get_loaded_locales() const; + + void add_translation(const Ref<Translation> &p_translation); + void remove_translation(const Ref<Translation> &p_translation); + + 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; + + StringName pseudolocalize(const StringName &p_message) const; + + bool is_pseudolocalization_enabled() const; + void set_pseudolocalization_enabled(bool p_enabled); + void reload_pseudolocalization(); + + String standardize_locale(const String &p_locale) const; + + 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; + + void setup(); + + void clear(); + + void load_translations(); + +#ifdef TOOLS_ENABLED + virtual void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override; +#endif // TOOLS_ENABLED + + TranslationServer(); +}; + +#endif // TRANSLATION_SERVER_H diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp index 3d37e17ef8..2683addd4b 100644 --- a/core/string/ustring.cpp +++ b/core/string/ustring.cpp @@ -36,7 +36,7 @@ #include "core/os/memory.h" #include "core/string/print_string.h" #include "core/string/string_name.h" -#include "core/string/translation.h" +#include "core/string/translation_server.h" #include "core/string/ucaps.h" #include "core/variant/variant.h" #include "core/version_generated.gen.h" @@ -1537,13 +1537,16 @@ Vector<double> String::split_floats(const String &p_splitter, bool p_allow_empty int from = 0; int len = length(); + String buffer = *this; while (true) { int end = find(p_splitter, from); if (end < 0) { end = len; } if (p_allow_empty || (end > from)) { - ret.push_back(String::to_float(&get_data()[from])); + buffer[end] = 0; + ret.push_back(String::to_float(&buffer.get_data()[from])); + buffer[end] = _cowdata.get(end); } if (end == len) { @@ -1561,6 +1564,7 @@ Vector<float> String::split_floats_mk(const Vector<String> &p_splitters, bool p_ int from = 0; int len = length(); + String buffer = *this; while (true) { int idx; int end = findmk(p_splitters, from, &idx); @@ -1572,7 +1576,9 @@ Vector<float> String::split_floats_mk(const Vector<String> &p_splitters, bool p_ } if (p_allow_empty || (end > from)) { - ret.push_back(String::to_float(&get_data()[from])); + buffer[end] = 0; + ret.push_back(String::to_float(&buffer.get_data()[from])); + buffer[end] = _cowdata.get(end); } if (end == len) { @@ -1639,13 +1645,43 @@ Vector<int> String::split_ints_mk(const Vector<String> &p_splitters, bool p_allo } String String::join(const Vector<String> &parts) const { + if (parts.is_empty()) { + return String(); + } else if (parts.size() == 1) { + return parts[0]; + } + + const int this_length = length(); + + int new_size = (parts.size() - 1) * this_length; + for (const String &part : parts) { + new_size += part.length(); + } + new_size += 1; + String ret; - for (int i = 0; i < parts.size(); ++i) { - if (i > 0) { - ret += *this; + ret.resize(new_size); + char32_t *ret_ptrw = ret.ptrw(); + const char32_t *this_ptr = ptr(); + + bool first = true; + for (const String &part : parts) { + if (first) { + first = false; + } else if (this_length) { + memcpy(ret_ptrw, this_ptr, this_length * sizeof(char32_t)); + ret_ptrw += this_length; + } + + const int part_length = part.length(); + if (part_length) { + memcpy(ret_ptrw, part.ptr(), part_length * sizeof(char32_t)); + ret_ptrw += part_length; } - ret += parts[i]; } + + *ret_ptrw = 0; + return ret; } @@ -1658,30 +1694,40 @@ char32_t String::char_lowercase(char32_t p_char) { } String String::to_upper() const { - String upper = *this; + if (is_empty()) { + return *this; + } - for (int i = 0; i < upper.size(); i++) { - const char32_t s = upper[i]; - const char32_t t = _find_upper(s); - if (s != t) { // avoid copy on write - upper[i] = t; - } + String upper; + upper.resize(size()); + const char32_t *old_ptr = ptr(); + char32_t *upper_ptrw = upper.ptrw(); + + while (*old_ptr) { + *upper_ptrw++ = _find_upper(*old_ptr++); } + *upper_ptrw = 0; + return upper; } String String::to_lower() const { - String lower = *this; + if (is_empty()) { + return *this; + } - for (int i = 0; i < lower.size(); i++) { - const char32_t s = lower[i]; - const char32_t t = _find_lower(s); - if (s != t) { // avoid copy on write - lower[i] = t; - } + String lower; + lower.resize(size()); + const char32_t *old_ptr = ptr(); + char32_t *lower_ptrw = lower.ptrw(); + + while (*old_ptr) { + *lower_ptrw++ = _find_lower(*old_ptr++); } + *lower_ptrw = 0; + return lower; } @@ -1919,15 +1965,16 @@ String String::hex_encode_buffer(const uint8_t *p_buffer, int p_len) { static const char hex[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; String ret; - char v[2] = { 0, 0 }; + ret.resize(p_len * 2 + 1); + char32_t *ret_ptrw = ret.ptrw(); for (int i = 0; i < p_len; i++) { - v[0] = hex[p_buffer[i] >> 4]; - ret += v; - v[0] = hex[p_buffer[i] & 0xF]; - ret += v; + *ret_ptrw++ = hex[p_buffer[i] >> 4]; + *ret_ptrw++ = hex[p_buffer[i] & 0xF]; } + *ret_ptrw = 0; + return ret; } @@ -1950,11 +1997,12 @@ Vector<uint8_t> String::hex_decode() const { Vector<uint8_t> out; int len = length() / 2; out.resize(len); + uint8_t *out_ptrw = out.ptrw(); for (int i = 0; i < len; i++) { char32_t c; HEX_TO_BYTE(first, i * 2); HEX_TO_BYTE(second, i * 2 + 1); - out.write[i] = first * 16 + second; + out_ptrw[i] = first * 16 + second; } return out; #undef HEX_TO_BYTE @@ -1975,14 +2023,16 @@ CharString String::ascii(bool p_allow_extended) const { CharString cs; cs.resize(size()); + char *cs_ptrw = cs.ptrw(); + const char32_t *this_ptr = ptr(); for (int i = 0; i < size(); i++) { - char32_t c = operator[](i); + char32_t c = this_ptr[i]; if ((c <= 0x7f) || (c <= 0xff && p_allow_extended)) { - cs[i] = c; + cs_ptrw[i] = c; } else { print_unicode_error(vformat("Invalid unicode codepoint (%x), cannot represent as ASCII/Latin-1", (uint32_t)c)); - cs[i] = 0x20; // ascii doesn't have a replacement character like unicode, 0x1a is sometimes used but is kinda arcane + cs_ptrw[i] = 0x20; // ASCII doesn't have a replacement character like unicode, 0x1a is sometimes used but is kinda arcane. } } @@ -3115,8 +3165,9 @@ Vector<uint8_t> String::md5_buffer() const { Vector<uint8_t> ret; ret.resize(16); + uint8_t *ret_ptrw = ret.ptrw(); for (int i = 0; i < 16; i++) { - ret.write[i] = hash[i]; + ret_ptrw[i] = hash[i]; } return ret; } @@ -3128,8 +3179,9 @@ Vector<uint8_t> String::sha1_buffer() const { Vector<uint8_t> ret; ret.resize(20); + uint8_t *ret_ptrw = ret.ptrw(); for (int i = 0; i < 20; i++) { - ret.write[i] = hash[i]; + ret_ptrw[i] = hash[i]; } return ret; @@ -3142,14 +3194,15 @@ Vector<uint8_t> String::sha256_buffer() const { Vector<uint8_t> ret; ret.resize(32); + uint8_t *ret_ptrw = ret.ptrw(); for (int i = 0; i < 32; i++) { - ret.write[i] = hash[i]; + ret_ptrw[i] = hash[i]; } return ret; } String String::insert(int p_at_pos, const String &p_string) const { - if (p_at_pos < 0) { + if (p_string.is_empty() || p_at_pos < 0) { return *this; } @@ -3157,17 +3210,27 @@ String String::insert(int p_at_pos, const String &p_string) const { p_at_pos = length(); } - String pre; + String ret; + ret.resize(length() + p_string.length() + 1); + char32_t *ret_ptrw = ret.ptrw(); + const char32_t *this_ptr = ptr(); + if (p_at_pos > 0) { - pre = substr(0, p_at_pos); + memcpy(ret_ptrw, this_ptr, p_at_pos * sizeof(char32_t)); + ret_ptrw += p_at_pos; } - String post; + memcpy(ret_ptrw, p_string.ptr(), p_string.length() * sizeof(char32_t)); + ret_ptrw += p_string.length(); + if (p_at_pos < length()) { - post = substr(p_at_pos, length() - p_at_pos); + memcpy(ret_ptrw, this_ptr + p_at_pos, (length() - p_at_pos) * sizeof(char32_t)); + ret_ptrw += length() - p_at_pos; } - return pre + p_string + post; + *ret_ptrw = 0; + + return ret; } String String::erase(int p_pos, int p_chars) const { @@ -3871,8 +3934,9 @@ Vector<String> String::bigrams() const { return b; } b.resize(n_pairs); + String *b_ptrw = b.ptrw(); for (int i = 0; i < n_pairs; i++) { - b.write[i] = substr(i, 2); + b_ptrw[i] = substr(i, 2); } return b; } @@ -3986,54 +4050,161 @@ String String::format(const Variant &values, const String &placeholder) const { return new_string; } -String String::replace(const String &p_key, const String &p_with) const { - String new_string; +static String _replace_common(const String &p_this, const String &p_key, const String &p_with, bool p_case_insensitive) { + if (p_key.is_empty() || p_this.is_empty()) { + return p_this; + } + + const int key_length = p_key.length(); + int search_from = 0; int result = 0; - while ((result = find(p_key, search_from)) >= 0) { - new_string += substr(search_from, result - search_from); - new_string += p_with; - search_from = result + p_key.length(); + LocalVector<int> found; + + while ((result = (p_case_insensitive ? p_this.findn(p_key, search_from) : p_this.find(p_key, search_from))) >= 0) { + found.push_back(result); + search_from = result + key_length; } - if (search_from == 0) { - return *this; + if (found.is_empty()) { + return p_this; + } + + String new_string; + + const int with_length = p_with.length(); + const int old_length = p_this.length(); + + new_string.resize(old_length + found.size() * (with_length - key_length) + 1); + + char32_t *new_ptrw = new_string.ptrw(); + const char32_t *old_ptr = p_this.ptr(); + const char32_t *with_ptr = p_with.ptr(); + + int last_pos = 0; + + for (const int &pos : found) { + if (last_pos != pos) { + memcpy(new_ptrw, old_ptr + last_pos, (pos - last_pos) * sizeof(char32_t)); + new_ptrw += (pos - last_pos); + } + if (with_length) { + memcpy(new_ptrw, with_ptr, with_length * sizeof(char32_t)); + new_ptrw += with_length; + } + last_pos = pos + key_length; + } + + if (last_pos != old_length) { + memcpy(new_ptrw, old_ptr + last_pos, (old_length - last_pos) * sizeof(char32_t)); + new_ptrw += old_length - last_pos; } - new_string += substr(search_from, length() - search_from); + *new_ptrw = 0; return new_string; } -String String::replace(const char *p_key, const char *p_with) const { - String new_string; +static String _replace_common(const String &p_this, char const *p_key, char const *p_with, bool p_case_insensitive) { + int key_length = strlen(p_key); + + if (key_length == 0 || p_this.is_empty()) { + return p_this; + } + int search_from = 0; int result = 0; - while ((result = find(p_key, search_from)) >= 0) { - new_string += substr(search_from, result - search_from); - new_string += p_with; - int k = 0; - while (p_key[k] != '\0') { - k++; + LocalVector<int> found; + + while ((result = (p_case_insensitive ? p_this.findn(p_key, search_from) : p_this.find(p_key, search_from))) >= 0) { + found.push_back(result); + search_from = result + key_length; + } + + if (found.is_empty()) { + return p_this; + } + + String new_string; + + // Create string to speed up copying as we can't do `memcopy` between `char32_t` and `char`. + const String with_string(p_with); + const int with_length = with_string.length(); + const int old_length = p_this.length(); + + new_string.resize(old_length + found.size() * (with_length - key_length) + 1); + + char32_t *new_ptrw = new_string.ptrw(); + const char32_t *old_ptr = p_this.ptr(); + const char32_t *with_ptr = with_string.ptr(); + + int last_pos = 0; + + for (const int &pos : found) { + if (last_pos != pos) { + memcpy(new_ptrw, old_ptr + last_pos, (pos - last_pos) * sizeof(char32_t)); + new_ptrw += (pos - last_pos); } - search_from = result + k; + if (with_length) { + memcpy(new_ptrw, with_ptr, with_length * sizeof(char32_t)); + new_ptrw += with_length; + } + last_pos = pos + key_length; } - if (search_from == 0) { - return *this; + if (last_pos != old_length) { + memcpy(new_ptrw, old_ptr + last_pos, (old_length - last_pos) * sizeof(char32_t)); + new_ptrw += old_length - last_pos; } - new_string += substr(search_from, length() - search_from); + *new_ptrw = 0; return new_string; } +String String::replace(const String &p_key, const String &p_with) const { + return _replace_common(*this, p_key, p_with, false); +} + +String String::replace(const char *p_key, const char *p_with) const { + return _replace_common(*this, p_key, p_with, false); +} + String String::replace_first(const String &p_key, const String &p_with) const { int pos = find(p_key); if (pos >= 0) { - return substr(0, pos) + p_with + substr(pos + p_key.length(), length()); + const int old_length = length(); + const int key_length = p_key.length(); + const int with_length = p_with.length(); + + String new_string; + new_string.resize(old_length + (with_length - key_length) + 1); + + char32_t *new_ptrw = new_string.ptrw(); + const char32_t *old_ptr = ptr(); + const char32_t *with_ptr = p_with.ptr(); + + if (pos > 0) { + memcpy(new_ptrw, old_ptr, pos * sizeof(char32_t)); + new_ptrw += pos; + } + + if (with_length) { + memcpy(new_ptrw, with_ptr, with_length * sizeof(char32_t)); + new_ptrw += with_length; + } + pos += key_length; + + if (pos != old_length) { + memcpy(new_ptrw, old_ptr + pos, (old_length - pos) * sizeof(char32_t)); + new_ptrw += (old_length - pos); + } + + *new_ptrw = 0; + + return new_string; } return *this; @@ -4042,55 +4213,45 @@ String String::replace_first(const String &p_key, const String &p_with) const { String String::replace_first(const char *p_key, const char *p_with) const { int pos = find(p_key); if (pos >= 0) { - int substring_length = strlen(p_key); - return substr(0, pos) + p_with + substr(pos + substring_length, length()); - } - - return *this; -} + const int old_length = length(); + const int key_length = strlen(p_key); + const int with_length = strlen(p_with); -String String::replacen(const String &p_key, const String &p_with) const { - String new_string; - int search_from = 0; - int result = 0; + String new_string; + new_string.resize(old_length + (with_length - key_length) + 1); - while ((result = findn(p_key, search_from)) >= 0) { - new_string += substr(search_from, result - search_from); - new_string += p_with; - search_from = result + p_key.length(); - } + char32_t *new_ptrw = new_string.ptrw(); + const char32_t *old_ptr = ptr(); - if (search_from == 0) { - return *this; - } + if (pos > 0) { + memcpy(new_ptrw, old_ptr, pos * sizeof(char32_t)); + new_ptrw += pos; + } - new_string += substr(search_from, length() - search_from); - return new_string; -} + for (int i = 0; i < with_length; ++i) { + *new_ptrw++ = p_with[i]; + } + pos += key_length; -String String::replacen(const char *p_key, const char *p_with) const { - String new_string; - int search_from = 0; - int result = 0; - int substring_length = strlen(p_key); + if (pos != old_length) { + memcpy(new_ptrw, old_ptr + pos, (old_length - pos) * sizeof(char32_t)); + new_ptrw += (old_length - pos); + } - if (substring_length == 0) { - return *this; // there's nothing to match or substitute - } + *new_ptrw = 0; - while ((result = findn(p_key, search_from)) >= 0) { - new_string += substr(search_from, result - search_from); - new_string += p_with; - search_from = result + substring_length; + return new_string; } - if (search_from == 0) { - return *this; - } + return *this; +} - new_string += substr(search_from, length() - search_from); +String String::replacen(const String &p_key, const String &p_with) const { + return _replace_common(*this, p_key, p_with, true); +} - return new_string; +String String::replacen(const char *p_key, const char *p_with) const { + return _replace_common(*this, p_key, p_with, true); } String String::repeat(int p_count) const { @@ -4384,10 +4545,7 @@ String String::simplify_path() const { dirs.remove_at(i); i--; } else if (d == "..") { - if (i == 0) { - dirs.remove_at(i); - i--; - } else { + if (i != 0) { dirs.remove_at(i); dirs.remove_at(i - 1); i -= 2; @@ -4466,7 +4624,7 @@ bool String::is_absolute_path() const { } } -String String::validate_identifier() const { +String String::validate_ascii_identifier() const { if (is_empty()) { return "_"; // Empty string is not a valid identifier; } @@ -4489,7 +4647,7 @@ String String::validate_identifier() const { return result; } -bool String::is_valid_identifier() const { +bool String::is_valid_ascii_identifier() const { int len = length(); if (len == 0) { @@ -4511,6 +4669,26 @@ bool String::is_valid_identifier() const { return true; } +bool String::is_valid_unicode_identifier() const { + const char32_t *str = ptr(); + int len = length(); + + if (len == 0) { + return false; // Empty string. + } + + if (!is_unicode_identifier_start(str[0])) { + return false; + } + + for (int i = 1; i < len; i++) { + if (!is_unicode_identifier_continue(str[i])) { + return false; + } + } + return true; +} + bool String::is_valid_string() const { int l = length(); const char32_t *src = get_data(); @@ -4757,8 +4935,9 @@ String String::xml_unescape() const { return String(); } str.resize(len + 1); - _xml_unescape(get_data(), l, str.ptrw()); - str[len] = 0; + char32_t *str_ptrw = str.ptrw(); + _xml_unescape(get_data(), l, str_ptrw); + str_ptrw[len] = 0; return str; } @@ -5321,6 +5500,11 @@ String String::lpad(int min_length, const String &character) const { // "fish %s %d pie" % ["frog", 12] // In case of an error, the string returned is the error description and "error" is true. String String::sprintf(const Array &values, bool *error) const { + static const String ZERO("0"); + static const String SPACE(" "); + static const String MINUS("-"); + static const String PLUS("+"); + String formatted; char32_t *self = (char32_t *)get_data(); bool in_format = false; @@ -5343,7 +5527,7 @@ String String::sprintf(const Array &values, bool *error) const { if (in_format) { // We have % - let's see what else we get. switch (c) { case '%': { // Replace %% with % - formatted += chr(c); + formatted += c; in_format = false; break; } @@ -5393,7 +5577,7 @@ String String::sprintf(const Array &values, bool *error) const { // Padding. int pad_chars_count = (negative || show_sign) ? min_chars - 1 : min_chars; - String pad_char = pad_with_zeros ? String("0") : String(" "); + const String &pad_char = pad_with_zeros ? ZERO : SPACE; if (left_justified) { str = str.rpad(pad_chars_count, pad_char); } else { @@ -5402,7 +5586,7 @@ String String::sprintf(const Array &values, bool *error) const { // Sign. if (show_sign || negative) { - String sign_char = negative ? "-" : "+"; + const String &sign_char = negative ? MINUS : PLUS; if (left_justified) { str = str.insert(0, sign_char); } else { @@ -5439,7 +5623,7 @@ String String::sprintf(const Array &values, bool *error) const { // Padding. Leave room for sign later if required. int pad_chars_count = (is_negative || show_sign) ? min_chars - 1 : min_chars; - String pad_char = (pad_with_zeros && is_finite) ? String("0") : String(" "); // Never pad NaN or inf with zeros + const String &pad_char = (pad_with_zeros && is_finite) ? ZERO : SPACE; // Never pad NaN or inf with zeros if (left_justified) { str = str.rpad(pad_chars_count, pad_char); } else { @@ -5448,7 +5632,7 @@ String String::sprintf(const Array &values, bool *error) const { // Add sign if needed. if (show_sign || is_negative) { - String sign_char = is_negative ? "-" : "+"; + const String &sign_char = is_negative ? MINUS : PLUS; if (left_justified) { str = str.insert(0, sign_char); } else { @@ -5501,7 +5685,7 @@ String String::sprintf(const Array &values, bool *error) const { // Padding. Leave room for sign later if required. int pad_chars_count = val < 0 ? min_chars - 1 : min_chars; - String pad_char = (pad_with_zeros && is_finite) ? String("0") : String(" "); // Never pad NaN or inf with zeros + const String &pad_char = (pad_with_zeros && is_finite) ? ZERO : SPACE; // Never pad NaN or inf with zeros if (left_justified) { number_str = number_str.rpad(pad_chars_count, pad_char); } else { @@ -5511,9 +5695,9 @@ String String::sprintf(const Array &values, bool *error) const { // Add sign if needed. if (val < 0) { if (left_justified) { - number_str = number_str.insert(0, "-"); + number_str = number_str.insert(0, MINUS); } else { - number_str = number_str.insert(pad_with_zeros ? 0 : number_str.length() - initial_len, "-"); + number_str = number_str.insert(pad_with_zeros ? 0 : number_str.length() - initial_len, MINUS); } } @@ -5678,7 +5862,7 @@ String String::sprintf(const Array &values, bool *error) const { in_decimals = false; break; default: - formatted += chr(c); + formatted += c; } } } diff --git a/core/string/ustring.h b/core/string/ustring.h index 9df2d56e80..11f15031f9 100644 --- a/core/string/ustring.h +++ b/core/string/ustring.h @@ -459,10 +459,11 @@ public: // node functions static String get_invalid_node_name_characters(bool p_allow_internal = false); String validate_node_name() const; - String validate_identifier() const; + String validate_ascii_identifier() const; String validate_filename() const; - bool is_valid_identifier() const; + bool is_valid_ascii_identifier() const; + bool is_valid_unicode_identifier() const; bool is_valid_int() const; bool is_valid_float() const; bool is_valid_hex_number(bool p_with_prefix) const; @@ -470,6 +471,9 @@ public: bool is_valid_ip_address() const; bool is_valid_filename() const; + // Use `is_valid_ascii_identifier()` instead. Kept for compatibility. + bool is_valid_identifier() const { return is_valid_ascii_identifier(); } + /** * The constructors must not depend on other overloads */ diff --git a/core/templates/command_queue_mt.cpp b/core/templates/command_queue_mt.cpp index ef75a70868..5fa767263f 100644 --- a/core/templates/command_queue_mt.cpp +++ b/core/templates/command_queue_mt.cpp @@ -33,14 +33,6 @@ #include "core/config/project_settings.h" #include "core/os/os.h" -void CommandQueueMT::lock() { - mutex.lock(); -} - -void CommandQueueMT::unlock() { - mutex.unlock(); -} - CommandQueueMT::CommandQueueMT() { command_mem.reserve(DEFAULT_COMMAND_MEM_SIZE_KB * 1024); } diff --git a/core/templates/command_queue_mt.h b/core/templates/command_queue_mt.h index 0748e9cb83..8ef5dd3064 100644 --- a/core/templates/command_queue_mt.h +++ b/core/templates/command_queue_mt.h @@ -362,35 +362,37 @@ class CommandQueueMT { return; } - lock(); + MutexLock lock(mutex); - uint32_t allowance_id = WorkerThreadPool::thread_enter_unlock_allowance_zone(&mutex); while (flush_read_ptr < command_mem.size()) { uint64_t size = *(uint64_t *)&command_mem[flush_read_ptr]; flush_read_ptr += 8; CommandBase *cmd = reinterpret_cast<CommandBase *>(&command_mem[flush_read_ptr]); + uint32_t allowance_id = WorkerThreadPool::thread_enter_unlock_allowance_zone(lock); cmd->call(); + WorkerThreadPool::thread_exit_unlock_allowance_zone(allowance_id); + + // Handle potential realloc due to the command and unlock allowance. + cmd = reinterpret_cast<CommandBase *>(&command_mem[flush_read_ptr]); + if (unlikely(cmd->sync)) { sync_head++; - unlock(); // Give an opportunity to awaiters right away. + lock.~MutexLock(); // Give an opportunity to awaiters right away. sync_cond_var.notify_all(); - lock(); + new (&lock) MutexLock(mutex); + // Handle potential realloc happened during unlock. + cmd = reinterpret_cast<CommandBase *>(&command_mem[flush_read_ptr]); } - // If the command involved reallocating the buffer, the address may have changed. - cmd = reinterpret_cast<CommandBase *>(&command_mem[flush_read_ptr]); cmd->~CommandBase(); flush_read_ptr += size; } - WorkerThreadPool::thread_exit_unlock_allowance_zone(allowance_id); command_mem.clear(); flush_read_ptr = 0; _prevent_sync_wraparound(); - - unlock(); } _FORCE_INLINE_ void _wait_for_sync(MutexLock<BinaryMutex> &p_lock) { @@ -406,9 +408,6 @@ class CommandQueueMT { void _no_op() {} public: - void lock(); - void unlock(); - /* NORMAL PUSH COMMANDS */ DECL_PUSH(0) SPACE_SEP_LIST(DECL_PUSH, 15) @@ -442,9 +441,8 @@ public: } void set_pump_task_id(WorkerThreadPool::TaskID p_task_id) { - lock(); + MutexLock lock(mutex); pump_task_id = p_task_id; - unlock(); } CommandQueueMT(); diff --git a/core/templates/cowdata.h b/core/templates/cowdata.h index f22ae1f1d3..fedcfaec3b 100644 --- a/core/templates/cowdata.h +++ b/core/templates/cowdata.h @@ -160,7 +160,7 @@ private: return *out; } - void _unref(void *p_data); + void _unref(); void _ref(const CowData *p_from); void _ref(const CowData &p_from); USize _copy_on_write(); @@ -222,12 +222,15 @@ public: } Error insert(Size p_pos, const T &p_val) { - ERR_FAIL_INDEX_V(p_pos, size() + 1, ERR_INVALID_PARAMETER); - resize(size() + 1); - for (Size i = (size() - 1); i > p_pos; i--) { - set(i, get(i - 1)); + Size new_size = size() + 1; + ERR_FAIL_INDEX_V(p_pos, new_size, ERR_INVALID_PARAMETER); + Error err = resize(new_size); + ERR_FAIL_COND_V(err, err); + T *p = ptrw(); + for (Size i = new_size - 1; i > p_pos; i--) { + p[i] = p[i - 1]; } - set(p_pos, p_val); + p[p_pos] = p_val; return OK; } @@ -242,30 +245,29 @@ public: }; template <typename T> -void CowData<T>::_unref(void *p_data) { - if (!p_data) { +void CowData<T>::_unref() { + if (!_ptr) { return; } SafeNumeric<USize> *refc = _get_refcount(); - if (refc->decrement() > 0) { return; // still in use } // clean up if constexpr (!std::is_trivially_destructible_v<T>) { - USize *count = _get_size(); - T *data = (T *)(count + 1); + USize current_size = *_get_size(); - for (USize i = 0; i < *count; ++i) { + for (USize i = 0; i < current_size; ++i) { // call destructors - data[i].~T(); + T *t = &_ptr[i]; + t->~T(); } } // free mem - Memory::free_static(((uint8_t *)p_data) - DATA_OFFSET, false); + Memory::free_static(((uint8_t *)_ptr) - DATA_OFFSET, false); } template <typename T> @@ -300,7 +302,7 @@ typename CowData<T>::USize CowData<T>::_copy_on_write() { } } - _unref(_ptr); + _unref(); _ptr = _data_ptr; rc = 1; @@ -321,7 +323,7 @@ Error CowData<T>::resize(Size p_size) { if (p_size == 0) { // wants to clean up - _unref(_ptr); + _unref(); _ptr = nullptr; return OK; } @@ -460,7 +462,7 @@ void CowData<T>::_ref(const CowData &p_from) { return; // self assign, do nothing. } - _unref(_ptr); + _unref(); _ptr = nullptr; if (!p_from._ptr) { @@ -474,7 +476,7 @@ void CowData<T>::_ref(const CowData &p_from) { template <typename T> CowData<T>::~CowData() { - _unref(_ptr); + _unref(); } #if defined(__GNUC__) && !defined(__clang__) diff --git a/core/templates/paged_allocator.h b/core/templates/paged_allocator.h index 4854e1b866..0b70fa02f3 100644 --- a/core/templates/paged_allocator.h +++ b/core/templates/paged_allocator.h @@ -55,7 +55,7 @@ class PagedAllocator { public: template <typename... Args> T *alloc(Args &&...p_args) { - if (thread_safe) { + if constexpr (thread_safe) { spin_lock.lock(); } if (unlikely(allocs_available == 0)) { @@ -76,7 +76,7 @@ public: allocs_available--; T *alloc = available_pool[allocs_available >> page_shift][allocs_available & page_mask]; - if (thread_safe) { + if constexpr (thread_safe) { spin_lock.unlock(); } memnew_placement(alloc, T(p_args...)); @@ -84,13 +84,13 @@ public: } void free(T *p_mem) { - if (thread_safe) { + if constexpr (thread_safe) { spin_lock.lock(); } p_mem->~T(); available_pool[allocs_available >> page_shift][allocs_available & page_mask] = p_mem; allocs_available++; - if (thread_safe) { + if constexpr (thread_safe) { spin_lock.unlock(); } } @@ -120,28 +120,28 @@ private: public: void reset(bool p_allow_unfreed = false) { - if (thread_safe) { + if constexpr (thread_safe) { spin_lock.lock(); } _reset(p_allow_unfreed); - if (thread_safe) { + if constexpr (thread_safe) { spin_lock.unlock(); } } bool is_configured() const { - if (thread_safe) { + if constexpr (thread_safe) { spin_lock.lock(); } bool result = page_size > 0; - if (thread_safe) { + if constexpr (thread_safe) { spin_lock.unlock(); } return result; } void configure(uint32_t p_page_size) { - if (thread_safe) { + if constexpr (thread_safe) { spin_lock.lock(); } ERR_FAIL_COND(page_pool != nullptr); // Safety check. @@ -149,7 +149,7 @@ public: page_size = nearest_power_of_2_templated(p_page_size); page_mask = page_size - 1; page_shift = get_shift_from_power_of_2(page_size); - if (thread_safe) { + if constexpr (thread_safe) { spin_lock.unlock(); } } @@ -161,7 +161,7 @@ public: } ~PagedAllocator() { - if (thread_safe) { + if constexpr (thread_safe) { spin_lock.lock(); } bool leaked = allocs_available < pages_allocated * page_size; @@ -172,7 +172,7 @@ public: } else { _reset(false); } - if (thread_safe) { + if constexpr (thread_safe) { spin_lock.unlock(); } } diff --git a/core/templates/rid_owner.h b/core/templates/rid_owner.h index 86304d3c73..537413e2ba 100644 --- a/core/templates/rid_owner.h +++ b/core/templates/rid_owner.h @@ -82,7 +82,7 @@ class RID_Alloc : public RID_AllocBase { mutable SpinLock spin_lock; _FORCE_INLINE_ RID _allocate_rid() { - if (THREAD_SAFE) { + if constexpr (THREAD_SAFE) { spin_lock.lock(); } @@ -128,7 +128,7 @@ class RID_Alloc : public RID_AllocBase { alloc_count++; - if (THREAD_SAFE) { + if constexpr (THREAD_SAFE) { spin_lock.unlock(); } @@ -156,14 +156,14 @@ public: if (p_rid == RID()) { return nullptr; } - if (THREAD_SAFE) { + if constexpr (THREAD_SAFE) { spin_lock.lock(); } uint64_t id = p_rid.get_id(); uint32_t idx = uint32_t(id & 0xFFFFFFFF); if (unlikely(idx >= max_alloc)) { - if (THREAD_SAFE) { + if constexpr (THREAD_SAFE) { spin_lock.unlock(); } return nullptr; @@ -176,14 +176,14 @@ public: if (unlikely(p_initialize)) { if (unlikely(!(validator_chunks[idx_chunk][idx_element] & 0x80000000))) { - if (THREAD_SAFE) { + if constexpr (THREAD_SAFE) { spin_lock.unlock(); } ERR_FAIL_V_MSG(nullptr, "Initializing already initialized RID"); } if (unlikely((validator_chunks[idx_chunk][idx_element] & 0x7FFFFFFF) != validator)) { - if (THREAD_SAFE) { + if constexpr (THREAD_SAFE) { spin_lock.unlock(); } ERR_FAIL_V_MSG(nullptr, "Attempting to initialize the wrong RID"); @@ -192,7 +192,7 @@ public: validator_chunks[idx_chunk][idx_element] &= 0x7FFFFFFF; //initialized } else if (unlikely(validator_chunks[idx_chunk][idx_element] != validator)) { - if (THREAD_SAFE) { + if constexpr (THREAD_SAFE) { spin_lock.unlock(); } if ((validator_chunks[idx_chunk][idx_element] & 0x80000000) && validator_chunks[idx_chunk][idx_element] != 0xFFFFFFFF) { @@ -203,7 +203,7 @@ public: T *ptr = &chunks[idx_chunk][idx_element]; - if (THREAD_SAFE) { + if constexpr (THREAD_SAFE) { spin_lock.unlock(); } @@ -221,14 +221,14 @@ public: } _FORCE_INLINE_ bool owns(const RID &p_rid) const { - if (THREAD_SAFE) { + if constexpr (THREAD_SAFE) { spin_lock.lock(); } uint64_t id = p_rid.get_id(); uint32_t idx = uint32_t(id & 0xFFFFFFFF); if (unlikely(idx >= max_alloc)) { - if (THREAD_SAFE) { + if constexpr (THREAD_SAFE) { spin_lock.unlock(); } return false; @@ -241,7 +241,7 @@ public: bool owned = (validator != 0x7FFFFFFF) && (validator_chunks[idx_chunk][idx_element] & 0x7FFFFFFF) == validator; - if (THREAD_SAFE) { + if constexpr (THREAD_SAFE) { spin_lock.unlock(); } @@ -249,14 +249,14 @@ public: } _FORCE_INLINE_ void free(const RID &p_rid) { - if (THREAD_SAFE) { + if constexpr (THREAD_SAFE) { spin_lock.lock(); } uint64_t id = p_rid.get_id(); uint32_t idx = uint32_t(id & 0xFFFFFFFF); if (unlikely(idx >= max_alloc)) { - if (THREAD_SAFE) { + if constexpr (THREAD_SAFE) { spin_lock.unlock(); } ERR_FAIL(); @@ -267,12 +267,12 @@ public: uint32_t validator = uint32_t(id >> 32); if (unlikely(validator_chunks[idx_chunk][idx_element] & 0x80000000)) { - if (THREAD_SAFE) { + if constexpr (THREAD_SAFE) { spin_lock.unlock(); } ERR_FAIL_MSG("Attempted to free an uninitialized or invalid RID."); } else if (unlikely(validator_chunks[idx_chunk][idx_element] != validator)) { - if (THREAD_SAFE) { + if constexpr (THREAD_SAFE) { spin_lock.unlock(); } ERR_FAIL(); @@ -284,7 +284,7 @@ public: alloc_count--; free_list_chunks[alloc_count / elements_in_chunk][alloc_count % elements_in_chunk] = idx; - if (THREAD_SAFE) { + if constexpr (THREAD_SAFE) { spin_lock.unlock(); } } @@ -293,7 +293,7 @@ public: return alloc_count; } void get_owned_list(List<RID> *p_owned) const { - if (THREAD_SAFE) { + if constexpr (THREAD_SAFE) { spin_lock.lock(); } for (size_t i = 0; i < max_alloc; i++) { @@ -302,14 +302,14 @@ public: p_owned->push_back(_make_from_id((validator << 32) | i)); } } - if (THREAD_SAFE) { + if constexpr (THREAD_SAFE) { spin_lock.unlock(); } } //used for fast iteration in the elements or RIDs void fill_owned_buffer(RID *p_rid_buffer) const { - if (THREAD_SAFE) { + if constexpr (THREAD_SAFE) { spin_lock.lock(); } uint32_t idx = 0; @@ -320,7 +320,7 @@ public: idx++; } } - if (THREAD_SAFE) { + if constexpr (THREAD_SAFE) { spin_lock.unlock(); } } diff --git a/core/templates/sort_array.h b/core/templates/sort_array.h index e7eaf8ee81..5bf5b2819d 100644 --- a/core/templates/sort_array.h +++ b/core/templates/sort_array.h @@ -174,14 +174,14 @@ public: while (true) { while (compare(p_array[p_first], p_pivot)) { - if (Validate) { + if constexpr (Validate) { ERR_BAD_COMPARE(p_first == unmodified_last - 1); } p_first++; } p_last--; while (compare(p_pivot, p_array[p_last])) { - if (Validate) { + if constexpr (Validate) { ERR_BAD_COMPARE(p_last == unmodified_first); } p_last--; @@ -251,7 +251,7 @@ public: inline void unguarded_linear_insert(int64_t p_last, T p_value, T *p_array) const { int64_t next = p_last - 1; while (compare(p_value, p_array[next])) { - if (Validate) { + if constexpr (Validate) { ERR_BAD_COMPARE(next == 0); } p_array[p_last] = p_array[next]; diff --git a/core/typedefs.h b/core/typedefs.h index 0de803293d..35c4668581 100644 --- a/core/typedefs.h +++ b/core/typedefs.h @@ -44,6 +44,9 @@ #include "core/error/error_list.h" #include <cstdint> +// Ensure that C++ standard is at least C++17. If on MSVC, also ensures that the `Zc:__cplusplus` flag is present. +static_assert(__cplusplus >= 201703L); + // Turn argument to string constant: // https://gcc.gnu.org/onlinedocs/cpp/Stringizing.html#Stringizing #ifndef _STR diff --git a/core/variant/array.cpp b/core/variant/array.cpp index 3685515db5..869499e668 100644 --- a/core/variant/array.cpp +++ b/core/variant/array.cpp @@ -235,7 +235,7 @@ void Array::assign(const Array &p_array) { for (int i = 0; i < size; i++) { const Variant &element = source[i]; if (element.get_type() != Variant::NIL && (element.get_type() != Variant::OBJECT || !typed.validate_object(element, "assign"))) { - ERR_FAIL_MSG(vformat(R"(Unable to convert array index %i from "%s" to "%s".)", i, Variant::get_type_name(element.get_type()), Variant::get_type_name(typed.type))); + ERR_FAIL_MSG(vformat(R"(Unable to convert array index %d from "%s" to "%s".)", i, Variant::get_type_name(element.get_type()), Variant::get_type_name(typed.type))); } } _p->array = p_array._p->array; @@ -258,11 +258,11 @@ void Array::assign(const Array &p_array) { continue; } if (!Variant::can_convert_strict(value->get_type(), typed.type)) { - ERR_FAIL_MSG("Unable to convert array index " + itos(i) + " from '" + Variant::get_type_name(value->get_type()) + "' to '" + Variant::get_type_name(typed.type) + "'."); + ERR_FAIL_MSG(vformat(R"(Unable to convert array index %d from "%s" to "%s".)", i, Variant::get_type_name(value->get_type()), Variant::get_type_name(typed.type))); } Callable::CallError ce; Variant::construct(typed.type, data[i], &value, 1, ce); - ERR_FAIL_COND_MSG(ce.error, vformat(R"(Unable to convert array index %i from "%s" to "%s".)", i, Variant::get_type_name(value->get_type()), Variant::get_type_name(typed.type))); + ERR_FAIL_COND_MSG(ce.error, vformat(R"(Unable to convert array index %d from "%s" to "%s".)", i, Variant::get_type_name(value->get_type()), Variant::get_type_name(typed.type))); } } else if (Variant::can_convert_strict(source_typed.type, typed.type)) { // from primitives to different convertible primitives @@ -270,7 +270,7 @@ void Array::assign(const Array &p_array) { const Variant *value = source + i; Callable::CallError ce; Variant::construct(typed.type, data[i], &value, 1, ce); - ERR_FAIL_COND_MSG(ce.error, vformat(R"(Unable to convert array index %i from "%s" to "%s".)", i, Variant::get_type_name(value->get_type()), Variant::get_type_name(typed.type))); + ERR_FAIL_COND_MSG(ce.error, vformat(R"(Unable to convert array index %d from "%s" to "%s".)", i, Variant::get_type_name(value->get_type()), Variant::get_type_name(typed.type))); } } else { ERR_FAIL_MSG(vformat(R"(Cannot assign contents of "Array[%s]" to "Array[%s]".)", Variant::get_type_name(source_typed.type), Variant::get_type_name(typed.type))); @@ -803,6 +803,10 @@ bool Array::is_same_typed(const Array &p_other) const { return _p->typed == p_other._p->typed; } +bool Array::is_same_instance(const Array &p_other) const { + return _p == p_other._p; +} + uint32_t Array::get_typed_builtin() const { return _p->typed.type; } @@ -815,6 +819,12 @@ Variant Array::get_typed_script() const { return _p->typed.script; } +Array Array::create_read_only() { + Array array; + array.make_read_only(); + return array; +} + void Array::make_read_only() { if (_p->read_only == nullptr) { _p->read_only = memnew(Variant); diff --git a/core/variant/array.h b/core/variant/array.h index 3aa957b312..12824ee3f6 100644 --- a/core/variant/array.h +++ b/core/variant/array.h @@ -186,12 +186,14 @@ public: void set_typed(uint32_t p_type, const StringName &p_class_name, const Variant &p_script); bool is_typed() const; bool is_same_typed(const Array &p_other) const; + bool is_same_instance(const Array &p_other) const; uint32_t get_typed_builtin() const; StringName get_typed_class_name() const; Variant get_typed_script() const; void make_read_only(); bool is_read_only() const; + static Array create_read_only(); Array(const Array &p_base, uint32_t p_type, const StringName &p_class_name, const Variant &p_script); Array(const Array &p_from); diff --git a/core/variant/binder_common.h b/core/variant/binder_common.h index 61b90e2a26..fa49767d46 100644 --- a/core/variant/binder_common.h +++ b/core/variant/binder_common.h @@ -214,11 +214,11 @@ struct VariantCaster<char32_t> { template <> struct PtrToArg<char32_t> { _FORCE_INLINE_ static char32_t convert(const void *p_ptr) { - return char32_t(*reinterpret_cast<const int *>(p_ptr)); + return char32_t(*reinterpret_cast<const int64_t *>(p_ptr)); } typedef int64_t EncodeT; _FORCE_INLINE_ static void encode(char32_t p_val, const void *p_ptr) { - *(int *)p_ptr = p_val; + *(int64_t *)p_ptr = p_val; } }; diff --git a/core/variant/callable.cpp b/core/variant/callable.cpp index 667aae879c..9dff5c1e91 100644 --- a/core/variant/callable.cpp +++ b/core/variant/callable.cpp @@ -112,7 +112,7 @@ Error Callable::rpcp(int p_id, const Variant **p_arguments, int p_argcount, Call argptrs[i + 2] = p_arguments[i]; } - CallError tmp; + CallError tmp; // TODO: Check `tmp`? Error err = (Error)obj->callp(SNAME("rpc_id"), argptrs, argcount, tmp).operator int64_t(); r_call_error.error = Callable::CallError::CALL_OK; diff --git a/core/variant/dictionary.cpp b/core/variant/dictionary.cpp index 7416101d51..0754814d35 100644 --- a/core/variant/dictionary.cpp +++ b/core/variant/dictionary.cpp @@ -32,6 +32,7 @@ #include "core/templates/hash_map.h" #include "core/templates/safe_refcount.h" +#include "core/variant/container_type_validate.h" #include "core/variant/variant.h" // required in this order by VariantInternal, do not remove this comment. #include "core/object/class_db.h" @@ -43,6 +44,9 @@ struct DictionaryPrivate { SafeRefCount refcount; Variant *read_only = nullptr; // If enabled, a pointer is used to a temporary value that is used to return read-only values. HashMap<Variant, Variant, VariantHasher, StringLikeVariantComparator> variant_map; + ContainerTypeValidate typed_key; + ContainerTypeValidate typed_value; + Variant *typed_fallback = nullptr; // Allows a typed dictionary to return dummy values when attempting an invalid access. }; void Dictionary::get_key_list(List<Variant> *p_keys) const { @@ -81,15 +85,7 @@ Variant Dictionary::get_value_at_index(int p_index) const { Variant &Dictionary::operator[](const Variant &p_key) { if (unlikely(_p->read_only)) { - if (p_key.get_type() == Variant::STRING_NAME) { - const StringName *sn = VariantInternal::get_string_name(&p_key); - const String &key = sn->operator String(); - if (likely(_p->variant_map.has(key))) { - *_p->read_only = _p->variant_map[key]; - } else { - *_p->read_only = Variant(); - } - } else if (likely(_p->variant_map.has(p_key))) { + if (likely(_p->variant_map.has(p_key))) { *_p->read_only = _p->variant_map[p_key]; } else { *_p->read_only = Variant(); @@ -97,12 +93,7 @@ Variant &Dictionary::operator[](const Variant &p_key) { return *_p->read_only; } else { - if (p_key.get_type() == Variant::STRING_NAME) { - const StringName *sn = VariantInternal::get_string_name(&p_key); - return _p->variant_map[sn->operator String()]; - } else { - return _p->variant_map[p_key]; - } + return _p->variant_map[p_key]; } } @@ -133,7 +124,9 @@ Variant *Dictionary::getptr(const Variant &p_key) { } Variant Dictionary::get_valid(const Variant &p_key) const { - HashMap<Variant, Variant, VariantHasher, StringLikeVariantComparator>::ConstIterator E(_p->variant_map.find(p_key)); + Variant key = p_key; + ERR_FAIL_COND_V(!_p->typed_key.validate(key, "get_valid"), Variant()); + HashMap<Variant, Variant, VariantHasher, StringLikeVariantComparator>::ConstIterator E(_p->variant_map.find(key)); if (!E) { return Variant(); @@ -142,7 +135,9 @@ Variant Dictionary::get_valid(const Variant &p_key) const { } Variant Dictionary::get(const Variant &p_key, const Variant &p_default) const { - const Variant *result = getptr(p_key); + Variant key = p_key; + ERR_FAIL_COND_V(!_p->typed_key.validate(key, "get"), p_default); + const Variant *result = getptr(key); if (!result) { return p_default; } @@ -151,10 +146,14 @@ Variant Dictionary::get(const Variant &p_key, const Variant &p_default) const { } Variant Dictionary::get_or_add(const Variant &p_key, const Variant &p_default) { - const Variant *result = getptr(p_key); + Variant key = p_key; + ERR_FAIL_COND_V(!_p->typed_key.validate(key, "get"), p_default); + const Variant *result = getptr(key); if (!result) { - operator[](p_key) = p_default; - return p_default; + Variant value = p_default; + ERR_FAIL_COND_V(!_p->typed_value.validate(value, "add"), value); + operator[](key) = value; + return value; } return *result; } @@ -168,12 +167,16 @@ bool Dictionary::is_empty() const { } bool Dictionary::has(const Variant &p_key) const { + Variant key = p_key; + ERR_FAIL_COND_V(!_p->typed_key.validate(key, "use 'has'"), false); return _p->variant_map.has(p_key); } bool Dictionary::has_all(const Array &p_keys) const { for (int i = 0; i < p_keys.size(); i++) { - if (!has(p_keys[i])) { + Variant key = p_keys[i]; + ERR_FAIL_COND_V(!_p->typed_key.validate(key, "use 'has_all'"), false); + if (!has(key)) { return false; } } @@ -181,8 +184,10 @@ bool Dictionary::has_all(const Array &p_keys) const { } Variant Dictionary::find_key(const Variant &p_value) const { + Variant value = p_value; + ERR_FAIL_COND_V(!_p->typed_value.validate(value, "find_key"), Variant()); for (const KeyValue<Variant, Variant> &E : _p->variant_map) { - if (E.value == p_value) { + if (E.value == value) { return E.key; } } @@ -190,8 +195,10 @@ Variant Dictionary::find_key(const Variant &p_value) const { } bool Dictionary::erase(const Variant &p_key) { + Variant key = p_key; + ERR_FAIL_COND_V(!_p->typed_key.validate(key, "erase"), false); ERR_FAIL_COND_V_MSG(_p->read_only, false, "Dictionary is in read-only state."); - return _p->variant_map.erase(p_key); + return _p->variant_map.erase(key); } bool Dictionary::operator==(const Dictionary &p_dictionary) const { @@ -251,8 +258,12 @@ void Dictionary::clear() { void Dictionary::merge(const Dictionary &p_dictionary, bool p_overwrite) { ERR_FAIL_COND_MSG(_p->read_only, "Dictionary is in read-only state."); for (const KeyValue<Variant, Variant> &E : p_dictionary._p->variant_map) { - if (p_overwrite || !has(E.key)) { - operator[](E.key) = E.value; + Variant key = E.key; + Variant value = E.value; + ERR_FAIL_COND(!_p->typed_key.validate(key, "merge")); + ERR_FAIL_COND(!_p->typed_value.validate(value, "merge")); + if (p_overwrite || !has(key)) { + operator[](key) = value; } } } @@ -269,6 +280,9 @@ void Dictionary::_unref() const { if (_p->read_only) { memdelete(_p->read_only); } + if (_p->typed_fallback) { + memdelete(_p->typed_fallback); + } memdelete(_p); } _p = nullptr; @@ -297,6 +311,9 @@ uint32_t Dictionary::recursive_hash(int recursion_count) const { Array Dictionary::keys() const { Array varr; + if (is_typed_key()) { + varr.set_typed(get_typed_key_builtin(), get_typed_key_class_name(), get_typed_key_script()); + } if (_p->variant_map.is_empty()) { return varr; } @@ -314,6 +331,9 @@ Array Dictionary::keys() const { Array Dictionary::values() const { Array varr; + if (is_typed_value()) { + varr.set_typed(get_typed_value_builtin(), get_typed_value_class_name(), get_typed_value_script()); + } if (_p->variant_map.is_empty()) { return varr; } @@ -329,6 +349,146 @@ Array Dictionary::values() const { return varr; } +void Dictionary::assign(const Dictionary &p_dictionary) { + const ContainerTypeValidate &typed_key = _p->typed_key; + const ContainerTypeValidate &typed_key_source = p_dictionary._p->typed_key; + + const ContainerTypeValidate &typed_value = _p->typed_value; + const ContainerTypeValidate &typed_value_source = p_dictionary._p->typed_value; + + if ((typed_key == typed_key_source || typed_key.type == Variant::NIL || (typed_key_source.type == Variant::OBJECT && typed_key.can_reference(typed_key_source))) && + (typed_value == typed_value_source || typed_value.type == Variant::NIL || (typed_value_source.type == Variant::OBJECT && typed_value.can_reference(typed_value_source)))) { + // From same to same or, + // from anything to variants or, + // from subclasses to base classes. + _p->variant_map = p_dictionary._p->variant_map; + return; + } + + int size = p_dictionary._p->variant_map.size(); + HashMap<Variant, Variant, VariantHasher, StringLikeVariantComparator> variant_map = HashMap<Variant, Variant, VariantHasher, StringLikeVariantComparator>(size); + + Vector<Variant> key_array; + key_array.resize(size); + Variant *key_data = key_array.ptrw(); + + Vector<Variant> value_array; + value_array.resize(size); + Variant *value_data = value_array.ptrw(); + + if (typed_key == typed_key_source || typed_key.type == Variant::NIL || (typed_key_source.type == Variant::OBJECT && typed_key.can_reference(typed_key_source))) { + // From same to same or, + // from anything to variants or, + // from subclasses to base classes. + int i = 0; + for (const KeyValue<Variant, Variant> &E : p_dictionary._p->variant_map) { + const Variant *key = &E.key; + key_data[i++] = *key; + } + } else if ((typed_key_source.type == Variant::NIL && typed_key.type == Variant::OBJECT) || (typed_key_source.type == Variant::OBJECT && typed_key_source.can_reference(typed_key))) { + // From variants to objects or, + // from base classes to subclasses. + int i = 0; + for (const KeyValue<Variant, Variant> &E : p_dictionary._p->variant_map) { + const Variant *key = &E.key; + if (key->get_type() != Variant::NIL && (key->get_type() != Variant::OBJECT || !typed_key.validate_object(*key, "assign"))) { + ERR_FAIL_MSG(vformat(R"(Unable to convert key from "%s" to "%s".)", Variant::get_type_name(key->get_type()), Variant::get_type_name(typed_key.type))); + } + key_data[i++] = *key; + } + } else if (typed_key.type == Variant::OBJECT || typed_key_source.type == Variant::OBJECT) { + ERR_FAIL_MSG(vformat(R"(Cannot assign contents of "Dictionary[%s, %s]" to "Dictionary[%s, %s]".)", Variant::get_type_name(typed_key_source.type), Variant::get_type_name(typed_value_source.type), + Variant::get_type_name(typed_key.type), Variant::get_type_name(typed_value.type))); + } else if (typed_key_source.type == Variant::NIL && typed_key.type != Variant::OBJECT) { + // From variants to primitives. + int i = 0; + for (const KeyValue<Variant, Variant> &E : p_dictionary._p->variant_map) { + const Variant *key = &E.key; + if (key->get_type() == typed_key.type) { + key_data[i++] = *key; + continue; + } + if (!Variant::can_convert_strict(key->get_type(), typed_key.type)) { + ERR_FAIL_MSG(vformat(R"(Unable to convert key from "%s" to "%s".)", Variant::get_type_name(key->get_type()), Variant::get_type_name(typed_key.type))); + } + Callable::CallError ce; + Variant::construct(typed_key.type, key_data[i++], &key, 1, ce); + ERR_FAIL_COND_MSG(ce.error, vformat(R"(Unable to convert key from "%s" to "%s".)", Variant::get_type_name(key->get_type()), Variant::get_type_name(typed_key.type))); + } + } else if (Variant::can_convert_strict(typed_key_source.type, typed_key.type)) { + // From primitives to different convertible primitives. + int i = 0; + for (const KeyValue<Variant, Variant> &E : p_dictionary._p->variant_map) { + const Variant *key = &E.key; + Callable::CallError ce; + Variant::construct(typed_key.type, key_data[i++], &key, 1, ce); + ERR_FAIL_COND_MSG(ce.error, vformat(R"(Unable to convert key from "%s" to "%s".)", Variant::get_type_name(key->get_type()), Variant::get_type_name(typed_key.type))); + } + } else { + ERR_FAIL_MSG(vformat(R"(Cannot assign contents of "Dictionary[%s, %s]" to "Dictionary[%s, %s].)", Variant::get_type_name(typed_key_source.type), Variant::get_type_name(typed_value_source.type), + Variant::get_type_name(typed_key.type), Variant::get_type_name(typed_value.type))); + } + + if (typed_value == typed_value_source || typed_value.type == Variant::NIL || (typed_value_source.type == Variant::OBJECT && typed_value.can_reference(typed_value_source))) { + // From same to same or, + // from anything to variants or, + // from subclasses to base classes. + int i = 0; + for (const KeyValue<Variant, Variant> &E : p_dictionary._p->variant_map) { + const Variant *value = &E.value; + value_data[i++] = *value; + } + } else if (((typed_value_source.type == Variant::NIL && typed_value.type == Variant::OBJECT) || (typed_value_source.type == Variant::OBJECT && typed_value_source.can_reference(typed_value)))) { + // From variants to objects or, + // from base classes to subclasses. + int i = 0; + for (const KeyValue<Variant, Variant> &E : p_dictionary._p->variant_map) { + const Variant *value = &E.value; + if (value->get_type() != Variant::NIL && (value->get_type() != Variant::OBJECT || !typed_value.validate_object(*value, "assign"))) { + ERR_FAIL_MSG(vformat(R"(Unable to convert value at key "%s" from "%s" to "%s".)", key_data[i], Variant::get_type_name(value->get_type()), Variant::get_type_name(typed_value.type))); + } + value_data[i++] = *value; + } + } else if (typed_value.type == Variant::OBJECT || typed_value_source.type == Variant::OBJECT) { + ERR_FAIL_MSG(vformat(R"(Cannot assign contents of "Dictionary[%s, %s]" to "Dictionary[%s, %s]".)", Variant::get_type_name(typed_key_source.type), Variant::get_type_name(typed_value_source.type), + Variant::get_type_name(typed_key.type), Variant::get_type_name(typed_value.type))); + } else if (typed_value_source.type == Variant::NIL && typed_value.type != Variant::OBJECT) { + // From variants to primitives. + int i = 0; + for (const KeyValue<Variant, Variant> &E : p_dictionary._p->variant_map) { + const Variant *value = &E.value; + if (value->get_type() == typed_value.type) { + value_data[i++] = *value; + continue; + } + if (!Variant::can_convert_strict(value->get_type(), typed_value.type)) { + ERR_FAIL_MSG(vformat(R"(Unable to convert value at key "%s" from "%s" to "%s".)", key_data[i], Variant::get_type_name(value->get_type()), Variant::get_type_name(typed_value.type))); + } + Callable::CallError ce; + Variant::construct(typed_value.type, value_data[i++], &value, 1, ce); + ERR_FAIL_COND_MSG(ce.error, vformat(R"(Unable to convert value at key "%s" from "%s" to "%s".)", key_data[i - 1], Variant::get_type_name(value->get_type()), Variant::get_type_name(typed_value.type))); + } + } else if (Variant::can_convert_strict(typed_value_source.type, typed_value.type)) { + // From primitives to different convertible primitives. + int i = 0; + for (const KeyValue<Variant, Variant> &E : p_dictionary._p->variant_map) { + const Variant *value = &E.value; + Callable::CallError ce; + Variant::construct(typed_value.type, value_data[i++], &value, 1, ce); + ERR_FAIL_COND_MSG(ce.error, vformat(R"(Unable to convert value at key "%s" from "%s" to "%s".)", key_data[i - 1], Variant::get_type_name(value->get_type()), Variant::get_type_name(typed_value.type))); + } + } else { + ERR_FAIL_MSG(vformat(R"(Cannot assign contents of "Dictionary[%s, %s]" to "Dictionary[%s, %s].)", Variant::get_type_name(typed_key_source.type), Variant::get_type_name(typed_value_source.type), + Variant::get_type_name(typed_key.type), Variant::get_type_name(typed_value.type))); + } + + for (int i = 0; i < size; i++) { + variant_map.insert(key_data[i], value_data[i]); + } + + _p->variant_map = variant_map; +} + const Variant *Dictionary::next(const Variant *p_key) const { if (p_key == nullptr) { // caller wants to get the first element @@ -337,7 +497,9 @@ const Variant *Dictionary::next(const Variant *p_key) const { } return nullptr; } - HashMap<Variant, Variant, VariantHasher, StringLikeVariantComparator>::Iterator E = _p->variant_map.find(*p_key); + Variant key = *p_key; + ERR_FAIL_COND_V(!_p->typed_key.validate(key, "next"), nullptr); + HashMap<Variant, Variant, VariantHasher, StringLikeVariantComparator>::Iterator E = _p->variant_map.find(key); if (!E) { return nullptr; @@ -367,6 +529,8 @@ bool Dictionary::is_read_only() const { Dictionary Dictionary::recursive_duplicate(bool p_deep, int recursion_count) const { Dictionary n; + n._p->typed_key = _p->typed_key; + n._p->typed_value = _p->typed_value; if (recursion_count > MAX_RECURSION) { ERR_PRINT("Max recursion reached"); @@ -387,6 +551,76 @@ Dictionary Dictionary::recursive_duplicate(bool p_deep, int recursion_count) con return n; } +void Dictionary::set_typed(uint32_t p_key_type, const StringName &p_key_class_name, const Variant &p_key_script, uint32_t p_value_type, const StringName &p_value_class_name, const Variant &p_value_script) { + ERR_FAIL_COND_MSG(_p->read_only, "Dictionary is in read-only state."); + ERR_FAIL_COND_MSG(_p->variant_map.size() > 0, "Type can only be set when dictionary is empty."); + ERR_FAIL_COND_MSG(_p->refcount.get() > 1, "Type can only be set when dictionary has no more than one user."); + ERR_FAIL_COND_MSG(_p->typed_key.type != Variant::NIL || _p->typed_value.type != Variant::NIL, "Type can only be set once."); + ERR_FAIL_COND_MSG((p_key_class_name != StringName() && p_key_type != Variant::OBJECT) || (p_value_class_name != StringName() && p_value_type != Variant::OBJECT), "Class names can only be set for type OBJECT."); + Ref<Script> key_script = p_key_script; + ERR_FAIL_COND_MSG(key_script.is_valid() && p_key_class_name == StringName(), "Script class can only be set together with base class name."); + Ref<Script> value_script = p_value_script; + ERR_FAIL_COND_MSG(value_script.is_valid() && p_value_class_name == StringName(), "Script class can only be set together with base class name."); + + _p->typed_key.type = Variant::Type(p_key_type); + _p->typed_key.class_name = p_key_class_name; + _p->typed_key.script = key_script; + _p->typed_key.where = "TypedDictionary.Key"; + + _p->typed_value.type = Variant::Type(p_value_type); + _p->typed_value.class_name = p_value_class_name; + _p->typed_value.script = value_script; + _p->typed_value.where = "TypedDictionary.Value"; +} + +bool Dictionary::is_typed() const { + return is_typed_key() || is_typed_value(); +} + +bool Dictionary::is_typed_key() const { + return _p->typed_key.type != Variant::NIL; +} + +bool Dictionary::is_typed_value() const { + return _p->typed_value.type != Variant::NIL; +} + +bool Dictionary::is_same_typed(const Dictionary &p_other) const { + return is_same_typed_key(p_other) && is_same_typed_value(p_other); +} + +bool Dictionary::is_same_typed_key(const Dictionary &p_other) const { + return _p->typed_key == p_other._p->typed_key; +} + +bool Dictionary::is_same_typed_value(const Dictionary &p_other) const { + return _p->typed_value == p_other._p->typed_value; +} + +uint32_t Dictionary::get_typed_key_builtin() const { + return _p->typed_key.type; +} + +uint32_t Dictionary::get_typed_value_builtin() const { + return _p->typed_value.type; +} + +StringName Dictionary::get_typed_key_class_name() const { + return _p->typed_key.class_name; +} + +StringName Dictionary::get_typed_value_class_name() const { + return _p->typed_value.class_name; +} + +Variant Dictionary::get_typed_key_script() const { + return _p->typed_key.script; +} + +Variant Dictionary::get_typed_value_script() const { + return _p->typed_value.script; +} + void Dictionary::operator=(const Dictionary &p_dictionary) { if (this == &p_dictionary) { return; @@ -398,6 +632,13 @@ const void *Dictionary::id() const { return _p; } +Dictionary::Dictionary(const Dictionary &p_base, uint32_t p_key_type, const StringName &p_key_class_name, const Variant &p_key_script, uint32_t p_value_type, const StringName &p_value_class_name, const Variant &p_value_script) { + _p = memnew(DictionaryPrivate); + _p->refcount.init(); + set_typed(p_key_type, p_key_class_name, p_key_script, p_value_type, p_value_class_name, p_value_script); + assign(p_base); +} + Dictionary::Dictionary(const Dictionary &p_from) { _p = nullptr; _ref(p_from); diff --git a/core/variant/dictionary.h b/core/variant/dictionary.h index 67178ee7b7..57fbefc8f2 100644 --- a/core/variant/dictionary.h +++ b/core/variant/dictionary.h @@ -80,6 +80,7 @@ public: uint32_t recursive_hash(int recursion_count) const; void operator=(const Dictionary &p_dictionary); + void assign(const Dictionary &p_dictionary); const Variant *next(const Variant *p_key = nullptr) const; Array keys() const; @@ -88,11 +89,26 @@ public: Dictionary duplicate(bool p_deep = false) const; Dictionary recursive_duplicate(bool p_deep, int recursion_count) const; + void set_typed(uint32_t p_key_type, const StringName &p_key_class_name, const Variant &p_key_script, uint32_t p_value_type, const StringName &p_value_class_name, const Variant &p_value_script); + bool is_typed() const; + bool is_typed_key() const; + bool is_typed_value() const; + bool is_same_typed(const Dictionary &p_other) const; + bool is_same_typed_key(const Dictionary &p_other) const; + bool is_same_typed_value(const Dictionary &p_other) const; + uint32_t get_typed_key_builtin() const; + uint32_t get_typed_value_builtin() const; + StringName get_typed_key_class_name() const; + StringName get_typed_value_class_name() const; + Variant get_typed_key_script() const; + Variant get_typed_value_script() const; + void make_read_only(); bool is_read_only() const; const void *id() const; + Dictionary(const Dictionary &p_base, uint32_t p_key_type, const StringName &p_key_class_name, const Variant &p_key_script, uint32_t p_value_type, const StringName &p_value_class_name, const Variant &p_value_script); Dictionary(const Dictionary &p_from); Dictionary(); ~Dictionary(); diff --git a/core/variant/type_info.h b/core/variant/type_info.h index d51c80eebe..6bb703f2dd 100644 --- a/core/variant/type_info.h +++ b/core/variant/type_info.h @@ -47,7 +47,9 @@ enum Metadata { METADATA_INT_IS_UINT32, METADATA_INT_IS_UINT64, METADATA_REAL_IS_FLOAT, - METADATA_REAL_IS_DOUBLE + METADATA_REAL_IS_DOUBLE, + METADATA_INT_IS_CHAR16, + METADATA_INT_IS_CHAR32, }; } @@ -104,8 +106,8 @@ MAKE_TYPE_INFO_WITH_META(uint32_t, Variant::INT, GodotTypeInfo::METADATA_INT_IS_ MAKE_TYPE_INFO_WITH_META(int32_t, Variant::INT, GodotTypeInfo::METADATA_INT_IS_INT32) MAKE_TYPE_INFO_WITH_META(uint64_t, Variant::INT, GodotTypeInfo::METADATA_INT_IS_UINT64) MAKE_TYPE_INFO_WITH_META(int64_t, Variant::INT, GodotTypeInfo::METADATA_INT_IS_INT64) -MAKE_TYPE_INFO(char16_t, Variant::INT) -MAKE_TYPE_INFO(char32_t, Variant::INT) +MAKE_TYPE_INFO_WITH_META(char16_t, Variant::INT, GodotTypeInfo::METADATA_INT_IS_CHAR16) +MAKE_TYPE_INFO_WITH_META(char32_t, Variant::INT, GodotTypeInfo::METADATA_INT_IS_CHAR32) MAKE_TYPE_INFO_WITH_META(float, Variant::FLOAT, GodotTypeInfo::METADATA_REAL_IS_FLOAT) MAKE_TYPE_INFO_WITH_META(double, Variant::FLOAT, GodotTypeInfo::METADATA_REAL_IS_DOUBLE) diff --git a/core/variant/typed_dictionary.h b/core/variant/typed_dictionary.h new file mode 100644 index 0000000000..67fc33b4fc --- /dev/null +++ b/core/variant/typed_dictionary.h @@ -0,0 +1,342 @@ +/**************************************************************************/ +/* typed_dictionary.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 TYPED_DICTIONARY_H +#define TYPED_DICTIONARY_H + +#include "core/object/object.h" +#include "core/variant/binder_common.h" +#include "core/variant/dictionary.h" +#include "core/variant/method_ptrcall.h" +#include "core/variant/type_info.h" +#include "core/variant/variant.h" + +template <typename K, typename V> +class TypedDictionary : public Dictionary { +public: + _FORCE_INLINE_ void operator=(const Dictionary &p_dictionary) { + ERR_FAIL_COND_MSG(!is_same_typed(p_dictionary), "Cannot assign a dictionary with a different element type."); + Dictionary::operator=(p_dictionary); + } + _FORCE_INLINE_ TypedDictionary(const Variant &p_variant) : + TypedDictionary(Dictionary(p_variant)) { + } + _FORCE_INLINE_ TypedDictionary(const Dictionary &p_dictionary) { + set_typed(Variant::OBJECT, K::get_class_static(), Variant(), Variant::OBJECT, V::get_class_static(), Variant()); + if (is_same_typed(p_dictionary)) { + Dictionary::operator=(p_dictionary); + } else { + assign(p_dictionary); + } + } + _FORCE_INLINE_ TypedDictionary() { + set_typed(Variant::OBJECT, K::get_class_static(), Variant(), Variant::OBJECT, V::get_class_static(), Variant()); + } +}; + +template <typename K, typename V> +struct VariantInternalAccessor<TypedDictionary<K, V>> { + static _FORCE_INLINE_ TypedDictionary<K, V> get(const Variant *v) { return *VariantInternal::get_dictionary(v); } + static _FORCE_INLINE_ void set(Variant *v, const TypedDictionary<K, V> &p_dictionary) { *VariantInternal::get_dictionary(v) = p_dictionary; } +}; + +template <typename K, typename V> +struct VariantInternalAccessor<const TypedDictionary<K, V> &> { + static _FORCE_INLINE_ TypedDictionary<K, V> get(const Variant *v) { return *VariantInternal::get_dictionary(v); } + static _FORCE_INLINE_ void set(Variant *v, const TypedDictionary<K, V> &p_dictionary) { *VariantInternal::get_dictionary(v) = p_dictionary; } +}; + +template <typename K, typename V> +struct PtrToArg<TypedDictionary<K, V>> { + _FORCE_INLINE_ static TypedDictionary<K, V> convert(const void *p_ptr) { + return TypedDictionary<K, V>(*reinterpret_cast<const Dictionary *>(p_ptr)); + } + typedef Dictionary EncodeT; + _FORCE_INLINE_ static void encode(TypedDictionary<K, V> p_val, void *p_ptr) { + *(Dictionary *)p_ptr = p_val; + } +}; + +template <typename K, typename V> +struct PtrToArg<const TypedDictionary<K, V> &> { + typedef Dictionary EncodeT; + _FORCE_INLINE_ static TypedDictionary<K, V> + convert(const void *p_ptr) { + return TypedDictionary<K, V>(*reinterpret_cast<const Dictionary *>(p_ptr)); + } +}; + +template <typename K, typename V> +struct GetTypeInfo<TypedDictionary<K, V>> { + static const Variant::Type VARIANT_TYPE = Variant::DICTIONARY; + static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE; + static inline PropertyInfo get_class_info() { + return PropertyInfo(Variant::DICTIONARY, String(), PROPERTY_HINT_DICTIONARY_TYPE, vformat("%s;%s", K::get_class_static(), V::get_class_static())); + } +}; + +template <typename K, typename V> +struct GetTypeInfo<const TypedDictionary<K, V> &> { + static const Variant::Type VARIANT_TYPE = Variant::DICTIONARY; + static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE; + static inline PropertyInfo get_class_info() { + return PropertyInfo(Variant::DICTIONARY, String(), PROPERTY_HINT_DICTIONARY_TYPE, vformat("%s;%s", K::get_class_static(), V::get_class_static())); + } +}; + +// Specialization for the rest of the Variant types. + +#define MAKE_TYPED_DICTIONARY_WITH_OBJECT(m_type, m_variant_type) \ + template <typename T> \ + class TypedDictionary<T, m_type> : public Dictionary { \ + public: \ + _FORCE_INLINE_ void operator=(const Dictionary &p_dictionary) { \ + ERR_FAIL_COND_MSG(!is_same_typed(p_dictionary), "Cannot assign an dictionary with a different element type."); \ + Dictionary::operator=(p_dictionary); \ + } \ + _FORCE_INLINE_ TypedDictionary(const Variant &p_variant) : \ + TypedDictionary(Dictionary(p_variant)) { \ + } \ + _FORCE_INLINE_ TypedDictionary(const Dictionary &p_dictionary) { \ + set_typed(Variant::OBJECT, T::get_class_static(), Variant(), m_variant_type, StringName(), Variant()); \ + if (is_same_typed(p_dictionary)) { \ + Dictionary::operator=(p_dictionary); \ + } else { \ + assign(p_dictionary); \ + } \ + } \ + _FORCE_INLINE_ TypedDictionary() { \ + set_typed(Variant::OBJECT, T::get_class_static(), Variant(), m_variant_type, StringName(), Variant()); \ + } \ + }; \ + template <typename T> \ + struct GetTypeInfo<TypedDictionary<T, m_type>> { \ + static const Variant::Type VARIANT_TYPE = Variant::DICTIONARY; \ + static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE; \ + static inline PropertyInfo get_class_info() { \ + return PropertyInfo(Variant::DICTIONARY, String(), PROPERTY_HINT_DICTIONARY_TYPE, \ + vformat("%s;%s", T::get_class_static(), m_variant_type == Variant::NIL ? "Variant" : Variant::get_type_name(m_variant_type))); \ + } \ + }; \ + template <typename T> \ + struct GetTypeInfo<const TypedDictionary<T, m_type> &> { \ + static const Variant::Type VARIANT_TYPE = Variant::DICTIONARY; \ + static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE; \ + static inline PropertyInfo get_class_info() { \ + return PropertyInfo(Variant::DICTIONARY, String(), PROPERTY_HINT_DICTIONARY_TYPE, \ + vformat("%s;%s", T::get_class_static(), m_variant_type == Variant::NIL ? "Variant" : Variant::get_type_name(m_variant_type))); \ + } \ + }; \ + template <typename T> \ + class TypedDictionary<m_type, T> : public Dictionary { \ + public: \ + _FORCE_INLINE_ void operator=(const Dictionary &p_dictionary) { \ + ERR_FAIL_COND_MSG(!is_same_typed(p_dictionary), "Cannot assign an dictionary with a different element type."); \ + Dictionary::operator=(p_dictionary); \ + } \ + _FORCE_INLINE_ TypedDictionary(const Variant &p_variant) : \ + TypedDictionary(Dictionary(p_variant)) { \ + } \ + _FORCE_INLINE_ TypedDictionary(const Dictionary &p_dictionary) { \ + set_typed(m_variant_type, StringName(), Variant(), Variant::OBJECT, T::get_class_static(), Variant()); \ + if (is_same_typed(p_dictionary)) { \ + Dictionary::operator=(p_dictionary); \ + } else { \ + assign(p_dictionary); \ + } \ + } \ + _FORCE_INLINE_ TypedDictionary() { \ + set_typed(m_variant_type, StringName(), Variant(), Variant::OBJECT, T::get_class_static(), Variant()); \ + } \ + }; \ + template <typename T> \ + struct GetTypeInfo<TypedDictionary<m_type, T>> { \ + static const Variant::Type VARIANT_TYPE = Variant::DICTIONARY; \ + static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE; \ + static inline PropertyInfo get_class_info() { \ + return PropertyInfo(Variant::DICTIONARY, String(), PROPERTY_HINT_DICTIONARY_TYPE, \ + vformat("%s;%s", m_variant_type == Variant::NIL ? "Variant" : Variant::get_type_name(m_variant_type), T::get_class_static())); \ + } \ + }; \ + template <typename T> \ + struct GetTypeInfo<const TypedDictionary<m_type, T> &> { \ + static const Variant::Type VARIANT_TYPE = Variant::DICTIONARY; \ + static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE; \ + static inline PropertyInfo get_class_info() { \ + return PropertyInfo(Variant::DICTIONARY, String(), PROPERTY_HINT_DICTIONARY_TYPE, \ + vformat("%s;%s", m_variant_type == Variant::NIL ? "Variant" : Variant::get_type_name(m_variant_type), T::get_class_static())); \ + } \ + }; + +#define MAKE_TYPED_DICTIONARY_EXPANDED(m_type_key, m_variant_type_key, m_type_value, m_variant_type_value) \ + template <> \ + class TypedDictionary<m_type_key, m_type_value> : public Dictionary { \ + public: \ + _FORCE_INLINE_ void operator=(const Dictionary &p_dictionary) { \ + ERR_FAIL_COND_MSG(!is_same_typed(p_dictionary), "Cannot assign an dictionary with a different element type."); \ + Dictionary::operator=(p_dictionary); \ + } \ + _FORCE_INLINE_ TypedDictionary(const Variant &p_variant) : \ + TypedDictionary(Dictionary(p_variant)) { \ + } \ + _FORCE_INLINE_ TypedDictionary(const Dictionary &p_dictionary) { \ + set_typed(m_variant_type_key, StringName(), Variant(), m_variant_type_value, StringName(), Variant()); \ + if (is_same_typed(p_dictionary)) { \ + Dictionary::operator=(p_dictionary); \ + } else { \ + assign(p_dictionary); \ + } \ + } \ + _FORCE_INLINE_ TypedDictionary() { \ + set_typed(m_variant_type_key, StringName(), Variant(), m_variant_type_value, StringName(), Variant()); \ + } \ + }; \ + template <> \ + struct GetTypeInfo<TypedDictionary<m_type_key, m_type_value>> { \ + static const Variant::Type VARIANT_TYPE = Variant::DICTIONARY; \ + static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE; \ + static inline PropertyInfo get_class_info() { \ + return PropertyInfo(Variant::DICTIONARY, String(), PROPERTY_HINT_DICTIONARY_TYPE, \ + vformat("%s;%s", m_variant_type_key == Variant::NIL ? "Variant" : Variant::get_type_name(m_variant_type_key), \ + m_variant_type_value == Variant::NIL ? "Variant" : Variant::get_type_name(m_variant_type_value))); \ + } \ + }; \ + template <> \ + struct GetTypeInfo<const TypedDictionary<m_type_key, m_type_value> &> { \ + static const Variant::Type VARIANT_TYPE = Variant::DICTIONARY; \ + static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE; \ + static inline PropertyInfo get_class_info() { \ + return PropertyInfo(Variant::DICTIONARY, String(), PROPERTY_HINT_DICTIONARY_TYPE, \ + vformat("%s;%s", m_variant_type_key == Variant::NIL ? "Variant" : Variant::get_type_name(m_variant_type_key), \ + m_variant_type_value == Variant::NIL ? "Variant" : Variant::get_type_name(m_variant_type_value))); \ + } \ + }; + +#define MAKE_TYPED_DICTIONARY_NIL(m_type, m_variant_type) \ + MAKE_TYPED_DICTIONARY_WITH_OBJECT(m_type, m_variant_type) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, bool, Variant::BOOL) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, uint8_t, Variant::INT) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, int8_t, Variant::INT) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, uint16_t, Variant::INT) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, int16_t, Variant::INT) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, uint32_t, Variant::INT) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, int32_t, Variant::INT) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, uint64_t, Variant::INT) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, int64_t, Variant::INT) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, float, Variant::FLOAT) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, double, Variant::FLOAT) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, String, Variant::STRING) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Vector2, Variant::VECTOR2) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Vector2i, Variant::VECTOR2I) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Rect2, Variant::RECT2) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Rect2i, Variant::RECT2I) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Vector3, Variant::VECTOR3) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Vector3i, Variant::VECTOR3I) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Transform2D, Variant::TRANSFORM2D) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Plane, Variant::PLANE) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Quaternion, Variant::QUATERNION) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, AABB, Variant::AABB) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Basis, Variant::BASIS) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Transform3D, Variant::TRANSFORM3D) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Color, Variant::COLOR) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, StringName, Variant::STRING_NAME) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, NodePath, Variant::NODE_PATH) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, RID, Variant::RID) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Callable, Variant::CALLABLE) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Signal, Variant::SIGNAL) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Dictionary, Variant::DICTIONARY) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Array, Variant::ARRAY) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, PackedByteArray, Variant::PACKED_BYTE_ARRAY) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, PackedInt32Array, Variant::PACKED_INT32_ARRAY) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, PackedInt64Array, Variant::PACKED_INT64_ARRAY) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, PackedFloat32Array, Variant::PACKED_FLOAT32_ARRAY) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, PackedFloat64Array, Variant::PACKED_FLOAT64_ARRAY) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, PackedStringArray, Variant::PACKED_STRING_ARRAY) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, PackedVector2Array, Variant::PACKED_VECTOR2_ARRAY) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, PackedVector3Array, Variant::PACKED_VECTOR3_ARRAY) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, PackedColorArray, Variant::PACKED_COLOR_ARRAY) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, PackedVector4Array, Variant::PACKED_VECTOR4_ARRAY) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, IPAddress, Variant::STRING) + +#define MAKE_TYPED_DICTIONARY(m_type, m_variant_type) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Variant, Variant::NIL) \ + MAKE_TYPED_DICTIONARY_NIL(m_type, m_variant_type) + +MAKE_TYPED_DICTIONARY_NIL(Variant, Variant::NIL) +MAKE_TYPED_DICTIONARY(bool, Variant::BOOL) +MAKE_TYPED_DICTIONARY(uint8_t, Variant::INT) +MAKE_TYPED_DICTIONARY(int8_t, Variant::INT) +MAKE_TYPED_DICTIONARY(uint16_t, Variant::INT) +MAKE_TYPED_DICTIONARY(int16_t, Variant::INT) +MAKE_TYPED_DICTIONARY(uint32_t, Variant::INT) +MAKE_TYPED_DICTIONARY(int32_t, Variant::INT) +MAKE_TYPED_DICTIONARY(uint64_t, Variant::INT) +MAKE_TYPED_DICTIONARY(int64_t, Variant::INT) +MAKE_TYPED_DICTIONARY(float, Variant::FLOAT) +MAKE_TYPED_DICTIONARY(double, Variant::FLOAT) +MAKE_TYPED_DICTIONARY(String, Variant::STRING) +MAKE_TYPED_DICTIONARY(Vector2, Variant::VECTOR2) +MAKE_TYPED_DICTIONARY(Vector2i, Variant::VECTOR2I) +MAKE_TYPED_DICTIONARY(Rect2, Variant::RECT2) +MAKE_TYPED_DICTIONARY(Rect2i, Variant::RECT2I) +MAKE_TYPED_DICTIONARY(Vector3, Variant::VECTOR3) +MAKE_TYPED_DICTIONARY(Vector3i, Variant::VECTOR3I) +MAKE_TYPED_DICTIONARY(Transform2D, Variant::TRANSFORM2D) +MAKE_TYPED_DICTIONARY(Plane, Variant::PLANE) +MAKE_TYPED_DICTIONARY(Quaternion, Variant::QUATERNION) +MAKE_TYPED_DICTIONARY(AABB, Variant::AABB) +MAKE_TYPED_DICTIONARY(Basis, Variant::BASIS) +MAKE_TYPED_DICTIONARY(Transform3D, Variant::TRANSFORM3D) +MAKE_TYPED_DICTIONARY(Color, Variant::COLOR) +MAKE_TYPED_DICTIONARY(StringName, Variant::STRING_NAME) +MAKE_TYPED_DICTIONARY(NodePath, Variant::NODE_PATH) +MAKE_TYPED_DICTIONARY(RID, Variant::RID) +MAKE_TYPED_DICTIONARY(Callable, Variant::CALLABLE) +MAKE_TYPED_DICTIONARY(Signal, Variant::SIGNAL) +MAKE_TYPED_DICTIONARY(Dictionary, Variant::DICTIONARY) +MAKE_TYPED_DICTIONARY(Array, Variant::ARRAY) +MAKE_TYPED_DICTIONARY(PackedByteArray, Variant::PACKED_BYTE_ARRAY) +MAKE_TYPED_DICTIONARY(PackedInt32Array, Variant::PACKED_INT32_ARRAY) +MAKE_TYPED_DICTIONARY(PackedInt64Array, Variant::PACKED_INT64_ARRAY) +MAKE_TYPED_DICTIONARY(PackedFloat32Array, Variant::PACKED_FLOAT32_ARRAY) +MAKE_TYPED_DICTIONARY(PackedFloat64Array, Variant::PACKED_FLOAT64_ARRAY) +MAKE_TYPED_DICTIONARY(PackedStringArray, Variant::PACKED_STRING_ARRAY) +MAKE_TYPED_DICTIONARY(PackedVector2Array, Variant::PACKED_VECTOR2_ARRAY) +MAKE_TYPED_DICTIONARY(PackedVector3Array, Variant::PACKED_VECTOR3_ARRAY) +MAKE_TYPED_DICTIONARY(PackedColorArray, Variant::PACKED_COLOR_ARRAY) +MAKE_TYPED_DICTIONARY(PackedVector4Array, Variant::PACKED_VECTOR4_ARRAY) +MAKE_TYPED_DICTIONARY(IPAddress, Variant::STRING) + +#undef MAKE_TYPED_DICTIONARY +#undef MAKE_TYPED_DICTIONARY_NIL +#undef MAKE_TYPED_DICTIONARY_EXPANDED +#undef MAKE_TYPED_DICTIONARY_WITH_OBJECT + +#endif // TYPED_DICTIONARY_H diff --git a/core/variant/variant.cpp b/core/variant/variant.cpp index 30a8facd67..186643b024 100644 --- a/core/variant/variant.cpp +++ b/core/variant/variant.cpp @@ -951,7 +951,7 @@ bool Variant::is_zero() const { return *reinterpret_cast<const ::RID *>(_data._mem) == ::RID(); } case OBJECT: { - return get_validated_object() == nullptr; + return _get_obj().obj == nullptr; } case CALLABLE: { return reinterpret_cast<const Callable *>(_data._mem)->is_null(); @@ -1072,13 +1072,6 @@ bool Variant::is_null() const { } } -bool Variant::initialize_ref(Object *p_object) { - RefCounted *ref_counted = const_cast<RefCounted *>(static_cast<const RefCounted *>(p_object)); - if (!ref_counted->init_ref()) { - return false; - } - return true; -} void Variant::reference(const Variant &p_variant) { switch (type) { case NIL: @@ -2120,7 +2113,7 @@ Variant::operator ::RID() const { } #endif Callable::CallError ce; - Variant ret = _get_obj().obj->callp(CoreStringName(get_rid), nullptr, 0, ce); + const Variant ret = _get_obj().obj->callp(CoreStringName(get_rid), nullptr, 0, ce); if (ce.error == Callable::CallError::CALL_OK && ret.get_type() == Variant::RID) { return ret; } diff --git a/core/variant/variant.h b/core/variant/variant.h index f352af24da..d4e4b330cd 100644 --- a/core/variant/variant.h +++ b/core/variant/variant.h @@ -254,7 +254,6 @@ private: } _data alignas(8); void reference(const Variant &p_variant); - static bool initialize_ref(Object *p_object); void _clear_internal(); @@ -857,7 +856,7 @@ String vformat(const String &p_text, const VarArgs... p_args) { bool error = false; String fmt = p_text.sprintf(args_array, &error); - ERR_FAIL_COND_V_MSG(error, String(), fmt); + ERR_FAIL_COND_V_MSG(error, String(), String("Formatting error in string \"") + p_text + "\": " + fmt + "."); return fmt; } diff --git a/core/variant/variant_call.cpp b/core/variant/variant_call.cpp index 5e402937cf..2da94de875 100644 --- a/core/variant/variant_call.cpp +++ b/core/variant/variant_call.cpp @@ -1724,6 +1724,8 @@ static void _register_variant_builtin_methods_string() { bind_string_method(validate_node_name, sarray(), varray()); bind_string_method(validate_filename, sarray(), varray()); + bind_string_method(is_valid_ascii_identifier, sarray(), varray()); + bind_string_method(is_valid_unicode_identifier, sarray(), varray()); bind_string_method(is_valid_identifier, sarray(), varray()); bind_string_method(is_valid_int, sarray(), varray()); bind_string_method(is_valid_float, sarray(), varray()); @@ -1849,6 +1851,7 @@ static void _register_variant_builtin_methods_math() { bind_method(Rect2, intersection, sarray("b"), varray()); bind_method(Rect2, merge, sarray("b"), varray()); bind_method(Rect2, expand, sarray("to"), varray()); + bind_method(Rect2, get_support, sarray("direction"), varray()); bind_method(Rect2, grow, sarray("amount"), varray()); bind_methodv(Rect2, grow_side, &Rect2::grow_side_bind, sarray("side", "amount"), varray()); bind_method(Rect2, grow_individual, sarray("left", "top", "right", "bottom"), varray()); @@ -2185,7 +2188,7 @@ static void _register_variant_builtin_methods_misc() { bind_method(AABB, merge, sarray("with"), varray()); bind_method(AABB, expand, sarray("to_point"), varray()); bind_method(AABB, grow, sarray("by"), varray()); - bind_method(AABB, get_support, sarray("dir"), varray()); + bind_method(AABB, get_support, sarray("direction"), varray()); bind_method(AABB, get_longest_axis, sarray(), varray()); bind_method(AABB, get_longest_axis_index, sarray(), varray()); bind_method(AABB, get_longest_axis_size, sarray(), varray()); @@ -2251,6 +2254,7 @@ static void _register_variant_builtin_methods_misc() { bind_method(Dictionary, size, sarray(), varray()); bind_method(Dictionary, is_empty, sarray(), varray()); bind_method(Dictionary, clear, sarray(), varray()); + bind_method(Dictionary, assign, sarray("dictionary"), varray()); bind_method(Dictionary, merge, sarray("dictionary", "overwrite"), varray(false)); bind_method(Dictionary, merged, sarray("dictionary", "overwrite"), varray(false)); bind_method(Dictionary, has, sarray("key"), varray()); @@ -2263,6 +2267,18 @@ static void _register_variant_builtin_methods_misc() { bind_method(Dictionary, duplicate, sarray("deep"), varray(false)); bind_method(Dictionary, get, sarray("key", "default"), varray(Variant())); bind_method(Dictionary, get_or_add, sarray("key", "default"), varray(Variant())); + bind_method(Dictionary, is_typed, sarray(), varray()); + bind_method(Dictionary, is_typed_key, sarray(), varray()); + bind_method(Dictionary, is_typed_value, sarray(), varray()); + bind_method(Dictionary, is_same_typed, sarray("dictionary"), varray()); + bind_method(Dictionary, is_same_typed_key, sarray("dictionary"), varray()); + bind_method(Dictionary, is_same_typed_value, sarray("dictionary"), varray()); + bind_method(Dictionary, get_typed_key_builtin, sarray(), varray()); + bind_method(Dictionary, get_typed_value_builtin, sarray(), varray()); + bind_method(Dictionary, get_typed_key_class_name, sarray(), varray()); + bind_method(Dictionary, get_typed_value_class_name, sarray(), varray()); + bind_method(Dictionary, get_typed_key_script, sarray(), varray()); + bind_method(Dictionary, get_typed_value_script, sarray(), varray()); bind_method(Dictionary, make_read_only, sarray(), varray()); bind_method(Dictionary, is_read_only, sarray(), varray()); bind_method(Dictionary, recursive_equal, sarray("dictionary", "recursion_count"), varray()); diff --git a/core/variant/variant_construct.cpp b/core/variant/variant_construct.cpp index 1edae407c2..fb75a874e7 100644 --- a/core/variant/variant_construct.cpp +++ b/core/variant/variant_construct.cpp @@ -198,6 +198,7 @@ void Variant::_register_variant_constructors() { add_constructor<VariantConstructNoArgs<Dictionary>>(sarray()); add_constructor<VariantConstructor<Dictionary, Dictionary>>(sarray("from")); + add_constructor<VariantConstructorTypedDictionary>(sarray("base", "key_type", "key_class_name", "key_script", "value_type", "value_class_name", "value_script")); add_constructor<VariantConstructNoArgs<Array>>(sarray()); add_constructor<VariantConstructor<Array, Array>>(sarray("from")); diff --git a/core/variant/variant_construct.h b/core/variant/variant_construct.h index b824044b82..68210a9451 100644 --- a/core/variant/variant_construct.h +++ b/core/variant/variant_construct.h @@ -153,11 +153,14 @@ public: class VariantConstructorObject { public: static void construct(Variant &r_ret, const Variant **p_args, Callable::CallError &r_error) { - VariantInternal::clear(&r_ret); if (p_args[0]->get_type() == Variant::NIL) { + VariantInternal::clear(&r_ret); + VariantTypeChanger<Object *>::change(&r_ret); VariantInternal::object_assign_null(&r_ret); r_error.error = Callable::CallError::CALL_OK; } else if (p_args[0]->get_type() == Variant::OBJECT) { + VariantInternal::clear(&r_ret); + VariantTypeChanger<Object *>::change(&r_ret); VariantInternal::object_assign(&r_ret, p_args[0]); r_error.error = Callable::CallError::CALL_OK; } else { @@ -169,6 +172,7 @@ public: static inline void validated_construct(Variant *r_ret, const Variant **p_args) { VariantInternal::clear(r_ret); + VariantTypeChanger<Object *>::change(r_ret); VariantInternal::object_assign(r_ret, p_args[0]); } static void ptr_construct(void *base, const void **p_args) { @@ -198,11 +202,13 @@ public: } VariantInternal::clear(&r_ret); + VariantTypeChanger<Object *>::change(&r_ret); VariantInternal::object_assign_null(&r_ret); } static inline void validated_construct(Variant *r_ret, const Variant **p_args) { VariantInternal::clear(r_ret); + VariantTypeChanger<Object *>::change(r_ret); VariantInternal::object_assign_null(r_ret); } static void ptr_construct(void *base, const void **p_args) { @@ -226,7 +232,7 @@ template <typename T> class VariantConstructorFromString { public: static void construct(Variant &r_ret, const Variant **p_args, Callable::CallError &r_error) { - if (p_args[0]->get_type() != Variant::STRING) { + if (!p_args[0]->is_string()) { r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = 0; r_error.expected = Variant::STRING; @@ -234,7 +240,7 @@ public: } VariantTypeChanger<T>::change(&r_ret); - const String &src_str = *VariantGetInternalPtr<String>::get_ptr(p_args[0]); + const String src_str = *p_args[0]; if (r_ret.get_type() == Variant::Type::INT) { r_ret = src_str.to_int(); @@ -394,6 +400,112 @@ public: } }; +class VariantConstructorTypedDictionary { +public: + static void construct(Variant &r_ret, const Variant **p_args, Callable::CallError &r_error) { + if (p_args[0]->get_type() != Variant::DICTIONARY) { + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 0; + r_error.expected = Variant::DICTIONARY; + return; + } + + if (p_args[1]->get_type() != Variant::INT) { + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 1; + r_error.expected = Variant::INT; + return; + } + + if (p_args[2]->get_type() != Variant::STRING_NAME) { + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 2; + r_error.expected = Variant::STRING_NAME; + return; + } + + if (p_args[4]->get_type() != Variant::INT) { + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 4; + r_error.expected = Variant::INT; + return; + } + + if (p_args[5]->get_type() != Variant::STRING_NAME) { + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 5; + r_error.expected = Variant::STRING_NAME; + return; + } + + const Dictionary &base_dict = *VariantGetInternalPtr<Dictionary>::get_ptr(p_args[0]); + const uint32_t key_type = p_args[1]->operator uint32_t(); + const StringName &key_class_name = *VariantGetInternalPtr<StringName>::get_ptr(p_args[2]); + const uint32_t value_type = p_args[4]->operator uint32_t(); + const StringName &value_class_name = *VariantGetInternalPtr<StringName>::get_ptr(p_args[5]); + r_ret = Dictionary(base_dict, key_type, key_class_name, *p_args[3], value_type, value_class_name, *p_args[6]); + } + + static inline void validated_construct(Variant *r_ret, const Variant **p_args) { + const Dictionary &base_dict = *VariantGetInternalPtr<Dictionary>::get_ptr(p_args[0]); + const uint32_t key_type = p_args[1]->operator uint32_t(); + const StringName &key_class_name = *VariantGetInternalPtr<StringName>::get_ptr(p_args[2]); + const uint32_t value_type = p_args[4]->operator uint32_t(); + const StringName &value_class_name = *VariantGetInternalPtr<StringName>::get_ptr(p_args[5]); + *r_ret = Dictionary(base_dict, key_type, key_class_name, *p_args[3], value_type, value_class_name, *p_args[6]); + } + + static void ptr_construct(void *base, const void **p_args) { + const Dictionary &base_dict = PtrToArg<Dictionary>::convert(p_args[0]); + const uint32_t key_type = PtrToArg<uint32_t>::convert(p_args[1]); + const StringName &key_class_name = PtrToArg<StringName>::convert(p_args[2]); + const Variant &key_script = PtrToArg<Variant>::convert(p_args[3]); + const uint32_t value_type = PtrToArg<uint32_t>::convert(p_args[4]); + const StringName &value_class_name = PtrToArg<StringName>::convert(p_args[5]); + const Variant &value_script = PtrToArg<Variant>::convert(p_args[6]); + Dictionary dst_arr = Dictionary(base_dict, key_type, key_class_name, key_script, value_type, value_class_name, value_script); + + PtrConstruct<Dictionary>::construct(dst_arr, base); + } + + static int get_argument_count() { + return 7; + } + + static Variant::Type get_argument_type(int p_arg) { + switch (p_arg) { + case 0: { + return Variant::DICTIONARY; + } break; + case 1: { + return Variant::INT; + } break; + case 2: { + return Variant::STRING_NAME; + } break; + case 3: { + return Variant::NIL; + } break; + case 4: { + return Variant::INT; + } break; + case 5: { + return Variant::STRING_NAME; + } break; + case 6: { + return Variant::NIL; + } break; + default: { + return Variant::NIL; + } break; + } + } + + static Variant::Type get_base_type() { + return Variant::DICTIONARY; + } +}; + class VariantConstructorTypedArray { public: static void construct(Variant &r_ret, const Variant **p_args, Callable::CallError &r_error) { @@ -411,7 +523,7 @@ public: return; } - if (p_args[2]->get_type() != Variant::STRING_NAME) { + if (!p_args[2]->is_string()) { r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = 2; r_error.expected = Variant::STRING_NAME; @@ -420,8 +532,7 @@ public: const Array &base_arr = *VariantGetInternalPtr<Array>::get_ptr(p_args[0]); const uint32_t type = p_args[1]->operator uint32_t(); - const StringName &class_name = *VariantGetInternalPtr<StringName>::get_ptr(p_args[2]); - r_ret = Array(base_arr, type, class_name, *p_args[3]); + r_ret = Array(base_arr, type, *p_args[2], *p_args[3]); } static inline void validated_construct(Variant *r_ret, const Variant **p_args) { diff --git a/core/variant/variant_internal.h b/core/variant/variant_internal.h index c52ab6917b..58a45c0a1f 100644 --- a/core/variant/variant_internal.h +++ b/core/variant/variant_internal.h @@ -125,10 +125,6 @@ public: } } - _FORCE_INLINE_ static bool initialize_ref(Object *object) { - return Variant::initialize_ref(object); - } - // Atomic types. _FORCE_INLINE_ static bool *get_bool(Variant *v) { return &v->_data._bool; } _FORCE_INLINE_ static const bool *get_bool(const Variant *v) { return &v->_data._bool; } diff --git a/core/variant/variant_op.h b/core/variant/variant_op.h index 0b94d79a97..ac39a4135f 100644 --- a/core/variant/variant_op.h +++ b/core/variant/variant_op.h @@ -548,14 +548,14 @@ public: class OperatorEvaluatorEqualObject { public: static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) { - const ObjectID &a = VariantInternal::get_object_id(&p_left); - const ObjectID &b = VariantInternal::get_object_id(&p_right); + const Object *a = p_left.get_validated_object(); + const Object *b = p_right.get_validated_object(); *r_ret = a == b; r_valid = true; } static inline void validated_evaluate(const Variant *left, const Variant *right, Variant *r_ret) { - const ObjectID &a = VariantInternal::get_object_id(left); - const ObjectID &b = VariantInternal::get_object_id(right); + const Object *a = left->get_validated_object(); + const Object *b = right->get_validated_object(); *VariantGetInternalPtr<bool>::get_ptr(r_ret) = a == b; } static void ptr_evaluate(const void *left, const void *right, void *r_ret) { @@ -567,12 +567,12 @@ public: class OperatorEvaluatorEqualObjectNil { public: static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) { - const Object *a = p_left.operator Object *(); + const Object *a = p_left.get_validated_object(); *r_ret = a == nullptr; r_valid = true; } static inline void validated_evaluate(const Variant *left, const Variant *right, Variant *r_ret) { - const Object *a = left->operator Object *(); + const Object *a = left->get_validated_object(); *VariantGetInternalPtr<bool>::get_ptr(r_ret) = a == nullptr; } static void ptr_evaluate(const void *left, const void *right, void *r_ret) { @@ -584,12 +584,12 @@ public: class OperatorEvaluatorEqualNilObject { public: static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) { - const Object *b = p_right.operator Object *(); + const Object *b = p_right.get_validated_object(); *r_ret = nullptr == b; r_valid = true; } static inline void validated_evaluate(const Variant *left, const Variant *right, Variant *r_ret) { - const Object *b = right->operator Object *(); + const Object *b = right->get_validated_object(); *VariantGetInternalPtr<bool>::get_ptr(r_ret) = nullptr == b; } static void ptr_evaluate(const void *left, const void *right, void *r_ret) { @@ -619,14 +619,14 @@ public: class OperatorEvaluatorNotEqualObject { public: static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) { - const ObjectID &a = VariantInternal::get_object_id(&p_left); - const ObjectID &b = VariantInternal::get_object_id(&p_right); + Object *a = p_left.get_validated_object(); + Object *b = p_right.get_validated_object(); *r_ret = a != b; r_valid = true; } static inline void validated_evaluate(const Variant *left, const Variant *right, Variant *r_ret) { - const ObjectID &a = VariantInternal::get_object_id(left); - const ObjectID &b = VariantInternal::get_object_id(right); + Object *a = left->get_validated_object(); + Object *b = right->get_validated_object(); *VariantGetInternalPtr<bool>::get_ptr(r_ret) = a != b; } static void ptr_evaluate(const void *left, const void *right, void *r_ret) { @@ -638,12 +638,12 @@ public: class OperatorEvaluatorNotEqualObjectNil { public: static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) { - Object *a = p_left.operator Object *(); + Object *a = p_left.get_validated_object(); *r_ret = a != nullptr; r_valid = true; } static inline void validated_evaluate(const Variant *left, const Variant *right, Variant *r_ret) { - Object *a = left->operator Object *(); + Object *a = left->get_validated_object(); *VariantGetInternalPtr<bool>::get_ptr(r_ret) = a != nullptr; } static void ptr_evaluate(const void *left, const void *right, void *r_ret) { @@ -655,12 +655,12 @@ public: class OperatorEvaluatorNotEqualNilObject { public: static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) { - Object *b = p_right.operator Object *(); + Object *b = p_right.get_validated_object(); *r_ret = nullptr != b; r_valid = true; } static inline void validated_evaluate(const Variant *left, const Variant *right, Variant *r_ret) { - Object *b = right->operator Object *(); + Object *b = right->get_validated_object(); *VariantGetInternalPtr<bool>::get_ptr(r_ret) = nullptr != b; } static void ptr_evaluate(const void *left, const void *right, void *r_ret) { diff --git a/core/variant/variant_parser.cpp b/core/variant/variant_parser.cpp index 9a0dd712ed..f5f96456d3 100644 --- a/core/variant/variant_parser.cpp +++ b/core/variant/variant_parser.cpp @@ -1140,6 +1140,146 @@ Error VariantParser::parse_value(Token &token, Variant &value, Stream *p_stream, return ERR_PARSE_ERROR; } } + } else if (id == "Dictionary") { + Error err = OK; + + get_token(p_stream, token, line, r_err_str); + if (token.type != TK_BRACKET_OPEN) { + r_err_str = "Expected '['"; + return ERR_PARSE_ERROR; + } + + get_token(p_stream, token, line, r_err_str); + if (token.type != TK_IDENTIFIER) { + r_err_str = "Expected type identifier for key"; + return ERR_PARSE_ERROR; + } + + static HashMap<StringName, Variant::Type> builtin_types; + if (builtin_types.is_empty()) { + for (int i = 1; i < Variant::VARIANT_MAX; i++) { + builtin_types[Variant::get_type_name((Variant::Type)i)] = (Variant::Type)i; + } + } + + Dictionary dict; + Variant::Type key_type = Variant::NIL; + StringName key_class_name; + Variant key_script; + bool got_comma_token = false; + if (builtin_types.has(token.value)) { + key_type = builtin_types.get(token.value); + } else if (token.value == "Resource" || token.value == "SubResource" || token.value == "ExtResource") { + Variant resource; + err = parse_value(token, resource, p_stream, line, r_err_str, p_res_parser); + if (err) { + if (token.value == "Resource" && err == ERR_PARSE_ERROR && r_err_str == "Expected '('" && token.type == TK_COMMA) { + err = OK; + r_err_str = String(); + key_type = Variant::OBJECT; + key_class_name = token.value; + got_comma_token = true; + } else { + return err; + } + } else { + Ref<Script> script = resource; + if (script.is_valid() && script->is_valid()) { + key_type = Variant::OBJECT; + key_class_name = script->get_instance_base_type(); + key_script = script; + } + } + } else if (ClassDB::class_exists(token.value)) { + key_type = Variant::OBJECT; + key_class_name = token.value; + } + + if (!got_comma_token) { + get_token(p_stream, token, line, r_err_str); + if (token.type != TK_COMMA) { + r_err_str = "Expected ',' after key type"; + return ERR_PARSE_ERROR; + } + } + + get_token(p_stream, token, line, r_err_str); + if (token.type != TK_IDENTIFIER) { + r_err_str = "Expected type identifier for value"; + return ERR_PARSE_ERROR; + } + + Variant::Type value_type = Variant::NIL; + StringName value_class_name; + Variant value_script; + bool got_bracket_token = false; + if (builtin_types.has(token.value)) { + value_type = builtin_types.get(token.value); + } else if (token.value == "Resource" || token.value == "SubResource" || token.value == "ExtResource") { + Variant resource; + err = parse_value(token, resource, p_stream, line, r_err_str, p_res_parser); + if (err) { + if (token.value == "Resource" && err == ERR_PARSE_ERROR && r_err_str == "Expected '('" && token.type == TK_BRACKET_CLOSE) { + err = OK; + r_err_str = String(); + value_type = Variant::OBJECT; + value_class_name = token.value; + got_comma_token = true; + } else { + return err; + } + } else { + Ref<Script> script = resource; + if (script.is_valid() && script->is_valid()) { + value_type = Variant::OBJECT; + value_class_name = script->get_instance_base_type(); + value_script = script; + } + } + } else if (ClassDB::class_exists(token.value)) { + value_type = Variant::OBJECT; + value_class_name = token.value; + } + + if (key_type != Variant::NIL || value_type != Variant::NIL) { + dict.set_typed(key_type, key_class_name, key_script, value_type, value_class_name, value_script); + } + + if (!got_bracket_token) { + get_token(p_stream, token, line, r_err_str); + if (token.type != TK_BRACKET_CLOSE) { + r_err_str = "Expected ']'"; + return ERR_PARSE_ERROR; + } + } + + get_token(p_stream, token, line, r_err_str); + if (token.type != TK_PARENTHESIS_OPEN) { + r_err_str = "Expected '('"; + return ERR_PARSE_ERROR; + } + + get_token(p_stream, token, line, r_err_str); + if (token.type != TK_CURLY_BRACKET_OPEN) { + r_err_str = "Expected '{'"; + return ERR_PARSE_ERROR; + } + + Dictionary values; + err = _parse_dictionary(values, p_stream, line, r_err_str, p_res_parser); + if (err) { + return err; + } + + get_token(p_stream, token, line, r_err_str); + if (token.type != TK_PARENTHESIS_CLOSE) { + r_err_str = "Expected ')'"; + return ERR_PARSE_ERROR; + } + + dict.assign(values); + + value = dict; } else if (id == "Array") { Error err = OK; @@ -2036,40 +2176,109 @@ Error VariantWriter::write(const Variant &p_variant, StoreStringFunc p_store_str case Variant::DICTIONARY: { Dictionary dict = p_variant; + + if (dict.is_typed()) { + p_store_string_func(p_store_string_ud, "Dictionary["); + + Variant::Type key_builtin_type = (Variant::Type)dict.get_typed_key_builtin(); + StringName key_class_name = dict.get_typed_key_class_name(); + Ref<Script> key_script = dict.get_typed_key_script(); + + if (key_script.is_valid()) { + String resource_text; + if (p_encode_res_func) { + resource_text = p_encode_res_func(p_encode_res_ud, key_script); + } + if (resource_text.is_empty() && key_script->get_path().is_resource_file()) { + resource_text = "Resource(\"" + key_script->get_path() + "\")"; + } + + if (!resource_text.is_empty()) { + p_store_string_func(p_store_string_ud, resource_text); + } else { + ERR_PRINT("Failed to encode a path to a custom script for a dictionary key type."); + p_store_string_func(p_store_string_ud, key_class_name); + } + } else if (key_class_name != StringName()) { + p_store_string_func(p_store_string_ud, key_class_name); + } else if (key_builtin_type == Variant::NIL) { + p_store_string_func(p_store_string_ud, "Variant"); + } else { + p_store_string_func(p_store_string_ud, Variant::get_type_name(key_builtin_type)); + } + + p_store_string_func(p_store_string_ud, ", "); + + Variant::Type value_builtin_type = (Variant::Type)dict.get_typed_value_builtin(); + StringName value_class_name = dict.get_typed_value_class_name(); + Ref<Script> value_script = dict.get_typed_value_script(); + + if (value_script.is_valid()) { + String resource_text; + if (p_encode_res_func) { + resource_text = p_encode_res_func(p_encode_res_ud, value_script); + } + if (resource_text.is_empty() && value_script->get_path().is_resource_file()) { + resource_text = "Resource(\"" + value_script->get_path() + "\")"; + } + + if (!resource_text.is_empty()) { + p_store_string_func(p_store_string_ud, resource_text); + } else { + ERR_PRINT("Failed to encode a path to a custom script for a dictionary value type."); + p_store_string_func(p_store_string_ud, value_class_name); + } + } else if (value_class_name != StringName()) { + p_store_string_func(p_store_string_ud, value_class_name); + } else if (value_builtin_type == Variant::NIL) { + p_store_string_func(p_store_string_ud, "Variant"); + } else { + p_store_string_func(p_store_string_ud, Variant::get_type_name(value_builtin_type)); + } + + p_store_string_func(p_store_string_ud, "]("); + } + if (unlikely(p_recursion_count > MAX_RECURSION)) { ERR_PRINT("Max recursion reached"); p_store_string_func(p_store_string_ud, "{}"); } else { - p_recursion_count++; - List<Variant> keys; dict.get_key_list(&keys); keys.sort(); - if (keys.is_empty()) { // Avoid unnecessary line break. + if (keys.is_empty()) { + // Avoid unnecessary line break. p_store_string_func(p_store_string_ud, "{}"); - break; - } + } else { + p_recursion_count++; - p_store_string_func(p_store_string_ud, "{\n"); - for (List<Variant>::Element *E = keys.front(); E; E = E->next()) { - write(E->get(), p_store_string_func, p_store_string_ud, p_encode_res_func, p_encode_res_ud, p_recursion_count, p_compat); - p_store_string_func(p_store_string_ud, ": "); - write(dict[E->get()], p_store_string_func, p_store_string_ud, p_encode_res_func, p_encode_res_ud, p_recursion_count, p_compat); - if (E->next()) { - p_store_string_func(p_store_string_ud, ",\n"); - } else { - p_store_string_func(p_store_string_ud, "\n"); + p_store_string_func(p_store_string_ud, "{\n"); + + for (List<Variant>::Element *E = keys.front(); E; E = E->next()) { + write(E->get(), p_store_string_func, p_store_string_ud, p_encode_res_func, p_encode_res_ud, p_recursion_count, p_compat); + p_store_string_func(p_store_string_ud, ": "); + write(dict[E->get()], p_store_string_func, p_store_string_ud, p_encode_res_func, p_encode_res_ud, p_recursion_count, p_compat); + if (E->next()) { + p_store_string_func(p_store_string_ud, ",\n"); + } else { + p_store_string_func(p_store_string_ud, "\n"); + } } + + p_store_string_func(p_store_string_ud, "}"); } + } - p_store_string_func(p_store_string_ud, "}"); + if (dict.is_typed()) { + p_store_string_func(p_store_string_ud, ")"); } } break; case Variant::ARRAY: { Array array = p_variant; - if (array.get_typed_builtin() != Variant::NIL) { + + if (array.is_typed()) { p_store_string_func(p_store_string_ud, "Array["); Variant::Type builtin_type = (Variant::Type)array.get_typed_builtin(); @@ -2107,6 +2316,7 @@ Error VariantWriter::write(const Variant &p_variant, StoreStringFunc p_store_str p_recursion_count++; p_store_string_func(p_store_string_ud, "["); + bool first = true; for (const Variant &var : array) { if (first) { @@ -2120,7 +2330,7 @@ Error VariantWriter::write(const Variant &p_variant, StoreStringFunc p_store_str p_store_string_func(p_store_string_ud, "]"); } - if (array.get_typed_builtin() != Variant::NIL) { + if (array.is_typed()) { p_store_string_func(p_store_string_ud, ")"); } } break; diff --git a/core/variant/variant_setget.cpp b/core/variant/variant_setget.cpp index 48176163a1..b60ff83cf1 100644 --- a/core/variant/variant_setget.cpp +++ b/core/variant/variant_setget.cpp @@ -703,6 +703,50 @@ struct VariantIndexedSetGet_Array { static uint64_t get_indexed_size(const Variant *base) { return 0; } }; +struct VariantIndexedSetGet_Dictionary { + static void get(const Variant *base, int64_t index, Variant *value, bool *oob) { + const Variant *ptr = VariantGetInternalPtr<Dictionary>::get_ptr(base)->getptr(index); + if (!ptr) { + *oob = true; + return; + } + *value = *ptr; + *oob = false; + } + static void ptr_get(const void *base, int64_t index, void *member) { + // Avoid ptrconvert for performance. + const Dictionary &v = *reinterpret_cast<const Dictionary *>(base); + const Variant *ptr = v.getptr(index); + NULL_TEST(ptr); + 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; + } + 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; + } + static void ptr_set(void *base, int64_t index, const void *member) { + Dictionary &v = *reinterpret_cast<Dictionary *>(base); + v[index] = PtrToArg<Variant>::convert(member); + } + static Variant::Type get_index_type() { return Variant::NIL; } + static uint32_t get_index_usage() { return PROPERTY_USAGE_DEFAULT; } + static uint64_t get_indexed_size(const Variant *base) { return VariantGetInternalPtr<Dictionary>::get_ptr(base)->size(); } +}; + struct VariantIndexedSetGet_String { static void get(const Variant *base, int64_t index, Variant *value, bool *oob) { int64_t length = VariantGetInternalPtr<String>::get_ptr(base)->length(); @@ -789,51 +833,6 @@ struct VariantIndexedSetGet_String { static uint64_t get_indexed_size(const Variant *base) { return VariantInternal::get_string(base)->length(); } }; -#define INDEXED_SETGET_STRUCT_DICT(m_base_type) \ - struct VariantIndexedSetGet_##m_base_type { \ - static void get(const Variant *base, int64_t index, Variant *value, bool *oob) { \ - const Variant *ptr = VariantGetInternalPtr<m_base_type>::get_ptr(base)->getptr(index); \ - if (!ptr) { \ - *oob = true; \ - return; \ - } \ - *value = *ptr; \ - *oob = false; \ - } \ - static void ptr_get(const void *base, int64_t index, void *member) { \ - /* avoid ptrconvert for performance*/ \ - const m_base_type &v = *reinterpret_cast<const m_base_type *>(base); \ - const Variant *ptr = v.getptr(index); \ - NULL_TEST(ptr); \ - PtrToArg<Variant>::encode(*ptr, member); \ - } \ - static void set(Variant *base, int64_t index, const Variant *value, bool *valid, bool *oob) { \ - if (VariantGetInternalPtr<m_base_type>::get_ptr(base)->is_read_only()) { \ - *valid = false; \ - *oob = true; \ - return; \ - } \ - (*VariantGetInternalPtr<m_base_type>::get_ptr(base))[index] = *value; \ - *oob = false; \ - *valid = true; \ - } \ - static void validated_set(Variant *base, int64_t index, const Variant *value, bool *oob) { \ - if (VariantGetInternalPtr<m_base_type>::get_ptr(base)->is_read_only()) { \ - *oob = true; \ - return; \ - } \ - (*VariantGetInternalPtr<m_base_type>::get_ptr(base))[index] = *value; \ - *oob = false; \ - } \ - static void ptr_set(void *base, int64_t index, const void *member) { \ - m_base_type &v = *reinterpret_cast<m_base_type *>(base); \ - v[index] = PtrToArg<Variant>::convert(member); \ - } \ - static Variant::Type get_index_type() { return Variant::NIL; } \ - static uint32_t get_index_usage() { return PROPERTY_USAGE_DEFAULT; } \ - static uint64_t get_indexed_size(const Variant *base) { return VariantGetInternalPtr<m_base_type>::get_ptr(base)->size(); } \ - }; - INDEXED_SETGET_STRUCT_BULTIN_NUMERIC(Vector2, double, real_t, 2) INDEXED_SETGET_STRUCT_BULTIN_NUMERIC(Vector2i, int64_t, int32_t, 2) INDEXED_SETGET_STRUCT_BULTIN_NUMERIC(Vector3, double, real_t, 3) @@ -858,8 +857,6 @@ INDEXED_SETGET_STRUCT_TYPED(PackedStringArray, String) INDEXED_SETGET_STRUCT_TYPED(PackedColorArray, Color) INDEXED_SETGET_STRUCT_TYPED(PackedVector4Array, Vector4) -INDEXED_SETGET_STRUCT_DICT(Dictionary) - struct VariantIndexedSetterGetterInfo { void (*setter)(Variant *base, int64_t index, const Variant *value, bool *valid, bool *oob) = nullptr; void (*getter)(const Variant *base, int64_t index, Variant *value, bool *oob) = nullptr; @@ -1288,8 +1285,8 @@ void Variant::get_property_list(List<PropertyInfo> *p_list) const { List<Variant> keys; dic->get_key_list(&keys); for (const Variant &E : keys) { - if (E.get_type() == Variant::STRING) { - p_list->push_back(PropertyInfo(Variant::STRING, E)); + if (E.is_string()) { + p_list->push_back(PropertyInfo(dic->get_valid(E).get_type(), E)); } } } else if (type == OBJECT) { diff --git a/core/variant/variant_utility.cpp b/core/variant/variant_utility.cpp index 7534a154a1..384fe6c4a6 100644 --- a/core/variant/variant_utility.cpp +++ b/core/variant/variant_utility.cpp @@ -452,12 +452,14 @@ Variant VariantUtilityFunctions::lerp(const Variant &from, const Variant &to, do case Variant::QUATERNION: case Variant::BASIS: case Variant::COLOR: + case Variant::TRANSFORM2D: + case Variant::TRANSFORM3D: break; default: r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = 0; r_error.expected = Variant::NIL; - return R"(Argument "from" must be "int", "float", "Vector2", "Vector3", "Vector4", "Quaternion", "Basis, or "Color".)"; + return R"(Argument "from" must be "int", "float", "Vector2", "Vector3", "Vector4", "Color", "Quaternion", "Basis", "Transform2D", or "Transform3D".)"; } if (from.get_type() != to.get_type()) { @@ -490,6 +492,12 @@ Variant VariantUtilityFunctions::lerp(const Variant &from, const Variant &to, do case Variant::BASIS: { return VariantInternalAccessor<Basis>::get(&from).slerp(VariantInternalAccessor<Basis>::get(&to), weight); } break; + case Variant::TRANSFORM2D: { + return VariantInternalAccessor<Transform2D>::get(&from).interpolate_with(VariantInternalAccessor<Transform2D>::get(&to), weight); + } break; + case Variant::TRANSFORM3D: { + return VariantInternalAccessor<Transform3D>::get(&from).interpolate_with(VariantInternalAccessor<Transform3D>::get(&to), weight); + } break; case Variant::COLOR: { return VariantInternalAccessor<Color>::get(&from).lerp(VariantInternalAccessor<Color>::get(&to), weight); } break; |