diff options
Diffstat (limited to 'core')
121 files changed, 4627 insertions, 2423 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 5b04986020..32f36e01f9 100644 --- a/core/config/project_settings.cpp +++ b/core/config/project_settings.cpp @@ -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); diff --git a/core/string/translation_server.compat.inc b/core/core_bind.compat.inc index 9d1ee8b9df..3e8ac3c5de 100644 --- a/core/string/translation_server.compat.inc +++ b/core/core_bind.compat.inc @@ -1,5 +1,5 @@ /**************************************************************************/ -/* translation_server.compat.inc */ +/* core_bind.compat.inc */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -30,9 +30,28 @@ #ifndef DISABLE_DEPRECATED -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("")); +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); +} + +// 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); } -#endif +void OS::_bind_compatibility_methods() { + ClassDB::bind_compatibility_method(D_METHOD("execute_with_pipe", "path", "arguments"), &OS::_execute_with_pipe_bind_compat_94434); +} + +} // namespace core_bind + +#endif // DISABLE_DEPRECATED diff --git a/core/core_bind.cpp b/core/core_bind.cpp index a15085bcde..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 ////// @@ -1500,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); @@ -1622,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); @@ -1748,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 69a359e4ed..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 { @@ -459,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_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.h b/core/error/error_macros.h index d31adb72be..19c16667d0 100644 --- a/core/error/error_macros.h +++ b/core/error/error_macros.h @@ -843,6 +843,6 @@ void _physics_interpolation_warning(const char *p_function, const char *p_file, _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__, UINT64_MAX, 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..296ebc901f 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); } diff --git a/core/extension/gdextension.cpp b/core/extension/gdextension.cpp index cb6832ea39..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,55 +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)); - } - } - - 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 - }; - Error err = OS::get_singleton()->open_dynamic_library(abs_path, library, &data); +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 @@ -828,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); @@ -847,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; @@ -871,7 +751,7 @@ GDExtension::GDExtension() { } GDExtension::~GDExtension() { - if (library != nullptr) { + if (is_library_open()) { close_library(); } #ifdef TOOLS_ENABLED @@ -888,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); @@ -909,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; - } + GDExtensionManager *extension_manager = GDExtensionManager::get_singleton(); - 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); - } - - 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; } @@ -1085,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 fce377f967..8eb8a2ed33 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; @@ -2337,6 +2371,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 +2730,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 +2743,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 +2789,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 +2805,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 +2821,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 +2835,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 * 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/string/translation.compat.inc b/core/extension/gdextension_library_loader.h index 68bd1831e4..f4372a75d4 100644 --- a/core/string/translation.compat.inc +++ b/core/extension/gdextension_library_loader.h @@ -1,5 +1,5 @@ /**************************************************************************/ -/* translation.compat.inc */ +/* gdextension_library_loader.h */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,14 +28,57 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ -#ifndef DISABLE_DEPRECATED +#ifndef GDEXTENSION_LIBRARY_LOADER_H +#define GDEXTENSION_LIBRARY_LOADER_H -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("")); -} +#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/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 02bf0a6039..eec27ce0aa 100644 --- a/core/io/file_access_pack.cpp +++ b/core/io/file_access_pack.cpp @@ -313,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); @@ -366,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 b35d405662..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" @@ -2734,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); @@ -3115,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; @@ -3564,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)); @@ -3800,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; @@ -3831,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; @@ -4143,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 d55cc39dbb..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 *); @@ -383,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_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 a24277fe72..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); } 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/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 432adb88da..6177cba6a4 100644 --- a/core/io/resource.cpp +++ b/core/io/resource.cpp @@ -40,12 +40,12 @@ #include <stdio.h> void Resource::emit_changed() { - if (ResourceLoader::is_within_load() && MessageQueue::get_main_singleton() != MessageQueue::get_singleton() && !MessageQueue::get_singleton()->is_flushing()) { - // Let the connection happen on the call queue, later, since signals are not thread-safe. - call_deferred("emit_signal", CoreStringName(changed)); - } else { - emit_signal(CoreStringName(changed)); + if (ResourceLoader::is_within_load() && !Thread::is_main_thread()) { + ResourceLoader::resource_changed_emit(this); + return; } + + emit_signal(CoreStringName(changed)); } void Resource::_resource_path_changed() { @@ -60,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(); } @@ -166,22 +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() && MessageQueue::get_main_singleton() != MessageQueue::get_singleton() && !MessageQueue::get_singleton()->is_flushing()) { - // Let the check and connection happen on the call queue, later, since signals are not thread-safe. - callable_mp(this, &Resource::connect_changed).call_deferred(p_callable, 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() && MessageQueue::get_main_singleton() != MessageQueue::get_singleton() && !MessageQueue::get_singleton()->is_flushing()) { - // Let the check and disconnection happen on the call queue, later, since signals are not thread-safe. - callable_mp(this, &Resource::disconnect_changed).call_deferred(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); } @@ -416,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 @@ -492,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 @@ -558,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() : @@ -573,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; @@ -609,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; @@ -631,28 +621,27 @@ bool ResourceCache::has(const String &p_path) { Ref<Resource> ResourceCache::get_ref(const String &p_path) { Ref<Resource> ref; - lock.lock(); - - Resource **res = resources.getptr(p_path); + { + MutexLock mutex_lock(lock); + 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; @@ -672,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 4988e73624..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" @@ -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,23 +318,26 @@ 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. CallQueue *own_mq_override = nullptr; if (load_nesting == 0) { - load_paths_stack = memnew(Vector<String>); - + 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()) { @@ -319,12 +349,21 @@ void ResourceLoader::_thread_load_function(void *p_userdata) { } // -- + bool xl_remapped = false; + const String &remapped_path = _path_remap(load_task.local_path, &xl_remapped); + + print_verbose("Loading resource: " + remapped_path); + Error load_err = OK; - 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_err, load_task.use_sub_threads, &load_task.progress); + 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; @@ -337,11 +376,10 @@ void ResourceLoader::_thread_load_function(void *p_userdata) { 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; @@ -354,27 +392,40 @@ void ResourceLoader::_thread_load_function(void *p_userdata) { 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); } @@ -399,6 +450,9 @@ void ResourceLoader::_thread_load_function(void *p_userdata) { } } + // 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(); } @@ -408,11 +462,10 @@ void ResourceLoader::_thread_load_function(void *p_userdata) { MessageQueue::set_thread_singleton_override(nullptr); memdelete(own_mq_override); } - if (load_paths_stack) { - memdelete(load_paths_stack); - load_paths_stack = nullptr; - } + DEV_ASSERT(load_paths_stack.is_empty()); } + + curr_load_task = curr_load_task_backup; } static String _validate_local_path(const String &p_path) { @@ -427,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; @@ -468,22 +529,32 @@ 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; bool must_not_register = false; - ThreadLoadTask unregistered_load_task; // Once set, must be valid up to the call to do the load. 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 (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). @@ -494,12 +565,14 @@ Ref<ResourceLoader::LoadToken> ResourceLoader::_load_start(const String &p_path, 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; @@ -512,17 +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; } } - // If we want to ignore cache, but there's another task loading it, we can't add this one to the map and we also have to finish within scope. + // 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->local_path.clear(); - unregistered_load_task = load_task; - load_task_ptr = &unregistered_load_task; + 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); @@ -530,20 +603,26 @@ Ref<ResourceLoader::LoadToken> ResourceLoader::_load_start(const String &p_path, } } - run_on_current_thread = must_not_register || 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 (must_not_register) { - 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; @@ -627,22 +706,16 @@ 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) { - thread_load_lock.~MutexLock(); + thread_load_lock.temp_unlock(); bool exit = !_ensure_load_progress(); OS::get_singleton()->delay_usec(1000); - new (&thread_load_lock) MutexLock(thread_load_mutex); + thread_load_lock.temp_relock(); if (exit) { break; } @@ -650,8 +723,15 @@ Ref<Resource> ResourceLoader::load_threaded_get(const String &p_path, Error *r_e } 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; + } } } @@ -670,7 +750,10 @@ 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)) { if (r_error) { *r_error = ERR_BUG; @@ -683,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. @@ -694,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. + 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); + } + + p_thread_load_lock.temp_relock(); load_task.awaited = true; - thread_load_mutex.unlock(); - wtp_task_err = WorkerThreadPool::get_singleton()->wait_for_task_completion(load_task.task_id); - } - 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(); - } - } 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); + 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); } } @@ -745,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() { @@ -774,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); @@ -1050,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; + } } } } @@ -1101,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()) { @@ -1151,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) { @@ -1159,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; } } @@ -1171,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() { @@ -1298,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 = nullptr; +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 5f1831f0d9..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,9 +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 type_hint; float progress = 0.0f; float max_reported_progress = 0.0f; @@ -178,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; @@ -206,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 984bb1c9c1..c40ee5b4d7 100644 --- a/core/math/a_star_grid_2d.cpp +++ b/core/math/a_star_grid_2d.cpp @@ -127,13 +127,18 @@ void AStarGrid2D::update() { } 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) { @@ -150,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; } @@ -207,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) { @@ -238,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); } } } @@ -259,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; @@ -276,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; - } - if (_jump(p_to, _get_point(to_x + 1, to_y)) != nullptr) { - 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 (_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; } @@ -394,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; } @@ -436,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); } } @@ -454,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; } @@ -500,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; @@ -535,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) { @@ -562,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)); @@ -698,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/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 fd53ed28fd..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); } 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/object/class_db.cpp b/core/object/class_db.cpp index ceeb04b8ea..9826d73a9d 100644 --- a/core/object/class_db.cpp +++ b/core/object/class_db.cpp @@ -181,7 +181,7 @@ 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, that isn't a runtime class. @@ -192,7 +192,7 @@ public: 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 @@ -227,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; @@ -271,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) { @@ -303,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; @@ -502,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]); } @@ -516,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) { @@ -554,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); @@ -565,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]); } @@ -626,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; @@ -643,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]); } @@ -680,7 +766,7 @@ 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) { @@ -695,7 +781,18 @@ bool ClassDB::is_abstract(const StringName &p_class) { Ref<Script> scr = ResourceLoader::load(path); return scr.is_valid() && scr->is_valid() && scr->is_abstract(); } - return ti->creation_func == nullptr && (!ti->gdextension || ti->gdextension->create_instance == nullptr); + + 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) { @@ -715,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) { @@ -1547,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; @@ -2207,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 228b82b588..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,11 +289,13 @@ 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); @@ -291,6 +305,7 @@ public: 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); diff --git a/core/object/object.cpp b/core/object/object.cpp index ac23bf831f..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" @@ -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) + "."); } @@ -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 cdc56e5ec5..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; @@ -704,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 59a43a7b29..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; @@ -101,6 +102,7 @@ public: static void init_languages(); static void finish_languages(); static bool are_languages_initialized(); + static bool thread_is_entered(); }; class PlaceHolderScriptInstance; @@ -454,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 56b9fa8475..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) { @@ -62,17 +63,14 @@ void WorkerThreadPool::_process_task(Task *p_task) { // 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; @@ -126,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. @@ -326,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(); @@ -348,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) { @@ -428,13 +423,9 @@ Error WorkerThreadPool::wait_for_task_completion(TaskID p_task_id) { 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] & ~1))->lock(); - } + for (uint32_t i = 0; i < MAX_UNLOCKABLE_LOCKS; i++) { + if (unlockable_locks[i].ulock) { + unlockable_locks[i].ulock->lock(); } } #endif @@ -442,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] & ~1))->unlock(); - } + for (uint32_t i = 0; i < MAX_UNLOCKABLE_LOCKS; i++) { + if (unlockable_locks[i].ulock) { + unlockable_locks[i].ulock->unlock(); } } #endif @@ -468,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. @@ -523,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; @@ -538,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; } @@ -546,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()); @@ -605,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) { @@ -648,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 } @@ -665,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] & ~1) == (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 @@ -708,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++) { @@ -768,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/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 91e0ce9379..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; 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 c866ff0415..220ed9da31 100644 --- a/core/register_core_types.cpp +++ b/core/register_core_types.cpp @@ -107,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; @@ -297,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"); } @@ -349,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"); } @@ -382,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 33d4a1bcde..020949371f 100644 --- a/core/string/translation.cpp +++ b/core/string/translation.cpp @@ -29,7 +29,6 @@ /**************************************************************************/ #include "translation.h" -#include "translation.compat.inc" #include "core/os/os.h" #include "core/os/thread.h" diff --git a/core/string/translation.h b/core/string/translation.h index 2c5baae8b7..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); diff --git a/core/string/translation_server.cpp b/core/string/translation_server.cpp index 6e784881d0..d4aa152340 100644 --- a/core/string/translation_server.cpp +++ b/core/string/translation_server.cpp @@ -29,7 +29,6 @@ /**************************************************************************/ #include "translation_server.h" -#include "translation_server.compat.inc" #include "core/config/project_settings.h" #include "core/io/resource_loader.h" @@ -284,6 +283,11 @@ String TranslationServer::_standardize_locale(const String &p_locale, bool p_add } 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); diff --git a/core/string/translation_server.h b/core/string/translation_server.h index ebe81d9712..bb285ab19c 100644 --- a/core/string/translation_server.h +++ b/core/string/translation_server.h @@ -74,10 +74,6 @@ class TranslationServer : public Object { static void _bind_methods(); -#ifndef DISABLE_DEPRECATED - static void _bind_compatibility_methods(); -#endif - struct LocaleScriptInfo { String name; String script; diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp index 3aaaf46b06..2683addd4b 100644 --- a/core/string/ustring.cpp +++ b/core/string/ustring.cpp @@ -1694,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; } @@ -1955,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; } @@ -1986,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 @@ -2011,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. } } @@ -3151,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; } @@ -3164,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; @@ -3178,8 +3194,9 @@ 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; } @@ -3917,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; } @@ -4032,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; } - new_string += substr(search_from, length() - search_from); + 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_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; @@ -4088,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()); - } + const int old_length = length(); + const int key_length = strlen(p_key); + const int with_length = strlen(p_with); - return *this; -} + String new_string; + new_string.resize(old_length + (with_length - key_length) + 1); -String String::replacen(const String &p_key, const String &p_with) const { - String new_string; - int search_from = 0; - int result = 0; - - 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 { @@ -4509,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; } @@ -4532,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) { @@ -4554,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(); @@ -4800,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; } 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 1e6c6e42a9..8ef5dd3064 100644 --- a/core/templates/command_queue_mt.h +++ b/core/templates/command_queue_mt.h @@ -362,23 +362,24 @@ 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]); } @@ -387,14 +388,11 @@ class CommandQueueMT { 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) { @@ -410,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) @@ -446,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/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 54cd1eda2f..869499e668 100644 --- a/core/variant/array.cpp +++ b/core/variant/array.cpp @@ -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..f2522a4545 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_key.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/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 c1ef31c784..186643b024 100644 --- a/core/variant/variant.cpp +++ b/core/variant/variant.cpp @@ -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 1cb3580c01..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(); 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 5afdb884f6..68210a9451 100644 --- a/core/variant/variant_construct.h +++ b/core/variant/variant_construct.h @@ -232,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; @@ -240,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(); @@ -400,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) { @@ -417,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; @@ -426,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_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; |