diff options
Diffstat (limited to 'core')
32 files changed, 1569 insertions, 262 deletions
diff --git a/core/core_bind.compat.inc b/core/core_bind.compat.inc index 83b7b33e38..3e8ac3c5de 100644 --- a/core/core_bind.compat.inc +++ b/core/core_bind.compat.inc @@ -32,6 +32,8 @@ namespace core_bind { +// Semaphore + void Semaphore::_post_bind_compat_93605() { post(1); } @@ -40,6 +42,16 @@ void Semaphore::_bind_compatibility_methods() { ClassDB::bind_compatibility_method(D_METHOD("post"), &Semaphore::_post_bind_compat_93605); } -}; // namespace core_bind +// OS + +Dictionary OS::_execute_with_pipe_bind_compat_94434(const String &p_path, const Vector<String> &p_arguments) { + return execute_with_pipe(p_path, p_arguments, true); +} + +void OS::_bind_compatibility_methods() { + ClassDB::bind_compatibility_method(D_METHOD("execute_with_pipe", "path", "arguments"), &OS::_execute_with_pipe_bind_compat_94434); +} + +} // namespace core_bind -#endif +#endif // DISABLE_DEPRECATED diff --git a/core/core_bind.cpp b/core/core_bind.cpp index 4172793f9d..750598ab20 100644 --- a/core/core_bind.cpp +++ b/core/core_bind.cpp @@ -314,12 +314,12 @@ int OS::execute(const String &p_path, const Vector<String> &p_arguments, Array r 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) { @@ -619,7 +619,7 @@ void OS::_bind_methods() { 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_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); diff --git a/core/core_bind.h b/core/core_bind.h index 122963e634..e4ba0e8f56 100644 --- a/core/core_bind.h +++ b/core/core_bind.h @@ -128,6 +128,12 @@ 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, @@ -161,7 +167,7 @@ public: 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); + 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); 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/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/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 d4b50facb2..63962a310b 100644 --- a/core/extension/gdextension.cpp +++ b/core/extension/gdextension.cpp @@ -355,7 +355,7 @@ void GDExtension::_register_extension_class_internal(GDExtensionClassLibraryPtr 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_ascii_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; diff --git a/core/extension/gdextension_interface.cpp b/core/extension/gdextension_interface.cpp index 0ebe86d0a7..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) { @@ -1679,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); diff --git a/core/extension/gdextension_interface.h b/core/extension/gdextension_interface.h index 9057e04bf3..d3132baf1b 100644 --- a/core/extension/gdextension_interface.h +++ b/core/extension/gdextension_interface.h @@ -2372,6 +2372,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 */ /** diff --git a/core/io/resource.cpp b/core/io/resource.cpp index ff12dc5851..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() { @@ -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); } 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_loader.cpp b/core/io/resource_loader.cpp index 7cf101b0de..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" @@ -234,17 +235,22 @@ void ResourceLoader::LoadToken::clear() { // User-facing tokens shouldn't be deleted until completely claimed. DEV_ASSERT(user_rc == 0 && user_path.is_empty()); - 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.task_id && !load_task.awaited) { - task_to_await = load_task.task_id; + 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); } - // 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(); + local_path.clear(); // Mark as already cleared. } } @@ -313,6 +319,7 @@ Ref<Resource> ResourceLoader::_load(const String &p_path, const String &p_origin } // 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; @@ -324,6 +331,9 @@ void ResourceLoader::_run_load_task(void *p_userdata) { } } + 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) { @@ -440,6 +450,9 @@ void ResourceLoader::_run_load_task(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(); } @@ -451,6 +464,8 @@ void ResourceLoader::_run_load_task(void *p_userdata) { } DEV_ASSERT(load_paths_stack.is_empty()); } + + curr_load_task = curr_load_task_backup; } static String _validate_local_path(const String &p_path) { @@ -521,9 +536,7 @@ Ref<ResourceLoader::LoadToken> ResourceLoader::_load_start(const String &p_path, 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); @@ -578,12 +591,11 @@ Ref<ResourceLoader::LoadToken> ResourceLoader::_load_start(const String &p_path, } } - // 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); @@ -591,9 +603,12 @@ 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) { + 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) { @@ -606,11 +621,8 @@ Ref<ResourceLoader::LoadToken> ResourceLoader::_load_start(const String &p_path, } } // MutexLock(thread_load_mutex). - if (run_on_current_thread) { + if (p_thread_mode == LOAD_THREAD_FROM_CURRENT) { _run_load_task(load_task_ptr); - if (must_not_register) { - load_token->res_if_unregistered = load_task_ptr->resource; - } } return load_token; @@ -738,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; @@ -777,6 +792,7 @@ Ref<Resource> ResourceLoader::_load_complete_inner(LoadToken &p_load_token, Erro // 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); } @@ -809,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() { @@ -838,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); @@ -1368,6 +1457,7 @@ bool ResourceLoader::timestamp_on_load = false; thread_local int ResourceLoader::load_nesting = 0; 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; diff --git a/core/io/resource_loader.h b/core/io/resource_loader.h index f75bf019fb..caaf9f8f45 100644 --- a/core/io/resource_loader.h +++ b/core/io/resource_loader.h @@ -106,6 +106,8 @@ class ResourceLoader { MAX_LOADERS = 64 }; + struct ThreadLoadTask; + public: enum ThreadLoadStatus { THREAD_LOAD_INVALID_RESOURCE, @@ -124,7 +126,7 @@ public: String local_path; String user_path; uint32_t user_rc = 0; // Having user RC implies regular RC incremented in one, until the user RC reaches zero. - Ref<Resource> res_if_unregistered; + ThreadLoadTask *task_if_unregistered = nullptr; void clear(); @@ -187,6 +189,13 @@ private: Ref<Resource> resource; 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 _run_load_task(void *p_userdata); @@ -194,6 +203,7 @@ private: static thread_local int load_nesting; 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; + static thread_local ThreadLoadTask *curr_load_task; static SafeBinaryMutex<BINARY_MUTEX_TAG> thread_load_mutex; friend SafeBinaryMutex<BINARY_MUTEX_TAG> &_get_res_loader_mutex(); @@ -214,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/math/a_star_grid_2d.cpp b/core/math/a_star_grid_2d.cpp index b1d2f82f9d..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; @@ -580,7 +621,7 @@ TypedArray<Dictionary> AStarGrid2D::get_point_data_in_region(const Rect2i &p_reg Dictionary dict; dict["id"] = p.id; dict["position"] = p.pos; - dict["solid"] = p.solid; + dict["solid"] = _get_solid_unchecked(p.id); dict["weight_scale"] = p.weight_scale; data.push_back(dict); } diff --git a/core/math/a_star_grid_2d.h b/core/math/a_star_grid_2d.h index c2be0bbf29..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,6 +159,7 @@ 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: 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/object/object.cpp b/core/object/object.cpp index 4be1dc4b34..d6b7d7a7fe 100644 --- a/core/object/object.cpp +++ b/core/object/object.cpp @@ -1724,33 +1724,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); diff --git a/core/object/object.h b/core/object/object.h index bc3f663baf..19e6fc5d47 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, }; diff --git a/core/os/os.h b/core/os/os.h index e6ce527720..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; @@ -130,9 +131,11 @@ public: 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; } @@ -179,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/string/string_name.cpp b/core/string/string_name.cpp index 28077fc8c5..0294dbfbbc 100644 --- a/core/string/string_name.cpp +++ b/core/string/string_name.cpp @@ -39,6 +39,30 @@ StaticCString StaticCString::create(const char *p_ptr) { return scs; } +bool StringName::_Data::operator==(const String &p_name) const { + if (cname) { + return p_name == cname; + } else { + return name == p_name; + } +} + +bool StringName::_Data::operator!=(const String &p_name) const { + return !operator==(p_name); +} + +bool StringName::_Data::operator==(const char *p_name) const { + if (cname) { + return strcmp(cname, p_name) == 0; + } else { + return name == p_name; + } +} + +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()); } @@ -139,19 +163,19 @@ void StringName::unref() { } 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 { @@ -168,9 +192,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(); @@ -178,6 +240,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) { @@ -216,7 +280,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; @@ -275,7 +339,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; @@ -333,7 +397,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; @@ -392,7 +456,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; @@ -429,7 +493,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; @@ -455,7 +519,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; @@ -474,15 +538,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 0eb98cf64b..288e2c7520 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; @@ -99,6 +104,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; @@ -175,7 +184,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/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/dictionary.cpp b/core/variant/dictionary.cpp index 733d13a106..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 { @@ -120,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(); @@ -129,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; } @@ -138,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; } @@ -155,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; } } @@ -168,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; } } @@ -177,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 { @@ -238,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; } } } @@ -256,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; @@ -284,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; } @@ -301,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; } @@ -316,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 @@ -324,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; @@ -354,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"); @@ -374,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; @@ -385,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_call.cpp b/core/variant/variant_call.cpp index 83f1f981b3..2da94de875 100644 --- a/core/variant/variant_call.cpp +++ b/core/variant/variant_call.cpp @@ -2254,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()); @@ -2266,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 a46fadb198..68210a9451 100644 --- a/core/variant/variant_construct.h +++ b/core/variant/variant_construct.h @@ -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) { 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 0cd4b86fe7..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; 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; |